Skip to content
Snippets Groups Projects
Commit a3228a87 authored by Cam Saül's avatar Cam Saül Committed by Cam Saül
Browse files

Minor formatting cleanup :yum:

parent d1a43668
No related branches found
No related tags found
No related merge requests found
Showing
with 217 additions and 299 deletions
......@@ -6,25 +6,27 @@
(:import (metabase.driver.query_processor.interface DateTimeField
Field)))
(defmulti parse-value (fn [field value]
(class field)))
(defprotocol IParseValueForField
(parse-value [this value]
"Parse a value for a given type of `Field`."))
(defmethod parse-value Field [field value]
(map->Value {:field field
:value value}))
(extend-protocol IParseValueForField
Field
(parse-value [this value]
(map->Value {:field this, :value value}))
(defmethod parse-value DateTimeField [field value]
(match value
(_ :guard u/date-string?)
(map->DateTimeValue {:field field
:value (u/->Timestamp value)})
DateTimeField
(parse-value [this value]
(match value
(_ :guard u/date-string?)
(map->DateTimeValue {:field this, :value (u/->Timestamp value)})
["relative_datetime" "current"]
(map->RelativeDateTimeValue {:amount 0, :field field})
["relative_datetime" "current"]
(map->RelativeDateTimeValue {:amount 0, :field this})
["relative_datetime" (amount :guard integer?) (unit :guard relative-datetime-value-unit?)]
(map->RelativeDateTimeValue {:amount amount
:field field
:unit (keyword unit)})
["relative_datetime" (amount :guard integer?) (unit :guard relative-datetime-value-unit?)]
(map->RelativeDateTimeValue {:amount amount
:field this
:unit (keyword unit)})
_ (throw (Exception. (format "Invalid value '%s': expected a DateTime." value)))))
_ (throw (Exception. (format "Invalid value '%s': expected a DateTime." value))))))
......@@ -11,12 +11,10 @@
InetSocketAddress
InetAddress)
java.sql.Timestamp
java.util.Calendar
(java.util Calendar TimeZone)
javax.xml.bind.DatatypeConverter
org.joda.time.format.DateTimeFormatter))
(set! *warn-on-reflection* true)
;;; ### Protocols
(defprotocol ITimestampCoercible
......@@ -32,6 +30,7 @@
this)
java.util.Date (->Timestamp [this]
(Timestamp. (.getTime this)))
;; Number is assumed to be a UNIX timezone in milliseconds (UTC)
Number (->Timestamp [this]
(Timestamp. this))
Calendar (->Timestamp [this]
......@@ -76,35 +75,39 @@
(defn now-iso8601
"Return the current date as an ISO-8601 formatted string."
[]
^String []
(date->iso-8601 (System/currentTimeMillis)))
(defn date-string?
"Is S a valid ISO 8601 date string?"
[s]
[^String s]
(boolean (when (string? s)
(try (->Timestamp s)
(catch Throwable e)))))
(defn ->Date
"Coerece DATE to a `java.util.Date`."
^java.util.Date [date]
(java.util.Date. (.getTime (->Timestamp date))))
(^java.util.Date []
(java.util.Date.))
(^java.util.Date [date]
(java.util.Date. (.getTime (->Timestamp date)))))
(defn ->Calendar
"Coerce DATE to a `java.util.Calendar`."
^java.util.Calendar [date]
(doto (Calendar/getInstance)
(.setTimeInMillis (.getTime (->Timestamp date)))))
(^java.util.Calendar []
(doto (Calendar/getInstance)
(.setTimeZone (TimeZone/getTimeZone "UTC"))))
(^java.util.Calendar [date]
(doto (->Calendar)
(.setTime (->Timestamp date)))))
(defn relative-date
"Return a new `Timestamp` relative to the current time using a relative date UNIT.
(relative-date :year -1) -> #inst 2014-11-12 ..."
^java.sql.Timestamp
([unit amount]
(^java.sql.Timestamp [unit amount]
(relative-date unit amount (Calendar/getInstance)))
([unit amount date]
(^java.sql.Timestamp [unit amount date]
(let [cal (->Calendar date)
[unit multiplier] (case unit
:second [Calendar/SECOND 1]
......@@ -133,13 +136,14 @@
(let [cal (->Calendar date)]
(case unit
:minute-of-hour (.get cal Calendar/MINUTE)
:hour-of-day (.get cal Calendar/HOUR)
:day-of-week (.get cal Calendar/DAY_OF_WEEK) ; 1 = Sunday, etc.
:hour-of-day (.get cal Calendar/HOUR_OF_DAY)
;; 1 = Sunday <-> 6 = Saturday
:day-of-week (.get cal Calendar/DAY_OF_WEEK)
:day-of-month (.get cal Calendar/DAY_OF_MONTH)
:day-of-year (.get cal Calendar/DAY_OF_YEAR)
:week-of-year (.get cal Calendar/WEEK_OF_YEAR)
:month-of-year (.get cal Calendar/MONTH)
:quarter-of-year (let [month (.get cal Calendar/MONTH)]
:month-of-year (inc (.get cal Calendar/MONTH))
:quarter-of-year (let [month (date-extract :month-of-year date)]
(int (/ (+ 2 month)
3)))
:year (.get cal Calendar/YEAR)))))
......@@ -153,25 +157,26 @@
(date-trunc :month).
;; -> #inst \"2015-11-01T00:00:00\""
([unit]
(^java.sql.Timestamp [unit]
(date-trunc unit (System/currentTimeMillis)))
([unit date]
(^java.sql.Timestamp [unit date]
(let [trunc-with-format (fn trunc-with-format
([format-string]
(trunc-with-format format-string date))
([format-string d]
(->Timestamp (format-date format-string d))))]
(case unit
:minute (trunc-with-format "yyyy-MM-dd'T'HH:mm:00")
:hour (trunc-with-format "yyyy-MM-dd'T'HH:00:00")
:day (trunc-with-format "yyyy-MM-dd")
:minute (trunc-with-format "yyyy-MM-dd'T'HH:mm:00+00:00")
:hour (trunc-with-format "yyyy-MM-dd'T'HH:00:00+00:00")
:day (trunc-with-format "yyyy-MM-dd+00:00")
:week (let [day-of-week (date-extract :day-of-week date)
date (relative-date :day (- (dec day-of-week)) date)]
(trunc-with-format "yyyy-MM-dd" date))
:month (trunc-with-format "yyyy-MM")
(trunc-with-format "yyyy-MM-dd+00:00" date))
:month (trunc-with-format "yyyy-MM-01+00:00")
:quarter (let [year (date-extract :year date)
quarter (date-extract :quarter date)]
(->Timestamp (format "%d-%02d" year (* 3 quarter))))))))
quarter (date-extract :quarter-of-year date)]
(->Timestamp (format "%d-%02d-01+00:00" year (- (* 3 quarter)
2))))))))
(defn date-trunc-or-extract
"Apply date bucketing with UNIT to DATE. DATE defaults to now."
......
......@@ -37,7 +37,7 @@
:display :table
:dataset_query {}
:visualization_settings {}
:database_id (db-id)}]
:database_id (id)}]
(with-temp Card [{id2 :id} {:name (random-name)
:public_perms common/perms-none
:creator_id (user->id :crowberto)
......@@ -50,7 +50,7 @@
(map :id)
set)
card-id))]
[(card-returned? (db-id) id1)
[(card-returned? (id) id1)
(card-returned? dbid id1)
(card-returned? dbid id2)])))))
......@@ -122,7 +122,7 @@
:visualization_settings {:global {:title nil}}
:public_perms 0
:created_at $
:database_id (db-id)
:database_id (id)
:table_id (id :categories)
:query_type "query"})
(post-card card-name)))
......@@ -155,7 +155,7 @@
:visualization_settings {:global {:title nil}}
:public_perms 0
:created_at $
:database_id (db-id)
:database_id (id)
:table_id (id :categories)
:query_type "query"})
(let [{:keys [id]} (post-card card-name)]
......
......@@ -127,7 +127,7 @@
:visualization_settings {:global {:title nil}}
:public_perms 0
:created_at $
:database_id (db-id)
:database_id (id)
:table_id (id :categories)
:query_type "query"})
:updated_at $
......
......@@ -35,7 +35,7 @@
:is_sample false
:organization_id nil
:description nil})
((user->client :rasta) :get 200 (format "database/%d" (db-id))))
((user->client :rasta) :get 200 (format "database/%d" (id))))
;; superusers *should* see DB details
(expect
......@@ -49,7 +49,7 @@
:is_sample false
:organization_id nil
:description nil})
((user->client :crowberto) :get 200 (format "database/%d" (db-id))))
((user->client :crowberto) :get 200 (format "database/%d" (id))))
;; ## POST /api/database
;; Check that we can create a Database
......@@ -202,9 +202,9 @@
:entity_name nil
:active true
:id (id :categories)
:db_id (db-id)
:db_id (id)
:created_at $})]})
(let [resp ((user->client :rasta) :get 200 (format "database/%d/metadata" (db-id)))]
(let [resp ((user->client :rasta) :get 200 (format "database/%d/metadata" (id)))]
(assoc resp :tables (filter #(= "CATEGORIES" (:name %)) (:tables resp)))))
......@@ -213,7 +213,7 @@
;; ## GET /api/database/:id/tables
;; These should come back in alphabetical order
(expect
(let [db-id (db-id)]
(let [db-id (id)]
[(match-$ (Table (id :categories))
{:description nil, :entity_type nil, :visibility_type nil, :schema "PUBLIC", :name "CATEGORIES", :rows 75, :updated_at $, :entity_name nil, :active true, :id $, :db_id db-id, :created_at $, :display_name "Categories"})
(match-$ (Table (id :checkins))
......@@ -222,4 +222,4 @@
{:description nil, :entity_type nil, :visibility_type nil, :schema "PUBLIC", :name "USERS", :rows 15, :updated_at $, :entity_name nil, :active true, :id $, :db_id db-id, :created_at $, :display_name "Users"})
(match-$ (Table (id :venues))
{:description nil, :entity_type nil, :visibility_type nil, :schema "PUBLIC", :name "VENUES", :rows 100, :updated_at $, :entity_name nil, :active true, :id $, :db_id db-id, :created_at $, :display_name "Venues"})])
((user->client :rasta) :get 200 (format "database/%d/tables" (db-id))))
((user->client :rasta) :get 200 (format "database/%d/tables" (id))))
......@@ -41,6 +41,6 @@
:running_time $
:id $
:uuid $})
((user->client :rasta) :post 200 "dataset" {:database (db-id)
((user->client :rasta) :post 200 "dataset" {:database (id)
:type "native"
:native {:query "foobar"}}))
......@@ -36,7 +36,7 @@
:entity_name nil
:active true
:id (id :users)
:db_id (db-id)
:db_id (id)
:created_at $})
:special_type "category" ; metabase.driver.generic-sql.sync/check-for-low-cardinality should have marked this as such because it had no other special_type
:name "NAME"
......
......@@ -27,7 +27,7 @@
:description rand-name
:public_perms 2
:display "table"
:dataset_query {:database (db-id)
:dataset_query {:database (id)
:type "query"
:query {:aggregation ["rows"]
:source_table (id :categories)}}
......
......@@ -29,25 +29,25 @@
(datasets/with-dataset-when-testing dataset-name
[{:name (format-name "categories")
:display_name "Categories"
:db_id (db-id)
:db_id (id)
:active true
:rows 75
:id (id :categories)}
{:name (format-name "checkins")
:display_name "Checkins"
:db_id (db-id)
:db_id (id)
:active true
:rows 1000
:id (id :checkins)}
{:name (format-name "users")
:display_name "Users"
:db_id (db-id)
:db_id (id)
:active true
:rows 15
:id (id :users)}
{:name (format-name "venues")
:display_name "Venues"
:db_id (db-id)
:db_id (id)
:active true
:rows 100
:id (id :venues)}]))))
......@@ -79,7 +79,7 @@
:active true
:pk_field (deref $pk_field)
:id (id :venues)
:db_id (db-id)
:db_id (id)
:created_at $})
((user->client :rasta) :get 200 (format "table/%d" (id :venues))))
......@@ -176,7 +176,7 @@
:entity_name nil
:active true
:id (id :categories)
:db_id (db-id)
:db_id (id)
:created_at $})
((user->client :rasta) :get 200 (format "table/%d/query_metadata" (id :categories))))
......@@ -291,7 +291,7 @@
:entity_name nil
:active true
:id (id :users)
:db_id (db-id)
:db_id (id)
:field_values {(keyword (str (id :users :last_login)))
user-last-login-date-strs
......@@ -389,7 +389,7 @@
:entity_name nil
:active true
:id (id :users)
:db_id (db-id)
:db_id (id)
:field_values {(keyword (str (id :users :last_login)))
user-last-login-date-strs
......@@ -441,7 +441,7 @@
:active true
:pk_field (deref $pk_field)
:id $
:db_id (db-id)
:db_id (id)
:created_at $})
(do ((user->client :crowberto) :put 200 (format "table/%d" (id :users)) {:display_name "Userz"
:entity_type "person"
......
......@@ -16,7 +16,7 @@
:cols [{:name :id, :base_type :IntegerField}]}}
(driver/process-query {:native {:query "SELECT ID FROM VENUES ORDER BY ID DESC LIMIT 2;"}
:type :native
:database (db-id)}))
:database (id)}))
;; Check that column ordering is maintained
(expect
......@@ -30,7 +30,7 @@
{:name :category_id, :base_type :IntegerField}]}}
(driver/process-query {:native {:query "SELECT ID, NAME, CATEGORY_ID FROM VENUES ORDER BY ID DESC LIMIT 2;"}
:type :native
:database (db-id)}))
:database (id)}))
;; Check that we get proper error responses for malformed SQL
(expect {:status :failed
......@@ -38,7 +38,7 @@
:error "Column \"ZID\" not found"}
(dissoc (driver/process-query {:native {:query "SELECT ZID FROM CHECKINS LIMIT 2;"} ; make sure people know it's to be expected
:type :native
:database (db-id)})
:database (id)})
:stacktrace
:query
:expanded-query))
......
......@@ -15,7 +15,7 @@
^{:doc "A delay that fetches or creates the Mongo test `Database`.
If DB is created, `load-data` and `sync-database!` are called to get the DB in a state that we can use for testing."}
mongo-test-db
(delay ((u/runtime-resolved-fn 'metabase.test.data 'get-or-create-database!) (loader/dataset-loader) data/test-data)))
(delay (@(resolve 'metabase.test.data/get-or-create-database!) (loader/dataset-loader) data/test-data)))
(defonce
^{:doc "A Delay that returns the ID of `mongo-test-db`, forcing creation of it if needed."}
......@@ -23,53 +23,3 @@
(delay (let [id (:id @mongo-test-db)]
(assert (integer? id))
id)))
;; TODO - This is duplicated with the code in metabase.test.data.datasets.
;; Remove this namespace when time permits.
(defn table-name->table
"Fetch `Table` for Mongo test database.
(table-name->table :users) -> {:id 100, :name \"users\", ...}"
[table-name]
{:pre [(keyword? table-name)]
:post [(map? %)]}
(assert (exists? Database :id @mongo-test-db-id)
(format "Database with ID %d no longer exists!?" @mongo-test-db-id))
(sel :one Table :db_id @mongo-test-db-id :name (name table-name)))
(def ^{:arglists '([table-name])} table-name->id
"Return ID of `Table` for Mongo test database (memoized).
(table-name->id :users) -> 10"
(memoize
(fn [table-name]
{:pre [(keyword? table-name)]
:post [(integer? %)]}
(:id (table-name->table table-name)))))
(defn table-name->field-name->field
"Return a map of `field-name -> field` for `Table` for Mongo test database."
[table-name]
(m/map-keys keyword (sel :many :field->obj [Field :name] :table_id (table-name->id table-name))))
(defn field-name->field
"Fetch `Field` for Mongo test database.
(field-name->field :users :name) -> {:id 292, :name \"name\", ...}"
[table-name field-name]
{:pre [(keyword? table-name)
(keyword? field-name)]
:post [(map? %)]}
((table-name->field-name->field table-name) field-name))
(def ^{:arglists '([table-name field-name])} field-name->id
"Return ID of `Field` for Mongo test Database (memoized).
(field-name->id :users :name) -> 292"
(memoize
(fn [table-name field-name]
{:pre [(keyword? table-name)
(keyword? field-name)]
:post [(integer? %)]}
(:id (field-name->field table-name field-name)))))
......@@ -1267,7 +1267,7 @@
;; RELATIVE DATES
(defn- database-def-with-timestamps [interval-seconds]
(let [{:keys [date-interval]} (driver/engine->driver *engine*)]
(let [{:keys [date-interval]} (driver)]
(create-database-definition "DB"
["checkins"
[{:field-name "timestamp"
......@@ -1306,7 +1306,7 @@
1
(with-temp-db [_ (checkins:1-per-day)]
(-> (driver/process-query
{:database (db-id)
{:database (id)
:type :query
:query {:source_table (id :checkins)
:aggregation ["count"]
......@@ -1317,7 +1317,7 @@
7
(with-temp-db [_ (checkins:1-per-day)]
(-> (driver/process-query
{:database (db-id)
{:database (id)
:type :query
:query {:source_table (id :checkins)
:aggregation ["count"]
......@@ -1331,7 +1331,7 @@
(defn- date-bucketing-unit-when-you [& {:keys [breakout-by filter-by]}]
(with-temp-db [_ (checkins:1-per-day)]
(let [results (driver/process-query
{:database (db-id)
{:database (id)
:type :query
:query {:source_table (id :checkins)
:aggregation ["count"]
......
......@@ -41,7 +41,7 @@
:creator_id (:id user)
:public_perms 2
:display "table"
:dataset_query {:database (db-id)
:dataset_query {:database (id)
:type :query
:query {:source_table (id :categories)}}
:visualization_settings {})
......@@ -61,7 +61,7 @@
:user_id (:id user)
:model "card"
:model_id (:id card)
:database_id (db-id)
:database_id (id)
:table_id (id :categories)
:details {:description (:description card)
:name (:name card)
......@@ -79,7 +79,7 @@
:user_id (:id user)
:model "card"
:model_id (:id card)
:database_id (db-id)
:database_id (id)
:table_id (id :categories)
:details {:description (:description card)
:name (:name card)
......@@ -97,7 +97,7 @@
:user_id (:id user)
:model "card"
:model_id (:id card)
:database_id (db-id)
:database_id (id)
:table_id (id :categories)
:details {:description (:description card)
:name (:name card)
......@@ -189,8 +189,8 @@
{:topic :database-sync
:user_id nil
:model "database"
:model_id (db-id)
:database_id (db-id)
:model_id (id)
:database_id (id)
:custom_id "abc"
:details {:status "started"
:name (:name (db))
......@@ -199,8 +199,8 @@
{:topic :database-sync
:user_id nil
:model "database"
:model_id (db-id)
:database_id (db-id)
:model_id (id)
:database_id (id)
:custom_id "abc"
:details {:status "completed"
:running_time 0
......@@ -210,11 +210,11 @@
(do
(k/delete Activity)
(let [_ (process-activity-event {:topic :database-sync-begin
:item {:database_id (db-id) :custom_id "abc"}})
:item {:database_id (id) :custom_id "abc"}})
activity1 (-> (db/sel :one Activity :topic "database-sync")
(select-keys [:topic :user_id :model :model_id :database_id :custom_id :details]))
_ (process-activity-event {:topic :database-sync-end
:item {:database_id (db-id) :custom_id "abc"}})
:item {:database_id (id) :custom_id "abc"}})
activity2 (-> (db/sel :one Activity :topic "database-sync")
(select-keys [:topic :user_id :model :model_id :database_id :custom_id :details])
(assoc-in [:details :running_time] 0))
......
......@@ -20,7 +20,7 @@
:description rand-name
:public_perms 2
:display "table"
:dataset_query {:database (db-id)
:dataset_query {:database (id)
:type "query"
:query {:aggregation ["rows"]
:source_table (id :categories)}}
......@@ -30,7 +30,7 @@
(defn- test-card-object [card]
{:description (:name card),
:table_id (id :categories),
:database_id (db-id),
:database_id (id),
:organization_id nil,
:query_type "query",
:name (:name card),
......
......@@ -20,44 +20,73 @@
FieldDefinition
TableDefinition)))
(declare temp-get)
(def ^:private ^:dynamic *temp-db* nil)
;;; ## ---------------------------------------- Dataset-Independent Data Fns ----------------------------------------
;; These functions offer a generic way to get bits of info like Table + Field IDs from any of our many driver/dataset combos.
(defn- default-dataset-get
([table-name]
(datasets/table-name->id *dataset* table-name))
([table-name field-name]
(datasets/field-name->id *dataset* table-name field-name)))
(def ^:dynamic *get-db*
(fn [] (datasets/db *dataset*)))
(defn db
"Return the current database.
Relies on the dynamic variable `*get-db`, which can be rebound with `with-db`."
[]
(*get-db*))
(defmacro with-db
"Run body with DB as the current database.
Calls to `db` and `id` use this value."
[db & body]
`(let [db# ~db]
(binding [*get-db* (constantly db#)]
~@body)))
(defn format-name [nm]
(datasets/format-name *dataset* (name nm)))
(defn- get-table-id-or-explode [db-id table-name]
(let [table-name (format-name table-name)]
(or (sel :one :id Table, :db_id db-id, :name table-name)
(throw (Exception. (format "No Table '%s' found for Database %d.\nFound: %s" table-name db-id
(u/pprint-to-str (sel :many :id->field [Table :name], :db_id db-id, :active true))))))))
(defn- get-field-id-or-explode [table-id field-name & {:keys [parent-id]}]
(let [field-name (format-name field-name)]
(or (sel :one :id Field, :active true, :table_id table-id, :name field-name, :parent_id parent-id)
(throw (Exception. (format "Couldn't find Field %s for Table %d.\nFound: %s"
(str \' field-name \' (when parent-id
(format " (parent: %d)" parent-id)))
table-id
(u/pprint-to-str (sel :many :id->field [Field :name], :active true, :table_id table-id))))))))
(defn id
"Return the ID of a `Table` or `Field` for the current driver / dataset."
{:arglists '([table-name]
[table-name field-name]
[table-name parent-field-name & nested-field-names])}
[& args]
{:pre [(every? keyword? args)]
:post [(integer? %)]}
(apply (if *temp-db* (comp :id temp-get)
default-dataset-get)
args))
(defn db []
"Get the ID of the current database or one of its `Tables` or `Fields`.
Relies on the dynamic variable `*get-db`, which can be rebound with `with-db`."
([]
{:post [(integer? %)]}
(:id (db)))
([table-name]
(get-table-id-or-explode (id) table-name))
([table-name field-name & nested-field-names]
(let [table-id (id table-name)]
(loop [parent-id (get-field-id-or-explode table-id field-name), [nested-field-name & more] nested-field-names]
(if-not nested-field-name
parent-id
(recur (get-field-id-or-explode table-id nested-field-name, :parent-id parent-id) more))))))
(defn driver
"Get the driver used by the current enigne."
[]
{:post [(map? %)]}
(if *temp-db*
*temp-db*
(datasets/db *dataset*)))
(driver/engine->driver datasets/*engine*))
(defn db-id []
{:post [(integer? %)]}
(:id (db)))
(defn fks-supported?
"Does the current engine support foreign keys?"
[]
(contains? (:features (driver)) :foreign-keys))
(defn fks-supported? [] (datasets/fks-supported? *dataset*))
(defn default-schema [] (datasets/default-schema *dataset*))
(defn format-name [name] (datasets/format-name *dataset* name))
(defn id-field-type [] (datasets/id-field-type *dataset*))
(defn sum-field-type [] (datasets/sum-field-type *dataset*))
(defn timestamp-field-type [] (datasets/timestamp-field-type *dataset*))
......@@ -123,73 +152,16 @@
(destroy-db! dataset-loader database-definition)))
;; ## Temporary Dataset Macros
;; The following functions are used internally by with-temp-db to implement easy Table/Field lookup
(defn- table-id->field-name->field
"Return a map of lowercased `Field` names -> fields for `Table` with TABLE-ID."
[table-id]
{:pre [(integer? table-id)]}
(->> (binding [*sel-disable-logging* true]
(sel :many :field->obj [Field :name], :table_id table-id, :parent_id nil))
(m/map-keys s/lower-case)
(m/map-keys (u/rpartial s/replace #"^_id$" "id")))) ; rename Mongo _id fields to ID so we can use the same name for any driver
(defn- db-id->table-name->table
"Return a map of lowercased `Table` names -> Tables for `Database` with DATABASE-ID.
Add a delay `:field-name->field` to each Table that calls `table-id->field-name->field` for that Table."
[database-id]
{:pre [(integer? database-id)]}
(->> (binding [*sel-disable-logging* true]
(sel :many :field->obj [Table :name] :db_id database-id))
(m/map-keys s/lower-case)
(m/map-vals #(assoc % :field-name->field (delay (table-id->field-name->field (:id %)))))))
(defn- temp-db-add-getter-delay
"Add a delay `:table-name->table` to DB that calls `db-id->table-name->table`."
[db]
(assoc db :table-name->table (delay (db-id->table-name->table (:id db)))))
(defn temp-get
"Internal - don't call this directly.
With two args, fetch `Table` with TABLE-NAME using `:table-name->table` delay on TEMP-DB.
With three args, fetch `Field` with FIELD-NAME by recursively fetching `Table` and using its `:field-name->field` delay."
([table-name]
{:pre [*temp-db*]
:post [(or (map? %) (assert nil (format "Couldn't find table '%s'.\nValid choices are: %s" (name table-name)
(vec (keys @(:table-name->table *temp-db*))))))]}
(@(:table-name->table *temp-db*) (name table-name)))
([table-name field-name]
{:post [(or (map? %) (assert nil (format "Couldn't find field '%s.%s'.\nValid choices are: %s" (name table-name) (name field-name)
(vec (keys @(:field-name->field (temp-get table-name)))))))]}
(@(:field-name->field (temp-get table-name)) (name field-name)))
([table-name parent-field-name & nested-field-names]
{:post [(or (map? %) (assert nil (format "Couldn't find nested field '%s.%s.%s'.\nValid choices are: %s" (name table-name) (name parent-field-name)
(apply str (interpose "." (map name nested-field-names)))
(vec (map :name @(:children (apply temp-get table-name parent-field-name (butlast nested-field-names))))))))]}
(binding [*sel-disable-logging* true]
(let [parent (apply temp-get table-name parent-field-name (some-> (butlast nested-field-names)
name))
children @(:children parent)
child-name->child (zipmap (map :name children) children)]
(child-name->child (name (last nested-field-names)))))))
(defn -with-temp-db [^DatabaseDefinition dbdef f]
(let [loader (dataset-loader)
dbdef (map->DatabaseDefinition (assoc dbdef :short-lived? true))]
(try
(binding [*sel-disable-logging* true]
(let [db (-> (get-or-create-database! loader dbdef)
temp-db-add-getter-delay)]
(assert db)
(assert (exists? Database :id (:id db)))
(binding [*temp-db* db
*sel-disable-logging* false]
(f db))))
(with-db (binding [*sel-disable-logging* true]
(let [db (get-or-create-database! loader dbdef)]
(assert db)
(assert (exists? Database :id (:id db)))
db))
(f db))
(finally
(binding [*sel-disable-logging* true]
(remove-database! loader dbdef))))))
......
......@@ -42,21 +42,21 @@
;; [name last_login]
(defonce ^:private users
(let [rando-password (fn [] (str (java.util.UUID/randomUUID)))]
[["Plato Yeshua" #inst "2014-04-01T08:30:00.000000000-00:00" (rando-password)]
["Felipinho Asklepios" #inst "2014-12-05T15:15:00.000000000-00:00" (rando-password)]
["Kaneonuskatew Eiran" #inst "2014-11-06T16:15:00.000000000-00:00" (rando-password)]
["Simcha Yan" #inst "2014-01-01T08:30:00.000000000-00:00" (rando-password)]
["Quentin Sören" #inst "2014-10-03T17:30:00.000000000-00:00" (rando-password)]
["Shad Ferdynand" #inst "2014-08-02T12:30:00.000000000-00:00" (rando-password)]
["Conchúr Tihomir" #inst "2014-08-02T09:30:00.000000000-00:00" (rando-password)]
["Szymon Theutrich" #inst "2014-02-01T10:15:00.000000000-00:00" (rando-password)]
["Nils Gotam" #inst "2014-04-03T09:30:00.000000000-00:00" (rando-password)]
["Frans Hevel" #inst "2014-07-03T19:30:00.000000000-00:00" (rando-password)]
["Spiros Teofil" #inst "2014-11-01T07:00:00.000000000-00:00" (rando-password)]
["Kfir Caj" #inst "2014-07-03T01:30:00.000000000-00:00" (rando-password)]
["Dwight Gresham" #inst "2014-08-01T10:30:00.000000000-00:00" (rando-password)]
["Broen Olujimi" #inst "2014-10-03T13:45:00.000000000-00:00" (rando-password)]
["Rüstem Hebel" #inst "2014-08-01T12:45:00.000000000-00:00" (rando-password)]]))
[["Plato Yeshua" #inst "2014-04-01T08:30" (rando-password)]
["Felipinho Asklepios" #inst "2014-12-05T15:15" (rando-password)]
["Kaneonuskatew Eiran" #inst "2014-11-06T16:15" (rando-password)]
["Simcha Yan" #inst "2014-01-01T08:30" (rando-password)]
["Quentin Sören" #inst "2014-10-03T17:30" (rando-password)]
["Shad Ferdynand" #inst "2014-08-02T12:30" (rando-password)]
["Conchúr Tihomir" #inst "2014-08-02T09:30" (rando-password)]
["Szymon Theutrich" #inst "2014-02-01T10:15" (rando-password)]
["Nils Gotam" #inst "2014-04-03T09:30" (rando-password)]
["Frans Hevel" #inst "2014-07-03T19:30" (rando-password)]
["Spiros Teofil" #inst "2014-11-01T07:00" (rando-password)]
["Kfir Caj" #inst "2014-07-03T01:30" (rando-password)]
["Dwight Gresham" #inst "2014-08-01T10:30" (rando-password)]
["Broen Olujimi" #inst "2014-10-03T13:45" (rando-password)]
["Rüstem Hebel" #inst "2014-08-01T12:45" (rando-password)]]))
;; name
(defonce ^:private categories
......
......@@ -28,14 +28,6 @@
"Return a dataset loader (an object that implements `IDatasetLoader`) for this dataset/driver.")
(db [this]
"Return `Database` containing test data for this driver.")
(table-name->table [this table-name]
"Given a TABLE-NAME keyword like `:venues`, *fetch* the corresponding `Table`.")
(table-name->id [this table-name]
"Given a TABLE-NAME keyword like `:venues`, return corresponding `Table` ID.")
(field-name->id [this table-name field-name]
"Given keyword TABLE-NAME and FIELD-NAME, return the corresponding `Field` ID.")
(fks-supported? [this]
"Does this driver support Foreign Keys?")
(default-schema [this]
"Return the default schema name that tables for this DB should be expected to have.")
(format-name [this table-or-field-name]
......@@ -65,20 +57,10 @@
(db [_]
@mongo-data/mongo-test-db)
(table-name->table [_ table-name]
(mongo-data/table-name->table table-name))
(table-name->id [_ table-name]
(mongo-data/table-name->id table-name))
(field-name->id [_ table-name field-name]
(mongo-data/field-name->id table-name (if (= field-name :id) :_id
field-name)))
(format-name [_ table-or-field-name]
(if (= table-or-field-name "id") "_id"
table-or-field-name))
(fks-supported? [_] false)
(default-schema [_] nil)
(id-field-type [_] :IntegerField)
(sum-field-type [_] :IntegerField)
......@@ -87,38 +69,16 @@
;; ## Generic SQL
;; TODO - Mongo implementation (etc.) might find these useful
(def ^:private memoized-table-name->id
(memoize
(fn [db-id table-name]
{:pre [(string? table-name)]
:post [(integer? %)]}
(sel :one :id Table :name table-name, :db_id db-id))))
(def ^:private memoized-field-name->id
(memoize
(fn [db-id table-name field-name]
{:pre [(string? field-name)]
:post [(integer? %)]}
(sel :one :id Field :name field-name, :table_id (memoized-table-name->id db-id table-name)))))
(defn- generic-sql-load-data! [{:keys [dbpromise], :as this}]
(when-not (realized? dbpromise)
(deliver dbpromise ((u/runtime-resolved-fn 'metabase.test.data 'get-or-create-database!) (dataset-loader this) data/test-data)))
(deliver dbpromise (@(resolve 'metabase.test.data/get-or-create-database!) (dataset-loader this) data/test-data)))
@dbpromise)
(def ^:private GenericSQLIDatasetMixin
{:load-data! generic-sql-load-data!
:db generic-sql-load-data!
:table-name->id (fn [this table-name]
(memoized-table-name->id (:id (db this)) (name table-name)))
:table-name->table (fn [this table-name]
(Table (table-name->id this (name table-name))))
:field-name->id (fn [this table-name field-name]
(memoized-field-name->id (:id (db this)) (name table-name) (name field-name)))
:format-name (fn [_ table-or-field-name]
table-or-field-name)
:fks-supported? (constantly true)
:timestamp-field-type (constantly :DateTimeField)
:id-field-type (constantly :IntegerField)})
......@@ -132,12 +92,6 @@
(merge GenericSQLIDatasetMixin
{:dataset-loader (fn [_]
(h2/dataset-loader))
:table-name->id (fn [this table-name]
(memoized-table-name->id (:id (db this)) (s/upper-case (name table-name))))
:table-name->table (fn [this table-name]
(Table (table-name->id this (s/upper-case (name table-name)))))
:field-name->id (fn [this table-name field-name]
(memoized-field-name->id (:id (db this)) (s/upper-case (name table-name)) (s/upper-case (name field-name))))
:default-schema (constantly "PUBLIC")
:format-name (fn [_ table-or-field-name]
(clojure.string/upper-case table-or-field-name))
......@@ -186,7 +140,7 @@
;; # Concrete Instances
(def dataset-name->dataset
(def ^:private dataset-name->dataset*
"Map of dataset keyword name -> dataset instance (i.e., an object that implements `IDataset`)."
{:mongo (MongoDriverData.)
:h2 (H2DriverData. (promise))
......@@ -196,7 +150,11 @@
(def ^:const all-valid-dataset-names
"Set of names of all valid datasets."
(set (keys dataset-name->dataset)))
(set (keys dataset-name->dataset*)))
(defn dataset-name->dataset [engine]
(or (dataset-name->dataset* engine)
(throw (Exception.(format "Invalid engine: %s\nMust be one of: %s" engine all-valid-dataset-names)))))
;; # Logic for determining which datasets to test against
......@@ -220,7 +178,7 @@
;; Double check that the specified datasets are all valid
(map (fn [dataset-name]
(assert (contains? all-valid-dataset-names dataset-name)
(format "Invalid dataset specified in MB_TEST_DATASETS: %s" (name dataset-name)))
(format "Invalid dataset specified in MB_TEST_DATASETS: %s" (name dataset-name)))
dataset-name))
set)))
......
......@@ -263,7 +263,7 @@
(defmacro Q* [f & args]
`(Q** ~f
{:database (data/db-id)
{:database (data/id)
:type "query"
:query {}
:context {:driver nil
......
......@@ -8,6 +8,7 @@
[db :as db]
[util :as u])
(metabase.models [table :refer [Table]])
[metabase.test.data :as data]
[metabase.test.data.datasets :as datasets]))
;; # ---------------------------------------- EXPECTAIONS FRAMEWORK SETTINGS ------------------------------
......@@ -72,8 +73,9 @@
(datasets/load-data! dataset)
;; Check that dataset is loaded and working
(assert (Table (datasets/table-name->id dataset :venues))
(format "Loading test dataset %s failed: could not find 'venues' Table!" dataset-name)))))
(datasets/with-dataset dataset-name
(assert (Table (data/id :venues))
(format "Loading test dataset %s failed: could not find 'venues' Table!" dataset-name))))))
(defn test-startup
{:expectations-options :before-run}
......
(ns metabase.test-util
"Tests for functions in `metabase.util`.
TODO - Why isn't this named `metabase.util-test`?"
(ns metabase.util-test
"Tests for functions in `metabase.util`."
(:require [expectations :refer :all]
[metabase.util :refer :all]))
;;; Date stuff
(def ^:private ^:const friday-the-13th #inst "2015-11-13T19:05:55")
(def ^:private ^:const saturday-the-14th #inst "2015-11-14T04:18:26")
(expect friday-the-13th (->Timestamp (->Date friday-the-13th)))
(expect friday-the-13th (->Timestamp (->Calendar friday-the-13th)))
(expect friday-the-13th (->Timestamp (->Calendar (.getTime friday-the-13th))))
(expect friday-the-13th (->Timestamp (.getTime friday-the-13th)))
(expect friday-the-13th (->Timestamp "2015-11-13T19:05:55+00:00"))
(expect 5 (date-extract :minute-of-hour friday-the-13th))
(expect 19 (date-extract :hour-of-day friday-the-13th))
(expect 6 (date-extract :day-of-week friday-the-13th))
(expect 7 (date-extract :day-of-week saturday-the-14th))
(expect 13 (date-extract :day-of-month friday-the-13th))
(expect 317 (date-extract :day-of-year friday-the-13th))
(expect 46 (date-extract :week-of-year friday-the-13th))
(expect 11 (date-extract :month-of-year friday-the-13th))
(expect 4 (date-extract :quarter-of-year friday-the-13th))
(expect 2015 (date-extract :year friday-the-13th))
(expect #inst "2015-11-13T19:05" (date-trunc :minute friday-the-13th))
(expect #inst "2015-11-13T19:00" (date-trunc :hour friday-the-13th))
(expect #inst "2015-11-13" (date-trunc :day friday-the-13th))
(expect #inst "2015-11-08" (date-trunc :week friday-the-13th))
(expect #inst "2015-11-08" (date-trunc :week saturday-the-14th))
(expect #inst "2015-11-01" (date-trunc :month friday-the-13th))
(expect #inst "2015-10-01" (date-trunc :quarter friday-the-13th))
;;; ## tests for ASSOC*
(expect {:a 100
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment