diff --git a/docs/developers-guide.md b/docs/developers-guide.md index 411b6c01c0f44708f575490591d1af3ba556a1c9..62121fb8503815f758a65cdaeded5f1b542feeab 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -189,7 +189,7 @@ Will give you a list of out-of-date dependencies. Once's this repo is made public, this Clojars badge will work and show the status as well: -[](http://jarkeeper.com/metabase/metabase) +[](https://jarkeeper.com/metabase/metabase) ## Documentation diff --git a/resources/migrations/019_add_schema_column_to_table.yaml b/resources/migrations/019_add_schema_column_to_table.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f8324856dee356f2c72e1aac4d30987793d0e465 --- /dev/null +++ b/resources/migrations/019_add_schema_column_to_table.yaml @@ -0,0 +1,11 @@ +databaseChangeLog: + - changeSet: + id: 19 + author: camsaul + changes: + - addColumn: + tableName: metabase_table + columns: + - column: + name: schema + type: VARCHAR(256) diff --git a/resources/migrations/liquibase.json b/resources/migrations/liquibase.json index 0baf1311681e58de0b48d399d286c766879c86a0..dd40971e4b708fa0a2b6aec31ac289f10fb2cb61 100644 --- a/resources/migrations/liquibase.json +++ b/resources/migrations/liquibase.json @@ -16,6 +16,7 @@ {"include": {"file": "migrations/015_add_revision_is_creation_field.yaml"}}, {"include": {"file": "migrations/016_user_last_login_allow_null.yaml"}}, {"include": {"file": "migrations/017_add_database_is_sample_field.yaml"}}, - {"include": {"file": "migrations/018_add_data_migrations_table.yaml"}} + {"include": {"file": "migrations/018_add_data_migrations_table.yaml"}}, + {"include": {"file": "migrations/019_add_schema_column_to_table.yaml"}} ] } diff --git a/src/metabase/core.clj b/src/metabase/core.clj index cbd4cf8162d1ff4a762b341942004fa3c8a5c2d1..3830201f90ce611bce9ff33a4b9518e90826a7ef 100644 --- a/src/metabase/core.clj +++ b/src/metabase/core.clj @@ -213,7 +213,7 @@ (defn- run-cmd [cmd & args] (let [cmd->fn {:migrate (fn [direction] - (db/migrate (keyword direction)))}] + (db/migrate @db/db-connection-details (keyword direction)))}] (if-let [f (cmd->fn cmd)] (do (apply f args) (println "Success.") diff --git a/src/metabase/db.clj b/src/metabase/db.clj index b52a434b56141a9a7736bf0e7bcee80230004118..a96c00c0e76c20e041f8516317e0498610bd4d87 100644 --- a/src/metabase/db.clj +++ b/src/metabase/db.clj @@ -54,7 +54,7 @@ codec/form-decode walk/keywordize-keys)))) -(def ^:private db-connection-details +(def db-connection-details "Connection details that can be used when pretending the Metabase DB is itself a `Database` (e.g., to use the Generic SQL driver functions on the Metabase DB itself)." (delay (or (when-let [uri (config/config-str :mb-db-connection-uri)] @@ -75,14 +75,16 @@ :user (config/config-str :mb-db-user) :password (config/config-str :mb-db-pass)})))) -(def ^:private jdbc-connection-details - "Connection details for Korma / JDBC." - (delay (let [details @db-connection-details] - (case (:type details) - :h2 (kdb/h2 (assoc details :naming {:keys s/lower-case - :fields s/upper-case})) - :mysql (kdb/mysql (assoc details :db (:dbname details))) - :postgres (kdb/postgres (assoc details :db (:dbname details))))))) +(defn jdbc-details + "Takes our own MB details map and formats them properly for connection details for Korma / JDBC." + [db-details] + {:pre [(map? db-details)]} + ;; TODO: it's probably a good idea to put some more validation here and be really strict about what's in `db-details` + (case (:type db-details) + :h2 (kdb/h2 (assoc db-details :naming {:keys s/lower-case + :fields s/upper-case})) + :mysql (kdb/mysql (assoc db-details :db (:dbname db-details))) + :postgres (kdb/postgres (assoc db-details :db (:dbname db-details))))) ;; ## MIGRATE @@ -100,24 +102,22 @@ * `:release-locks` - Manually release migration locks left by an earlier failed migration. (This shouldn't be necessary now that we run migrations inside a transaction, but is available just in case)." - ([direction] - (migrate @jdbc-connection-details direction)) - ([jdbc-connection-details direction] - (try - (jdbc/with-db-transaction [conn jdbc-connection-details] - (let [^Database database (-> (DatabaseFactory/getInstance) - (.findCorrectDatabaseImplementation (JdbcConnection. (jdbc/get-connection conn)))) - ^Liquibase liquibase (Liquibase. changelog-file (ClassLoaderResourceAccessor.) database)] - (case direction - :up (.update liquibase "") - :down (.rollback liquibase 10000 "") - :down-one (.rollback liquibase 1 "") - :print (let [writer (StringWriter.)] - (.update liquibase "" writer) - (.toString writer)) - :release-locks (.forceReleaseLocks liquibase)))) - (catch Throwable e - (throw (DatabaseException. e)))))) + [db-details direction] + (try + (jdbc/with-db-transaction [conn (jdbc-details db-details)] + (let [^Database database (-> (DatabaseFactory/getInstance) + (.findCorrectDatabaseImplementation (JdbcConnection. (jdbc/get-connection conn)))) + ^Liquibase liquibase (Liquibase. changelog-file (ClassLoaderResourceAccessor.) database)] + (case direction + :up (.update liquibase "") + :down (.rollback liquibase 10000 "") + :down-one (.rollback liquibase 1 "") + :print (let [writer (StringWriter.)] + (.update liquibase "" writer) + (.toString writer)) + :release-locks (.forceReleaseLocks liquibase)))) + (catch Throwable e + (throw (DatabaseException. e))))) ;; ## SETUP-DB @@ -148,23 +148,24 @@ (defn setup-db "Do general perparation of database by validating that we can connect. Caller can specify if we should run any pending database migrations." - [& {:keys [auto-migrate] - :or {auto-migrate true}}] + [& {:keys [db-details auto-migrate] + :or {db-details @db-connection-details + auto-migrate true}}] (reset! setup-db-has-been-called? true) ;; Test DB connection and throw exception if we have any troubles connecting (log/info "Verifying Database Connection ...") - (assert (db-can-connect? {:engine (:type @db-connection-details) - :details @db-connection-details}) + (assert (db-can-connect? {:engine (:type db-details) + :details db-details}) "Unable to connect to Metabase DB.") (log/info "Verify Database Connection ... CHECK") ;; Run through our DB migration process and make sure DB is fully prepared (if auto-migrate - (migrate :up) + (migrate db-details :up) ;; if we are not doing auto migrations then print out migration sql for user to run manually ;; then throw an exception to short circuit the setup process and make it clear we can't proceed - (let [sql (migrate :print)] + (let [sql (migrate db-details :print)] (log/info (str "Database Upgrade Required\n\n" "NOTICE: Your database requires updates to work with this version of Metabase. " "Please execute the following sql commands on your database before proceeding.\n\n" @@ -175,7 +176,7 @@ (log/info "Database Migrations Current ... CHECK") ;; Establish our 'default' Korma DB Connection - (kdb/default-connection (kdb/create-db @jdbc-connection-details)) + (kdb/default-connection (kdb/create-db (jdbc-details db-details))) ;; Do any custom code-based migrations now that the db structure is up to date ;; NOTE: we use dynamic resolution to prevent circular dependencies diff --git a/src/metabase/db/migrations.clj b/src/metabase/db/migrations.clj index 3a7ba7545a706ff7e4f49ad027a4090a54495712..4e4af8754df4c483bc7d4d1841ec2f05fce8f5ef 100644 --- a/src/metabase/db/migrations.clj +++ b/src/metabase/db/migrations.clj @@ -5,6 +5,7 @@ [metabase.db :as db] (metabase.models [card :refer [Card]] [database :refer [Database]] + [table :refer [Table]] [setting :as setting]) [metabase.sample-data :as sample-data] [metabase.util :as u])) @@ -66,3 +67,16 @@ (defmigration set-mongodb-databases-ssl-false (doseq [{:keys [id details]} (db/sel :many :fields [Database :id :details] :engine "mongo")] (db/upd Database id, :details (assoc details :ssl false)))) + + +;; Set default values for :schema in existing tables now that we've added the column +;; That way sync won't get confused next time around +(defmigration set-default-schemas + (doseq [[engine default-schema] [["postgres" "public"] + ["h2" "PUBLIC"]]] + (k/update Table + (k/set-fields {:schema default-schema}) + (k/where {:schema nil + :db_id [in (k/subselect Database + (k/fields :id) + (k/where {:engine engine}))]})))) diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj index d3b7f3e92612471c909b092f4a88654edeb8cdfe..2253387b38511832605d4addbfcf32e366318362 100644 --- a/src/metabase/driver.clj +++ b/src/metabase/driver.clj @@ -44,7 +44,7 @@ (def ^:private ^:const required-fns #{:can-connect? - :active-table-names + :active-tables :active-column-names->type :table-pks :field-values-lazy-seq @@ -171,9 +171,11 @@ Check whether we can connect to a `Database` with DETAILS-MAP and perform a simple query. For example, a SQL database might try running a query like `SELECT 1;`. This function should return `true` or `false`. -* `(active-table-names [database])` +* `(active-tables [database])` - Return a set of string names of tables, collections, or equivalent that currently exist in DATABASE. + Return a set of maps containing information about the active tables/views, collections, or equivalent that currently exist in DATABASE. + Each map should contain the key `:name`, which is the string name of the table. For databases that have a concept of schemas, + this map should also include the string name of the table's `:schema`. * `(active-column-names->type [table])` diff --git a/src/metabase/driver/generic_sql.clj b/src/metabase/driver/generic_sql.clj index 5b409ff6da8606c94bc35ff57a9ba29098dabe60..04de23abf524dcf3f3839ba4f3d03c8c6f27fd9b 100644 --- a/src/metabase/driver/generic_sql.clj +++ b/src/metabase/driver/generic_sql.clj @@ -31,23 +31,19 @@ (with-jdbc-metadata [_ database] (do-sync-fn))) -(defn- active-table-names [database] +(defn- active-tables [excluded-schemas database] (with-jdbc-metadata [^java.sql.DatabaseMetaData md database] - (->> (.getTables md nil nil nil (into-array String ["TABLE", "VIEW"])) - jdbc/result-set-seq - (map :table_name) - set))) + (set (for [table (filter #(not (contains? excluded-schemas (:table_schem %))) + (jdbc/result-set-seq (.getTables md nil nil nil (into-array String ["TABLE", "VIEW"]))))] + {:name (:table_name table) + :schema (:table_schem table)})))) (defn- active-column-names->type [column->base-type table] {:pre [(map? column->base-type)]} (with-jdbc-metadata [^java.sql.DatabaseMetaData md @(:db table)] - (->> (.getColumns md nil nil (:name table) nil) - jdbc/result-set-seq - (filter #(not= (:table_schem %) "INFORMATION_SCHEMA")) ; filter out internal tables - (map (fn [{:keys [column_name type_name]}] - {column_name (or (column->base-type (keyword type_name)) - :UnknownField)})) - (into {})))) + (into {} (for [{:keys [column_name type_name]} (jdbc/result-set-seq (.getColumns md nil (:schema table) (:name table) nil))] + {column_name (or (column->base-type (keyword type_name)) + :UnknownField)})))) (defn- table-pks [table] (with-jdbc-metadata [^java.sql.DatabaseMetaData md @(:db table)] @@ -188,6 +184,10 @@ Return a korma form for a date relative to NOW(), e.g. on that would produce SQL like `(NOW() + INTERVAL '1 month')`. + * `excluded-schemas` *(OPTIONAL)* + + Set of string names of schemas to skip syncing tables from. + * `qp-clause->handler` *(OPTIONAL)* A map of query processor clause keywords to functions of the form `(fn [korma-query query-map])` that are used apply them. @@ -198,14 +198,15 @@ ;; Verify the driver (verify-sql-driver driver) (merge - {:features #{:foreign-keys - :standard-deviation-aggregations - :unix-timestamp-special-type-fields} + {:features (set (cond-> [:foreign-keys + :standard-deviation-aggregations + :unix-timestamp-special-type-fields] + (:set-timezone-sql driver) (conj :set-timezone))) :qp-clause->handler qp/clause->handler :can-connect? (partial can-connect? (:connection-details->spec driver)) :process-query process-query :sync-in-context sync-in-context - :active-table-names active-table-names + :active-tables (partial active-tables (:excluded-schemas driver)) :active-column-names->type (partial active-column-names->type (:column->base-type driver)) :table-pks table-pks :field-values-lazy-seq field-values-lazy-seq diff --git a/src/metabase/driver/generic_sql/native.clj b/src/metabase/driver/generic_sql/native.clj index 2d97764d0348f5b35fc7db687b2d9ff45d556b62..4a91b9d2bc0a6dfa129c320321d08b96437c4c5a 100644 --- a/src/metabase/driver/generic_sql/native.clj +++ b/src/metabase/driver/generic_sql/native.clj @@ -25,26 +25,30 @@ {:keys [features set-timezone-sql]} (driver/engine->driver (:engine database))] (jdbc/with-db-transaction [t-conn db-conn] + (let [^java.sql.Connection jdbc-connection (:connection t-conn)] + ;; Disable auto-commit for this transaction, that way shady queries are unable to modify the database + (.setAutoCommit jdbc-connection false) + (try + ;; Set the timezone if applicable + (when-let [timezone (driver/report-timezone)] + (when (and (seq timezone) + (contains? features :set-timezone)) + (log/debug (u/format-color 'green "%s" set-timezone-sql)) + (try (jdbc/db-do-prepared t-conn set-timezone-sql [timezone]) + (catch Throwable e + (log/error (u/format-color 'red "Failed to set timezone: %s" (.getMessage e))))))) - ;; Set the timezone if applicable. We do this *before* making the transaction read-only because some DBs - ;; won't let you set the timezone on a read-only connection - (when-let [timezone (driver/report-timezone)] - (when (and (seq timezone) - (contains? features :set-timezone)) - (log/debug (u/format-color 'green "%s" set-timezone-sql)) - (try (jdbc/db-do-prepared t-conn set-timezone-sql [timezone]) - (catch Throwable e - (log/error (u/format-color 'red "Failed to set timezone: %s" (.getMessage e))))))) + ;; Now run the query itself + (log/debug (u/format-color 'green "%s" sql)) + (let [[columns & [first-row :as rows]] (jdbc/query t-conn sql, :as-arrays? true)] + {:rows rows + :columns columns + :cols (for [[column first-value] (zipmap columns first-row)] + {:name column + :base_type (value->base-type first-value)})}) - ;; Now make the transaction read-only and run the query itself - (.setReadOnly ^com.mchange.v2.c3p0.impl.NewProxyConnection (:connection t-conn) true) - (log/debug (u/format-color 'green "%s" sql)) - (let [[columns & [first-row :as rows]] (jdbc/query t-conn sql, :as-arrays? true)] - {:rows rows - :columns columns - :cols (for [[column first-value] (zipmap columns first-row)] - {:name column - :base_type (value->base-type first-value)})}))) + ;; Rollback any changes made during this transaction just to be extra-double-sure JDBC doesn't try to commit them automatically for us + (finally (.rollback jdbc-connection)))))) (catch java.sql.SQLException e (let [^String message (or (->> (.getMessage e) ; error message comes back like 'Column "ZID" not found; SQL statement: ... [error-code]' sometimes (re-find #"^(.*);") ; the user already knows the SQL, and error code is meaningless diff --git a/src/metabase/driver/generic_sql/query_processor.clj b/src/metabase/driver/generic_sql/query_processor.clj index 47a2a36c076614d50e6c42ae1a6e5c4995c5de37..ddd733154ffa9434caa1b204f258a7c313d79a67 100644 --- a/src/metabase/driver/generic_sql/query_processor.clj +++ b/src/metabase/driver/generic_sql/query_processor.clj @@ -1,17 +1,18 @@ (ns metabase.driver.generic-sql.query-processor "The Query Processor is responsible for translating the Metabase Query Language into korma SQL forms." (:require [clojure.core.match :refer [match]] - [clojure.tools.logging :as log] + [clojure.java.jdbc :as jdbc] [clojure.string :as s] + [clojure.tools.logging :as log] (korma [core :as k] [db :as kdb]) (korma.sql [fns :as kfns] [utils :as utils]) [metabase.config :as config] [metabase.driver :as driver] - [metabase.driver.query-processor :as qp] (metabase.driver.generic-sql [native :as native] [util :refer :all]) + [metabase.driver.query-processor :as qp] [metabase.util :as u]) (:import java.sql.Timestamp java.util.Date @@ -34,9 +35,9 @@ (formatted ([this] (formatted this false)) - ([{:keys [table-name special-type field-name], :as field} include-as?] + ([{:keys [schema-name table-name special-type field-name], :as field} include-as?] (let [->timestamp (:unix-timestamp->timestamp (:driver *query*)) - field (cond-> (keyword (str table-name \. field-name)) + field (cond-> (keyword (str (when schema-name (str schema-name \.)) table-name \. field-name)) (= special-type :timestamp_seconds) (->timestamp :seconds) (= special-type :timestamp_milliseconds) (->timestamp :milliseconds))] (if include-as? [field (keyword field-name)] @@ -181,14 +182,15 @@ (defn- apply-page [korma-query {{:keys [items page]} :page}] (-> korma-query - (k/limit items) - (k/offset (* items (dec page))))) + ((-> *query* :driver :qp-clause->handler :limit) {:limit items}) ; lookup apply-limit from the driver and use that rather than calling k/limit directly + (k/offset (* items (dec page))))) ; so drivers that override it (like SQL Server) don't need to override this function as well (defn- log-korma-form [korma-form] (when (config/config-bool :mb-db-logging) (when-not qp/*disable-qp-logging* (log/debug + (u/format-color 'green "\nKORMA FORM: 😋\n%s" (u/pprint-to-str (dissoc korma-form :db :ent :from :options :aliases :results :type :alias))) (u/format-color 'blue "\nSQL: 😈\n%s\n" (-> (k/as-sql korma-form) (s/replace #"\sFROM" "\nFROM") ; add newlines to the SQL to make it more readable (s/replace #"\sLEFT JOIN" "\nLEFT JOIN") @@ -235,17 +237,18 @@ (kdb/with-db (:db entity) (if (and (seq timezone) (contains? (:features driver) :set-timezone)) - (kdb/transaction - (try (k/exec-raw [(:set-timezone-sql driver) [timezone]]) - (catch Throwable e - (log/error (u/format-color 'red "Failed to set timezone: %s" (.getMessage e))))) - (k/exec korma-query)) + (try (kdb/transaction (k/exec-raw [(:set-timezone-sql driver) [timezone]]) + (k/exec korma-query)) + (catch Throwable e + (log/error (u/format-color 'red "Failed to set timezone:\n%s" + (with-out-str (jdbc/print-sql-exception-chain e)))) + (k/exec korma-query))) (k/exec korma-query)))) (catch java.sql.SQLException e - (let [^String message (or (->> (.getMessage e) ; error message comes back like "Error message ... [status-code]" sometimes + (let [^String message (or (->> (.getMessage e) ; error message comes back like "Error message ... [status-code]" sometimes (re-find #"(?s)(^.*)\s+\[[\d-]+\]$") ; status code isn't useful and makes unit tests hard to write so strip it off - second) ; (?s) = Pattern.DOTALL - tell regex `.` to match newline characters as well + second) ; (?s) = Pattern.DOTALL - tell regex `.` to match newline characters as well (.getMessage e))] (throw (Exception. message))))))) diff --git a/src/metabase/driver/generic_sql/util.clj b/src/metabase/driver/generic_sql/util.clj index 212330b9fad6d388d494c8ca41a753139c242251..7c19cd3112f10c813c845a9b698ad0eba1a390cf 100644 --- a/src/metabase/driver/generic_sql/util.clj +++ b/src/metabase/driver/generic_sql/util.clj @@ -76,9 +76,11 @@ ([{db-delay :db, :as table}] {:pre [(delay? db-delay)]} (korma-entity @db-delay table)) - ([db {table-name :name}] + ([db {schema :schema, table-name :name}] {:pre [(map? db)]} - {:table table-name + {:table (if (seq schema) + (str schema \. table-name) + table-name) :pk :id :db (db->korma-db db)})) diff --git a/src/metabase/driver/mongo.clj b/src/metabase/driver/mongo.clj index ee901d88adaa929184b2f468913755a629a1a95a..bb8a0f28f81f5f99e178298aa8d4a7c2e059a1ec 100644 --- a/src/metabase/driver/mongo.clj +++ b/src/metabase/driver/mongo.clj @@ -76,10 +76,10 @@ (with-mongo-connection [_ database] (do-sync-fn))) -(defn- active-table-names [database] +(defn- active-tables [database] (with-mongo-connection [^com.mongodb.DB conn database] - (-> (mdb/get-collection-names conn) - (set/difference #{"system.indexes"})))) + (set (for [collection (set/difference (mdb/get-collection-names conn) #{"system.indexes"})] + {:name collection})))) (defn- active-column-names->type [table] (with-mongo-connection [_ @(:db table)] @@ -147,7 +147,7 @@ :default false}] :features #{:nested-fields} :can-connect? can-connect? - :active-table-names active-table-names + :active-tables active-tables :field-values-lazy-seq field-values-lazy-seq :active-column-names->type active-column-names->type :table-pks (constantly #{"_id"}) diff --git a/src/metabase/driver/mongo/util.clj b/src/metabase/driver/mongo/util.clj index fceb2687fef38f1bfa192895b9d46d37cedb9dbb..94dcb818d5561bce80e5f9e91879d6276f966268 100644 --- a/src/metabase/driver/mongo/util.clj +++ b/src/metabase/driver/mongo/util.clj @@ -44,13 +44,12 @@ "Run F with a new connection (bound to `*mongo-connection*`) to DATABASE. Don't use this directly; use `with-mongo-connection`." [f database] - ;; The Mongo SSL detail is keyed by :use-ssl because the frontend has accidentally been saving all of the Mongo DBs with {:ssl true} - (let [{:keys [dbname host port user pass use-ssl] - :or {port 27017, pass "", use-ssl false}} (cond - (string? database) {:dbname database} - (:dbname (:details database)) (:details database) ; entire Database obj - (:dbname database) database ; connection details map only - :else (throw (Exception. (str "with-mongo-connection failed: bad connection details:" (:details database))))) + (let [{:keys [dbname host port user pass ssl] + :or {port 27017, pass "", ssl false}} (cond + (string? database) {:dbname database} + (:dbname (:details database)) (:details database) ; entire Database obj + (:dbname database) database ; connection details map only + :else (throw (Exception. (str "with-mongo-connection failed: bad connection details:" (:details database))))) user (when (seq user) ; ignore empty :user and :pass strings user) pass (when (seq pass) @@ -58,7 +57,7 @@ server-address (mg/server-address host port) credentials (when user (mcred/create user dbname pass)) - connect (partial mg/connect server-address (build-connection-options :ssl? use-ssl)) + connect (partial mg/connect server-address (build-connection-options :ssl? ssl)) conn (if credentials (connect credentials) (connect)) diff --git a/src/metabase/driver/mysql.clj b/src/metabase/driver/mysql.clj index 2665a72eca5942fe22a37244063f25b0f0444fd4..04fed050c2beaa386241541cbc0c26cc53258736 100644 --- a/src/metabase/driver/mysql.clj +++ b/src/metabase/driver/mysql.clj @@ -138,36 +138,36 @@ message)) (defdriver mysql - (-> {:driver-name "MySQL" - :details-fields [{:name "host" - :display-name "Host" - :default "localhost"} - {:name "port" - :display-name "Port" - :type :integer - :default 3306} - {:name "dbname" - :display-name "Database name" - :placeholder "birds_of_the_word" - :required true} - {:name "user" - :display-name "Database username" - :placeholder "What username do you use to login to the database?" - :required true} - {:name "password" - :display-name "Database password" - :type :password - :placeholder "*******"}] - :column->base-type column->base-type - :sql-string-length-fn :CHAR_LENGTH - :connection-details->spec connection-details->spec - :unix-timestamp->timestamp unix-timestamp->timestamp - :date date - :date-interval date-interval - ;; If this fails you need to load the timezone definitions from your system into MySQL; - ;; run the command `mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql` - ;; See https://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html for details - :set-timezone-sql "SET @@session.time_zone = ?;" - :humanize-connection-error-message humanize-connection-error-message} - sql-driver - (update :features conj :set-timezone))) + (sql-driver + {:driver-name "MySQL" + :details-fields [{:name "host" + :display-name "Host" + :default "localhost"} + {:name "port" + :display-name "Port" + :type :integer + :default 3306} + {:name "dbname" + :display-name "Database name" + :placeholder "birds_of_the_word" + :required true} + {:name "user" + :display-name "Database username" + :placeholder "What username do you use to login to the database?" + :required true} + {:name "password" + :display-name "Database password" + :type :password + :placeholder "*******"}] + :column->base-type column->base-type + :sql-string-length-fn :CHAR_LENGTH + :excluded-schemas #{"INFORMATION_SCHEMA"} + :connection-details->spec connection-details->spec + :unix-timestamp->timestamp unix-timestamp->timestamp + :date date + :date-interval date-interval + ;; If this fails you need to load the timezone definitions from your system into MySQL; + ;; run the command `mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql` + ;; See https://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html for details + :set-timezone-sql "SET @@session.time_zone = ?;" + :humanize-connection-error-message humanize-connection-error-message})) diff --git a/src/metabase/driver/postgres.clj b/src/metabase/driver/postgres.clj index 714b4f46611cf08dec1529d62df58eccbc7a703a..fb651823b3d7a3c0438feb83ca2a529cee28c6b5 100644 --- a/src/metabase/driver/postgres.clj +++ b/src/metabase/driver/postgres.clj @@ -166,38 +166,37 @@ message)) (defdriver postgres - (-> {:driver-name "PostgreSQL" - :details-fields [{:name "host" - :display-name "Host" - :default "localhost"} - {:name "port" - :display-name "Port" - :type :integer - :default 5432} - {:name "dbname" - :display-name "Database name" - :placeholder "birds_of_the_word" - :required true} - {:name "user" - :display-name "Database username" - :placeholder "What username do you use to login to the database?" - :required true} - {:name "password" - :display-name "Database password" - :type :password - :placeholder "*******"} - {:name "ssl" - :display-name "Use a secure connection (SSL)?" - :type :boolean - :default false}] - :sql-string-length-fn :CHAR_LENGTH - :column->base-type column->base-type - :connection-details->spec connection-details->spec - :unix-timestamp->timestamp unix-timestamp->timestamp - :date date - :date-interval date-interval - :set-timezone-sql "SET LOCAL timezone TO ?;" - :driver-specific-sync-field! driver-specific-sync-field! - :humanize-connection-error-message humanize-connection-error-message} - sql-driver - (update :features conj :set-timezone))) + (sql-driver + {:driver-name "PostgreSQL" + :details-fields [{:name "host" + :display-name "Host" + :default "localhost"} + {:name "port" + :display-name "Port" + :type :integer + :default 5432} + {:name "dbname" + :display-name "Database name" + :placeholder "birds_of_the_word" + :required true} + {:name "user" + :display-name "Database username" + :placeholder "What username do you use to login to the database?" + :required true} + {:name "password" + :display-name "Database password" + :type :password + :placeholder "*******"} + {:name "ssl" + :display-name "Use a secure connection (SSL)?" + :type :boolean + :default false}] + :sql-string-length-fn :CHAR_LENGTH + :column->base-type column->base-type + :connection-details->spec connection-details->spec + :unix-timestamp->timestamp unix-timestamp->timestamp + :date date + :date-interval date-interval + :set-timezone-sql "UPDATE pg_settings SET setting = ? WHERE name ILIKE 'timezone';" + :driver-specific-sync-field! driver-specific-sync-field! + :humanize-connection-error-message humanize-connection-error-message})) diff --git a/src/metabase/driver/query_processor/annotate.clj b/src/metabase/driver/query_processor/annotate.clj index b054789617c1c924caa9a61b80a0914335ab1442..46e2657ee45e41164ae0fabd1d8fd90c62dace39 100644 --- a/src/metabase/driver/query_processor/annotate.clj +++ b/src/metabase/driver/query_processor/annotate.clj @@ -232,6 +232,7 @@ :field-id :id :field-name :name :field-display-name :display_name + :schema-name :schema_name :special-type :special_type :preview-display :preview_display :table-id :table_id}) diff --git a/src/metabase/driver/query_processor/interface.clj b/src/metabase/driver/query_processor/interface.clj index 05906e8da9a056d3f2a982ea0f61879cee4512da..75ecb94fc734ee133de247b9ad567ee4d2328928 100644 --- a/src/metabase/driver/query_processor/interface.clj +++ b/src/metabase/driver/query_processor/interface.clj @@ -40,6 +40,7 @@ ^Keyword base-type ^Keyword special-type ^Integer table-id + ^String schema-name ^String table-name ^Integer position ^String description diff --git a/src/metabase/driver/query_processor/resolve.clj b/src/metabase/driver/query_processor/resolve.clj index 31e249e993dfddb05ce2bcd33e2657ea81c7977a..5333c15de20f785f576a33b1c74fda143c9a8976 100644 --- a/src/metabase/driver/query_processor/resolve.clj +++ b/src/metabase/driver/query_processor/resolve.clj @@ -79,8 +79,11 @@ :else this)) (defmethod resolve-table Field [{:keys [table-id], :as this} table-id->table] - (assoc this :table-name (:name (or (table-id->table table-id) - (throw (Exception. (format "Query expansion failed: could not find table %d." table-id))))))) + (let [table (or (table-id->table table-id) + (throw (Exception. (format "Query expansion failed: could not find table %d." table-id))))] + (assoc this + :table-name (:name table) + :schema-name (:schema table)))) ;; ## FieldPlaceholder @@ -194,7 +197,7 @@ [{{source-table-id :source-table} :query, database-id :database, :keys [table-ids fk-field-ids], :as expanded-query-dict}] {:pre [(integer? source-table-id)]} (let [table-ids (conj table-ids source-table-id) - table-id->table (sel :many :id->fields [Table :name :id] :id [in table-ids]) + table-id->table (sel :many :id->fields [Table :schema :name :id] :id [in table-ids]) join-tables (vals (dissoc table-id->table source-table-id))] (-<> expanded-query-dict diff --git a/src/metabase/driver/sync.clj b/src/metabase/driver/sync.clj index e2431503f47341ac9bf0fcffef261709cc127906..0f27e1e1d7dfe2226034cf82be95c246103197f1 100644 --- a/src/metabase/driver/sync.clj +++ b/src/metabase/driver/sync.clj @@ -38,46 +38,66 @@ ;; ## sync-database! and sync-table! -(defn- -sync-database! [{:keys [active-table-names], :as driver} database] - (let [start-time (System/currentTimeMillis) +(defn- validate-active-tables [results] + (when-not (and (set? results) + (every? map? results) + (every? :name results)) + (throw (Exception. "Invalid results returned by active-tables. Results should be a set of maps like {:name \"table_name\", :schema \"schema_name_or_nil\"}.")))) + +(defn- mark-inactive-tables! + "Mark any `Tables` that are no longer active as such. These are ones that exist in the DB but didn't come back from `active-tables`." + [database active-tables existing-table->id] + (doseq [[[{table :name, schema :schema, :as table}] table-id] existing-table->id] + (when-not (contains? active-tables table) + (upd Table table-id :active false) + (log/info (u/format-color 'cyan "Marked table %s.%s%s as inactive." (:name database) (if schema (str schema \.) "") table)) + + ;; We need to mark driver Table's Fields as inactive so we don't expose them in UI such as FK selector (etc.) + (k/update Field + (k/where {:table_id table-id}) + (k/set-fields {:active false}))))) + +(defn- create-new-tables! + "Create new `Tables` as needed. These are ones that came back from `active-tables` but don't already exist in the DB." + [database active-tables existing-table->id] + (let [existing-tables (set (keys existing-table->id)) + new-tables (set/difference active-tables existing-tables)] + (when (seq new-tables) + (log/debug (u/format-color 'blue "Found new tables: %s" (for [{table :name, schema :schema} new-tables] + (if schema + (str schema \. table) + table)))) + (doseq [{table :name, schema :schema} new-tables] + ;; If it's a _metabase_metadata table then we'll handle later once everything else has been synced + (when-not (= (s/lower-case table) "_metabase_metadata") + (ins Table :db_id (:id database), :active true, :schema schema, :name table)))))) + +(defn- fetch-and-sync-database-active-tables! [driver database] + (sync-database-active-tables! driver (for [table (sel :many Table, :db_id (:id database) :active true)] + ;; replace default delays with ones that reuse database (and don't require a DB call) + (assoc table :db (delay database))))) + +(defn- -sync-database! [{:keys [active-tables], :as driver} database] + (let [active-tables (active-tables database) + existing-table->id (into {} (for [{:keys [name schema id]} (sel :many :fields [Table :name :schema :id], :db_id (:id database), :active true)] + {{:name name, :schema schema} id}))] + (validate-active-tables active-tables) + + (mark-inactive-tables! database active-tables existing-table->id) + (create-new-tables! database active-tables existing-table->id)) + + (fetch-and-sync-database-active-tables! driver database) + + ;; Ok, now if we had a _metabase_metadata table from earlier we can go ahead and sync from it + (sync-metabase-metadata-table! driver database)) + +(defn- -sync-database-with-tracking! [driver database] + (let [start-time (System/currentTimeMillis) tracking-hash (str (java.util.UUID/randomUUID))] (log/info (u/format-color 'magenta "Syncing %s database '%s'..." (name (:engine database)) (:name database))) (events/publish-event :database-sync-begin {:database_id (:id database) :custom_id tracking-hash}) - (let [active-table-names (active-table-names database) - table-name->id (sel :many :field->id [Table :name] :db_id (:id database) :active true)] - (assert (set? active-table-names) "active-table-names should return a set.") - (assert (every? string? active-table-names) "active-table-names should return the names of Tables as *strings*.") - - ;; First, let's mark any Tables that are no longer active as such. - ;; These are ones that exist in table-name->id but not in active-table-names. - (doseq [[table-name table-id] table-name->id] - (when-not (contains? active-table-names table-name) - (upd Table table-id :active false) - (log/info (u/format-color 'cyan "Marked table %s.%s as inactive." (:name database) table-name)) - - ;; We need to mark driver Table's Fields as inactive so we don't expose them in UI such as FK selector (etc.) - (k/update Field - (k/where {:table_id table-id}) - (k/set-fields {:active false})))) - - ;; Next, we'll create new Tables (ones that came back in active-table-names but *not* in table-name->id) - (let [existing-table-names (set (keys table-name->id)) - new-table-names (set/difference active-table-names existing-table-names)] - (when (seq new-table-names) - (log/debug (u/format-color 'blue "Found new tables: %s" new-table-names)) - (doseq [new-table-name new-table-names] - ;; If it's a _metabase_metadata table then we'll handle later once everything else has been synced - (when-not (= (s/lower-case new-table-name) "_metabase_metadata") - (ins Table :db_id (:id database), :active true, :name new-table-name)))))) - - ;; Now sync the active tables - (->> (sel :many Table :db_id (:id database) :active true) - (map #(assoc % :db (delay database))) ; replace default delays with ones that reuse database (and don't require a DB call) - (sync-database-active-tables! driver)) - - ;; Ok, now if we had a _metabase_metadata table from earlier we can go ahead and sync from it - (sync-metabase-metadata-table! driver database) + (-sync-database! driver database) (events/publish-event :database-sync-end {:database_id (:id database) :custom_id tracking-hash :running_time (- (System/currentTimeMillis) start-time)}) (log/info (u/format-color 'magenta "Finished syncing %s database %s. (%d ms)" (name (:engine database)) (:name database) @@ -88,7 +108,7 @@ [{:keys [sync-in-context], :as driver} database] (binding [qp/*disable-qp-logging* true *sel-disable-logging* true] - (let [f (partial -sync-database! driver database)] + (let [f (partial -sync-database-with-tracking! driver database)] (if sync-in-context (sync-in-context database f) (f))))) @@ -108,9 +128,9 @@ `keypath` is of the form `table-name.key` or `table-name.field-name.key`, where `key` is the name of some property of `Table` or `Field`. This functionality is currently only used by the Sample Dataset. In order to use this functionality, drivers must implement optional fn `:table-rows-seq`." - [{:keys [table-rows-seq active-table-names]} database] + [{:keys [table-rows-seq active-tables]} database] (when table-rows-seq - (doseq [table-name (active-table-names database)] + (doseq [{table-name :name} (active-tables database)] (when (= (s/lower-case table-name) "_metabase_metadata") (doseq [{:keys [keypath value]} (table-rows-seq database table-name)] (let [[_ table-name field-name k] (re-matches #"^([^.]+)\.(?:([^.]+)\.)?([^.]+)$" keypath)] diff --git a/src/metabase/util.clj b/src/metabase/util.clj index 9f890c7d6f51e1840ca35aa53e943982a9315d88..9d1f4460c7834d21f63350f570efa68622958f2e 100644 --- a/src/metabase/util.clj +++ b/src/metabase/util.clj @@ -228,9 +228,9 @@ `(try (~f ~@params) (catch Throwable e# - (log/error (color/red ~(format "Caught exception in %s:" f) + (log/error (color/red ~(format "Caught exception in %s: " f) (or (.getMessage e#) e#) - (with-out-str (.printStackTrace e#))))))) + #_(with-out-str (.printStackTrace e#))))))) (defn indecies-satisfying "Return a set of indencies in COLL that satisfy PRED. diff --git a/test/metabase/api/database_test.clj b/test/metabase/api/database_test.clj index 77c4c65ec0c73909bc7bcc3f36ebc39d08d8939b..5a92f85445cfa988660029ab8385f28b84a73e02 100644 --- a/test/metabase/api/database_test.clj +++ b/test/metabase/api/database_test.clj @@ -167,66 +167,67 @@ ;; ## GET /api/meta/table/:id/query_metadata ;; TODO - add in example with Field :values (expect - (match-$ (db) - {:created_at $ - :engine "h2" - :id $ - :updated_at $ - :name "Test Database" - :is_sample false - :organization_id nil - :description nil - :tables [(match-$ (Table (id :categories)) - {:description nil - :entity_type nil - :visibility_type nil - :name "CATEGORIES" - :display_name "Categories" - :fields [(match-$ (Field (id :categories :id)) - {:description nil - :table_id (id :categories) - :special_type "id" - :name "ID" - :display_name "Id" - :updated_at $ - :active true - :id $ - :field_type "info" - :position 0 - :target nil - :preview_display true - :created_at $ - :base_type "BigIntegerField" - :parent_id nil - :parent nil - :values []}) - (match-$ (Field (id :categories :name)) - {:description nil - :table_id (id :categories) - :special_type "name" - :name "NAME" - :display_name "Name" - :updated_at $ - :active true - :id $ - :field_type "info" - :position 0 - :target nil - :preview_display true - :created_at $ - :base_type "TextField" - :parent_id nil - :parent nil - :values []})] - :rows 75 - :updated_at $ - :entity_name nil - :active true - :id (id :categories) - :db_id (db-id) - :created_at $})]}) - (let [resp ((user->client :rasta) :get 200 (format "database/%d/metadata" (db-id)))] - (assoc resp :tables (filter #(= "CATEGORIES" (:name %)) (:tables resp))))) + (match-$ (db) + {:created_at $ + :engine "h2" + :id $ + :updated_at $ + :name "Test Database" + :is_sample false + :organization_id nil + :description nil + :tables [(match-$ (Table (id :categories)) + {:description nil + :entity_type nil + :visibility_type nil + :schema "PUBLIC" + :name "CATEGORIES" + :display_name "Categories" + :fields [(match-$ (Field (id :categories :id)) + {:description nil + :table_id (id :categories) + :special_type "id" + :name "ID" + :display_name "Id" + :updated_at $ + :active true + :id $ + :field_type "info" + :position 0 + :target nil + :preview_display true + :created_at $ + :base_type "BigIntegerField" + :parent_id nil + :parent nil + :values []}) + (match-$ (Field (id :categories :name)) + {:description nil + :table_id (id :categories) + :special_type "name" + :name "NAME" + :display_name "Name" + :updated_at $ + :active true + :id $ + :field_type "info" + :position 0 + :target nil + :preview_display true + :created_at $ + :base_type "TextField" + :parent_id nil + :parent nil + :values []})] + :rows 75 + :updated_at $ + :entity_name nil + :active true + :id (id :categories) + :db_id (db-id) + :created_at $})]}) + (let [resp ((user->client :rasta) :get 200 (format "database/%d/metadata" (db-id)))] + (assoc resp :tables (filter #(= "CATEGORIES" (:name %)) (:tables resp))))) ;; # DB TABLES ENDPOINTS @@ -236,11 +237,11 @@ (expect (let [db-id (db-id)] [(match-$ (Table (id :categories)) - {:description nil, :entity_type nil, :visibility_type nil, :name "CATEGORIES", :rows 75, :updated_at $, :entity_name nil, :active true, :id $, :db_id db-id, :created_at $, :display_name "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)) - {:description nil, :entity_type nil, :visibility_type nil, :name "CHECKINS", :rows 1000, :updated_at $, :entity_name nil, :active true, :id $, :db_id db-id, :created_at $, :display_name "Checkins"}) + {:description nil, :entity_type nil, :visibility_type nil, :schema "PUBLIC", :name "CHECKINS", :rows 1000, :updated_at $, :entity_name nil, :active true, :id $, :db_id db-id, :created_at $, :display_name "Checkins"}) (match-$ (Table (id :users)) - {:description nil, :entity_type nil, :visibility_type nil, :name "USERS", :rows 15, :updated_at $, :entity_name nil, :active true, :id $, :db_id db-id, :created_at $, :display_name "Users"}) + {: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, :name "VENUES", :rows 100, :updated_at $, :entity_name nil, :active true, :id $, :db_id db-id, :created_at $, :display_name "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)))) diff --git a/test/metabase/api/field_test.clj b/test/metabase/api/field_test.clj index 14b9bba345d4abe4aa571d791914f5dc04bdd26d..fffc0ef87e18236229aa70e042284904632473ed 100644 --- a/test/metabase/api/field_test.clj +++ b/test/metabase/api/field_test.clj @@ -13,44 +13,45 @@ ;; ## GET /api/field/:id (expect (match-$ (Field (id :users :name)) - {:description nil - :table_id (id :users) - :table (match-$ (Table (id :users)) - {:description nil - :entity_type nil - :visibility_type nil - :db (match-$ (db) - {:created_at $ - :engine "h2" - :id $ - :updated_at $ - :name "Test Database" - :is_sample false - :organization_id nil - :description nil}) - :name "USERS" - :display_name "Users" - :rows 15 - :updated_at $ - :entity_name nil - :active true - :id (id :users) - :db_id (db-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" - :display_name "Name" - :updated_at $ - :active true - :id (id :users :name) - :field_type "info" - :position 0 + {:description nil + :table_id (id :users) + :table (match-$ (Table (id :users)) + {:description nil + :entity_type nil + :visibility_type nil + :db (match-$ (db) + {:created_at $ + :engine "h2" + :id $ + :updated_at $ + :name "Test Database" + :is_sample false + :organization_id nil + :description nil}) + :schema "PUBLIC" + :name "USERS" + :display_name "Users" + :rows 15 + :updated_at $ + :entity_name nil + :active true + :id (id :users) + :db_id (db-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" + :display_name "Name" + :updated_at $ + :active true + :id (id :users :name) + :field_type "info" + :position 0 :preview_display true :created_at $ :base_type "TextField" :parent_id nil :parent nil}) - ((user->client :rasta) :get 200 (format "field/%d" (id :users :name)))) + ((user->client :rasta) :get 200 (format "field/%d" (id :users :name)))) ;; ## GET /api/field/:id/summary diff --git a/test/metabase/api/table_test.clj b/test/metabase/api/table_test.clj index 39bb606a814450cb2013c32b8b798924ad645862..784a7a8b4d45c702f17f8e08e08ce8022aea1dcb 100644 --- a/test/metabase/api/table_test.clj +++ b/test/metabase/api/table_test.clj @@ -52,35 +52,36 @@ :rows 100 :id (id :venues)}])))) (->> ((user->client :rasta) :get 200 "table") - (map #(dissoc % :db :created_at :updated_at :entity_name :description :entity_type :visibility_type)) + (map #(dissoc % :db :created_at :updated_at :schema :entity_name :description :entity_type :visibility_type)) set)) ;; ## GET /api/table/:id (expect (match-$ (Table (id :venues)) - {:description nil - :entity_type nil + {:description nil + :entity_type nil :visibility_type nil - :db (match-$ (db) - {:created_at $ - :engine "h2" - :id $ - :updated_at $ - :name "Test Database" - :is_sample false - :organization_id nil - :description nil}) - :name "VENUES" - :display_name "Venues" - :rows 100 - :updated_at $ - :entity_name nil - :active true - :pk_field (deref $pk_field) - :id (id :venues) - :db_id (db-id) - :created_at $}) - ((user->client :rasta) :get 200 (format "table/%d" (id :venues)))) + :db (match-$ (db) + {:created_at $ + :engine "h2" + :id $ + :updated_at $ + :name "Test Database" + :is_sample false + :organization_id nil + :description nil}) + :schema "PUBLIC" + :name "VENUES" + :display_name "Venues" + :rows 100 + :updated_at $ + :entity_name nil + :active true + :pk_field (deref $pk_field) + :id (id :venues) + :db_id (db-id) + :created_at $}) + ((user->client :rasta) :get 200 (format "table/%d" (id :venues)))) ;; ## GET /api/table/:id/fields (expect [(match-$ (Field (id :categories :id)) @@ -120,63 +121,64 @@ ;; ## GET /api/table/:id/query_metadata (expect (match-$ (Table (id :categories)) - {:description nil - :entity_type nil + {:description nil + :entity_type nil :visibility_type nil - :db (match-$ (db) - {:created_at $ - :engine "h2" - :id $ - :updated_at $ - :name "Test Database" - :is_sample false - :organization_id nil - :description nil}) - :name "CATEGORIES" - :display_name "Categories" - :fields [(match-$ (Field (id :categories :id)) - {:description nil - :table_id (id :categories) - :special_type "id" - :name "ID" - :display_name "Id" - :updated_at $ - :active true - :id $ - :field_type "info" - :position 0 - :target nil - :preview_display true - :created_at $ - :base_type "BigIntegerField" - :parent_id nil - :parent nil}) - (match-$ (Field (id :categories :name)) - {:description nil - :table_id (id :categories) - :special_type "name" - :name "NAME" - :display_name "Name" - :updated_at $ - :active true - :id $ - :field_type "info" - :position 0 - :target nil - :preview_display true - :created_at $ - :base_type "TextField" - :parent_id nil - :parent nil})] - :field_values {} - :rows 75 - :updated_at $ - :entity_name nil - :active true - :id (id :categories) - :db_id (db-id) - :created_at $}) - ((user->client :rasta) :get 200 (format "table/%d/query_metadata" (id :categories)))) + :db (match-$ (db) + {:created_at $ + :engine "h2" + :id $ + :updated_at $ + :name "Test Database" + :is_sample false + :organization_id nil + :description nil}) + :schema "PUBLIC" + :name "CATEGORIES" + :display_name "Categories" + :fields [(match-$ (Field (id :categories :id)) + {:description nil + :table_id (id :categories) + :special_type "id" + :name "ID" + :display_name "Id" + :updated_at $ + :active true + :id $ + :field_type "info" + :position 0 + :target nil + :preview_display true + :created_at $ + :base_type "BigIntegerField" + :parent_id nil + :parent nil}) + (match-$ (Field (id :categories :name)) + {:description nil + :table_id (id :categories) + :special_type "name" + :name "NAME" + :display_name "Name" + :updated_at $ + :active true + :id $ + :field_type "info" + :position 0 + :target nil + :preview_display true + :created_at $ + :base_type "TextField" + :parent_id nil + :parent nil})] + :field_values {} + :rows 75 + :updated_at $ + :entity_name nil + :active true + :id (id :categories) + :db_id (db-id) + :created_at $}) + ((user->client :rasta) :get 200 (format "table/%d/query_metadata" (id :categories)))) (def ^:private user-last-login-date-strs @@ -201,212 +203,214 @@ ;;; Make sure that getting the User table *does* include info about the password field, but not actual values themselves (expect (match-$ (sel :one Table :id (id :users)) - {:description nil - :entity_type nil + {:description nil + :entity_type nil :visibility_type nil - :db (match-$ (db) - {:created_at $ - :engine "h2" - :id $ - :updated_at $ - :name "Test Database" - :is_sample false - :organization_id nil - :description nil}) - :name "USERS" - :display_name "Users" - :fields [(match-$ (sel :one Field :id (id :users :id)) - {:description nil - :table_id (id :users) - :special_type "id" - :name "ID" - :display_name "Id" - :updated_at $ - :active true - :id $ - :field_type "info" - :position 0 - :target nil - :preview_display true - :created_at $ - :base_type "BigIntegerField" - :parent_id nil - :parent nil}) - (match-$ (sel :one Field :id (id :users :last_login)) - {:description nil - :table_id (id :users) - :special_type "category" - :name "LAST_LOGIN" - :display_name "Last Login" - :updated_at $ - :active true - :id $ - :field_type "info" - :position 0 - :target nil - :preview_display true - :created_at $ - :base_type "DateTimeField" - :parent_id nil - :parent nil}) - (match-$ (sel :one Field :id (id :users :name)) - {:description nil - :table_id (id :users) - :special_type "category" - :name "NAME" - :display_name "Name" - :updated_at $ - :active true - :id $ - :field_type "info" - :position 0 - :target nil - :preview_display true - :created_at $ - :base_type "TextField" - :parent_id nil - :parent nil}) - (match-$ (sel :one Field :table_id (id :users) :name "PASSWORD") - {:description nil - :table_id (id :users) - :special_type "category" - :name "PASSWORD" - :display_name "Password" - :updated_at $ - :active true - :id $ - :field_type "sensitive" - :position 0 - :target nil - :preview_display true - :created_at $ - :base_type "TextField" - :parent_id nil - :parent nil})] - :rows 15 - :updated_at $ - :entity_name nil - :active true - :id (id :users) - :db_id (db-id) - :field_values {(keyword (str (id :users :last_login))) - user-last-login-date-strs + :db (match-$ (db) + {:created_at $ + :engine "h2" + :id $ + :updated_at $ + :name "Test Database" + :is_sample false + :organization_id nil + :description nil}) + :schema "PUBLIC" + :name "USERS" + :display_name "Users" + :fields [(match-$ (sel :one Field :id (id :users :id)) + {:description nil + :table_id (id :users) + :special_type "id" + :name "ID" + :display_name "Id" + :updated_at $ + :active true + :id $ + :field_type "info" + :position 0 + :target nil + :preview_display true + :created_at $ + :base_type "BigIntegerField" + :parent_id nil + :parent nil}) + (match-$ (sel :one Field :id (id :users :last_login)) + {:description nil + :table_id (id :users) + :special_type "category" + :name "LAST_LOGIN" + :display_name "Last Login" + :updated_at $ + :active true + :id $ + :field_type "info" + :position 0 + :target nil + :preview_display true + :created_at $ + :base_type "DateTimeField" + :parent_id nil + :parent nil}) + (match-$ (sel :one Field :id (id :users :name)) + {:description nil + :table_id (id :users) + :special_type "category" + :name "NAME" + :display_name "Name" + :updated_at $ + :active true + :id $ + :field_type "info" + :position 0 + :target nil + :preview_display true + :created_at $ + :base_type "TextField" + :parent_id nil + :parent nil}) + (match-$ (sel :one Field :table_id (id :users) :name "PASSWORD") + {:description nil + :table_id (id :users) + :special_type "category" + :name "PASSWORD" + :display_name "Password" + :updated_at $ + :active true + :id $ + :field_type "sensitive" + :position 0 + :target nil + :preview_display true + :created_at $ + :base_type "TextField" + :parent_id nil + :parent nil})] + :rows 15 + :updated_at $ + :entity_name nil + :active true + :id (id :users) + :db_id (db-id) + :field_values {(keyword (str (id :users :last_login))) + user-last-login-date-strs - (keyword (str (id :users :name))) - ["Broen Olujimi" - "Conchúr Tihomir" - "Dwight Gresham" - "Felipinho Asklepios" - "Frans Hevel" - "Kaneonuskatew Eiran" - "Kfir Caj" - "Nils Gotam" - "Plato Yeshua" - "Quentin Sören" - "Rüstem Hebel" - "Shad Ferdynand" - "Simcha Yan" - "Spiros Teofil" - "Szymon Theutrich"]} - :created_at $}) - ((user->client :rasta) :get 200 (format "table/%d/query_metadata?include_sensitive_fields=true" (id :users)))) + (keyword (str (id :users :name))) + ["Broen Olujimi" + "Conchúr Tihomir" + "Dwight Gresham" + "Felipinho Asklepios" + "Frans Hevel" + "Kaneonuskatew Eiran" + "Kfir Caj" + "Nils Gotam" + "Plato Yeshua" + "Quentin Sören" + "Rüstem Hebel" + "Shad Ferdynand" + "Simcha Yan" + "Spiros Teofil" + "Szymon Theutrich"]} + :created_at $}) + ((user->client :rasta) :get 200 (format "table/%d/query_metadata?include_sensitive_fields=true" (id :users)))) ;;; GET api/table/:id/query_metadata ;;; Make sure that getting the User table does *not* include password info (expect (match-$ (Table (id :users)) - {:description nil - :entity_type nil + {:description nil + :entity_type nil :visibility_type nil - :db (match-$ (db) - {:created_at $ - :engine "h2" - :id $ - :updated_at $ - :name "Test Database" - :is_sample false - :organization_id nil - :description nil}) - :name "USERS" - :display_name "Users" - :fields [(match-$ (Field (id :users :id)) - {:description nil - :table_id (id :users) - :special_type "id" - :name "ID" - :display_name "Id" - :updated_at $ - :active true - :id $ - :field_type "info" - :position 0 - :target nil - :preview_display true - :created_at $ - :base_type "BigIntegerField" - :parent_id nil - :parent nil}) - (match-$ (Field (id :users :last_login)) - {:description nil - :table_id (id :users) - :special_type "category" - :name "LAST_LOGIN" - :display_name "Last Login" - :updated_at $ - :active true - :id $ - :field_type "info" - :position 0 - :target nil - :preview_display true - :created_at $ - :base_type "DateTimeField" - :parent_id nil - :parent nil}) - (match-$ (Field (id :users :name)) - {:description nil - :table_id (id :users) - :special_type "category" - :name "NAME" - :display_name "Name" - :updated_at $ - :active true - :id $ - :field_type "info" - :position 0 - :target nil - :preview_display true - :created_at $ - :base_type "TextField" - :parent_id nil - :parent nil})] - :rows 15 - :updated_at $ - :entity_name nil - :active true - :id (id :users) - :db_id (db-id) - :field_values {(keyword (str (id :users :last_login))) - user-last-login-date-strs + :db (match-$ (db) + {:created_at $ + :engine "h2" + :id $ + :updated_at $ + :name "Test Database" + :is_sample false + :organization_id nil + :description nil}) + :schema "PUBLIC" + :name "USERS" + :display_name "Users" + :fields [(match-$ (Field (id :users :id)) + {:description nil + :table_id (id :users) + :special_type "id" + :name "ID" + :display_name "Id" + :updated_at $ + :active true + :id $ + :field_type "info" + :position 0 + :target nil + :preview_display true + :created_at $ + :base_type "BigIntegerField" + :parent_id nil + :parent nil}) + (match-$ (Field (id :users :last_login)) + {:description nil + :table_id (id :users) + :special_type "category" + :name "LAST_LOGIN" + :display_name "Last Login" + :updated_at $ + :active true + :id $ + :field_type "info" + :position 0 + :target nil + :preview_display true + :created_at $ + :base_type "DateTimeField" + :parent_id nil + :parent nil}) + (match-$ (Field (id :users :name)) + {:description nil + :table_id (id :users) + :special_type "category" + :name "NAME" + :display_name "Name" + :updated_at $ + :active true + :id $ + :field_type "info" + :position 0 + :target nil + :preview_display true + :created_at $ + :base_type "TextField" + :parent_id nil + :parent nil})] + :rows 15 + :updated_at $ + :entity_name nil + :active true + :id (id :users) + :db_id (db-id) + :field_values {(keyword (str (id :users :last_login))) + user-last-login-date-strs - (keyword (str (id :users :name))) - ["Broen Olujimi" - "Conchúr Tihomir" - "Dwight Gresham" - "Felipinho Asklepios" - "Frans Hevel" - "Kaneonuskatew Eiran" - "Kfir Caj" - "Nils Gotam" - "Plato Yeshua" - "Quentin Sören" - "Rüstem Hebel" - "Shad Ferdynand" - "Simcha Yan" - "Spiros Teofil" - "Szymon Theutrich"]} - :created_at $}) - ((user->client :rasta) :get 200 (format "table/%d/query_metadata" (id :users)))) + (keyword (str (id :users :name))) + ["Broen Olujimi" + "Conchúr Tihomir" + "Dwight Gresham" + "Felipinho Asklepios" + "Frans Hevel" + "Kaneonuskatew Eiran" + "Kfir Caj" + "Nils Gotam" + "Plato Yeshua" + "Quentin Sören" + "Rüstem Hebel" + "Shad Ferdynand" + "Simcha Yan" + "Spiros Teofil" + "Szymon Theutrich"]} + :created_at $}) + ((user->client :rasta) :get 200 (format "table/%d/query_metadata" (id :users)))) ;; ## PUT /api/table/:id @@ -415,34 +419,35 @@ ;; reset Table back to its original state (upd Table (id :users) :display_name "Users" :entity_type nil :visibility_type nil :description nil) table) - {:description "What a nice table!" - :entity_type "person" + {:description "What a nice table!" + :entity_type "person" :visibility_type "hidden" - :db (match-$ (db) - {:description nil - :organization_id $ - :name "Test Database" - :is_sample false - :updated_at $ - :details $ - :id $ - :engine "h2" - :created_at $}) - :name "USERS" - :rows 15 - :updated_at $ - :entity_name nil - :display_name "Userz" - :active true - :pk_field (deref $pk_field) - :id $ - :db_id (db-id) - :created_at $}) - (do ((user->client :crowberto) :put 200 (format "table/%d" (id :users)) {:display_name "Userz" - :entity_type "person" - :visibility_type "hidden" - :description "What a nice table!"}) - ((user->client :crowberto) :get 200 (format "table/%d" (id :users))))) + :db (match-$ (db) + {:description nil + :organization_id $ + :name "Test Database" + :is_sample false + :updated_at $ + :details $ + :id $ + :engine "h2" + :created_at $}) + :schema "PUBLIC" + :name "USERS" + :rows 15 + :updated_at $ + :entity_name nil + :display_name "Userz" + :active true + :pk_field (deref $pk_field) + :id $ + :db_id (db-id) + :created_at $}) + (do ((user->client :crowberto) :put 200 (format "table/%d" (id :users)) {:display_name "Userz" + :entity_type "person" + :visibility_type "hidden" + :description "What a nice table!"}) + ((user->client :crowberto) :get 200 (format "table/%d" (id :users))))) ;; ## GET /api/table/:id/fks @@ -473,27 +478,28 @@ :created_at $ :updated_at $ :table (match-$ (Table (id :checkins)) - {:description nil - :entity_type nil + {:description nil + :entity_type nil :visibility_type nil - :name "CHECKINS" - :display_name "Checkins" - :rows 1000 - :updated_at $ - :entity_name nil - :active true - :id $ - :db_id $ - :created_at $ - :db (match-$ (db) - {:description nil, - :organization_id nil, - :name "Test Database", - :is_sample false, - :updated_at $, - :id $, - :engine "h2", - :created_at $})})}) + :schema "PUBLIC" + :name "CHECKINS" + :display_name "Checkins" + :rows 1000 + :updated_at $ + :entity_name nil + :active true + :id $ + :db_id $ + :created_at $ + :db (match-$ (db) + {:description nil, + :organization_id nil, + :name "Test Database", + :is_sample false, + :updated_at $, + :id $, + :engine "h2", + :created_at $})})}) :destination (match-$ users-id-field {:id $ :table_id $ @@ -511,18 +517,19 @@ :created_at $ :updated_at $ :table (match-$ (Table (id :users)) - {:description nil - :entity_type nil + {:description nil + :entity_type nil :visibility_type nil - :name "USERS" - :display_name "Users" - :rows 15 - :updated_at $ - :entity_name nil - :active true - :id $ - :db_id $ - :created_at $})})})] + :schema "PUBLIC" + :name "USERS" + :display_name "Users" + :rows 15 + :updated_at $ + :entity_name nil + :active true + :id $ + :db_id $ + :created_at $})})})] ((user->client :rasta) :get 200 (format "table/%d/fks" (id :users)))) diff --git a/test/metabase/driver/generic_sql_test.clj b/test/metabase/driver/generic_sql_test.clj index 94a9a7d1bab9f5af578dabdc1296687251325cc9..7459071a2d342d0ed9c6e080fe8d3dc8a4cc2269 100644 --- a/test/metabase/driver/generic_sql_test.clj +++ b/test/metabase/driver/generic_sql_test.clj @@ -21,10 +21,13 @@ (def users-name-field (delay (Field (id :users :name)))) -;; ACTIVE-TABLE-NAMES +;; ACTIVE-TABLES (expect - #{"CATEGORIES" "VENUES" "CHECKINS" "USERS"} - ((:active-table-names h2) (db))) + #{{:name "CATEGORIES", :schema "PUBLIC"} + {:name "VENUES", :schema "PUBLIC"} + {:name "CHECKINS", :schema "PUBLIC"} + {:name "USERS", :schema "PUBLIC"}} + ((:active-tables h2) (db))) ;; ACTIVE-COLUMN-NAMES->TYPE (expect diff --git a/test/metabase/driver/mongo_test.clj b/test/metabase/driver/mongo_test.clj index 83f76b0441fd5aa9b83541f5e4430a2abfaf1e8c..966fcfd2d6403bd2d8ed021d867795385d69285e 100644 --- a/test/metabase/driver/mongo_test.clj +++ b/test/metabase/driver/mongo_test.clj @@ -109,10 +109,13 @@ (resolve-private-fns metabase.driver.mongo field->base-type table->column-names) -;; ### active-table-names +;; ### active-tables (expect-when-testing-mongo - #{"checkins" "categories" "users" "venues"} - ((:active-table-names mongo) @mongo-test-db)) + #{{:name "checkins"} + {:name "categories"} + {:name "users"} + {:name "venues"}} + ((:active-tables mongo) @mongo-test-db)) ;; ### table->column-names (expect-when-testing-mongo diff --git a/test/metabase/driver/postgres_test.clj b/test/metabase/driver/postgres_test.clj index 7b2e3ff94b587648c253c7efbfac4cdc70c3a4f7..a14ee96a8fd3a16e09744ccdd70d5657afe61ebd 100644 --- a/test/metabase/driver/postgres_test.clj +++ b/test/metabase/driver/postgres_test.clj @@ -46,8 +46,8 @@ ;; Check that we can load a Postgres Database with a :UUIDField (expect-with-dataset :postgres - {:cols [{:description nil, :base_type :IntegerField, :name "id", :display_name "Id", :preview_display true, :special_type :id, :target nil, :extra_info {}} - {:description nil, :base_type :UUIDField, :name "user_id", :display_name "User Id", :preview_display true, :special_type :category, :target nil, :extra_info {}}], + {:cols [{:description nil, :base_type :IntegerField, :schema_name "public", :name "id", :display_name "Id", :preview_display true, :special_type :id, :target nil, :extra_info {}} + {:description nil, :base_type :UUIDField, :schema_name "public", :name "user_id", :display_name "User Id", :preview_display true, :special_type :category, :target nil, :extra_info {}}], :columns ["id" "user_id"], :rows [[1 #uuid "4f01dcfd-13f7-430c-8e6f-e505c0851027"] [2 #uuid "4652b2e7-d940-4d55-a971-7e484566663e"] diff --git a/test/metabase/driver/query_processor_test.clj b/test/metabase/driver/query_processor_test.clj index d676c6f5f454f7c18717728b5c52c94293dada68..6a43f249943bcb4057c9842a49845019d2ccd0b6 100644 --- a/test/metabase/driver/query_processor_test.clj +++ b/test/metabase/driver/query_processor_test.clj @@ -46,50 +46,52 @@ ;; These are meant for inclusion in the expected output of the QP tests, to save us from writing the same results several times ;; #### categories + +(defn- col-defaults [] + {:extra_info {} + :target nil + :description nil + :preview_display true + :schema_name (default-schema)}) + (defn- categories-col "Return column information for the `categories` column named by keyword COL." [col] - (case col - :id {:extra_info {} :target nil :special_type :id, :base_type (id-field-type), :description nil, - :name (format-name "id") :display_name "Id" :preview_display true :table_id (id :categories), :id (id :categories :id)} - :name {:extra_info {} :target nil :special_type :name, :base_type :TextField, :description nil, - :name (format-name "name") :display_name "Name" :preview_display true :table_id (id :categories), :id (id :categories :name)})) + (merge + (col-defaults) + {:table_id (id :categories) + :id (id :categories col)} + (case col + :id {:special_type :id + :base_type (id-field-type) + :name (format-name "id") + :display_name "Id"} + :name {:special_type :name + :base_type :TextField + :name (format-name "name") + :display_name "Name"}))) ;; #### users (defn- users-col "Return column information for the `users` column named by keyword COL." [col] - (case col - :id {:extra_info {} - :target nil - :special_type :id - :base_type (id-field-type) - :description nil - :name (format-name "id") - :display_name "Id" - :preview_display true - :table_id (id :users) - :id (id :users :id)} - :name {:extra_info {} - :target nil - :special_type :category - :base_type :TextField - :description nil - :name (format-name "name") - :display_name "Name" - :preview_display true - :table_id (id :users) - :id (id :users :name)} - :last_login {:extra_info {} - :target nil - :special_type :category - :base_type (timestamp-field-type) - :description nil - :name (format-name "last_login") - :display_name "Last Login" - :preview_display true - :table_id (id :users) - :id (id :users :last_login)})) + (merge + (col-defaults) + {:table_id (id :users) + :id (id :users col)} + (case col + :id {:special_type :id + :base_type (id-field-type) + :name (format-name "id") + :display_name "Id"} + :name {:special_type :category + :base_type :TextField + :name (format-name "name") + :display_name "Name"} + :last_login {:special_type :category + :base_type (timestamp-field-type) + :name (format-name "last_login") + :display_name "Last Login"}))) ;; #### venues (defn- venues-columns @@ -100,71 +102,41 @@ (defn- venues-col "Return column information for the `venues` column named by keyword COL." [col] - (case col - :id {:extra_info {} - :target nil - :special_type :id - :base_type (id-field-type) - :description nil - :name (format-name "id") - :display_name "Id" - :preview_display true - :table_id (id :venues) - :id (id :venues :id)} - :category_id {:extra_info (if (fks-supported?) {:target_table_id (id :categories)} - {}) - :target (if (fks-supported?) (-> (categories-col :id) - (dissoc :target :extra_info)) - nil) - :special_type (if (fks-supported?) :fk - :category) - :base_type :IntegerField - :description nil - :name (format-name "category_id") - :display_name "Category Id" - :preview_display true - :table_id (id :venues) - :id (id :venues :category_id)} - :price {:extra_info {} - :target nil - :special_type :category - :base_type :IntegerField - :description nil - :name (format-name "price") - :display_name "Price" - :preview_display true - :table_id (id :venues) - :id (id :venues :price)} - :longitude {:extra_info {} - :target nil - :special_type :longitude, - :base_type :FloatField, - :description nil - :name (format-name "longitude") - :display_name "Longitude" - :preview_display true - :table_id (id :venues) - :id (id :venues :longitude)} - :latitude {:extra_info {} - :target nil - :special_type :latitude - :base_type :FloatField - :description nil - :name (format-name "latitude") - :display_name "Latitude" - :preview_display true - :table_id (id :venues) - :id (id :venues :latitude)} - :name {:extra_info {} - :target nil - :special_type :name - :base_type :TextField - :description nil - :name (format-name "name") - :display_name "Name" - :preview_display true - :table_id (id :venues) - :id (id :venues :name)})) + (merge + (col-defaults) + {:table_id (id :venues) + :id (id :venues col)} + (case col + :id {:special_type :id + :base_type (id-field-type) + :name (format-name "id") + :display_name "Id"} + :category_id {:extra_info (if (fks-supported?) {:target_table_id (id :categories)} + {}) + :target (if (fks-supported?) (-> (categories-col :id) + (dissoc :target :extra_info :schema_name)) + nil) + :special_type (if (fks-supported?) :fk + :category) + :base_type :IntegerField + :name (format-name "category_id") + :display_name "Category Id"} + :price {:special_type :category + :base_type :IntegerField + :name (format-name "price") + :display_name "Price"} + :longitude {:special_type :longitude, + :base_type :FloatField, + :name (format-name "longitude") + :display_name "Longitude"} + :latitude {:special_type :latitude + :base_type :FloatField + :name (format-name "latitude") + :display_name "Latitude"} + :name {:special_type :name + :base_type :TextField + :name (format-name "name") + :display_name "Name"}))) (defn- venues-cols "`cols` information for all the columns in `venues`." @@ -175,45 +147,35 @@ (defn- checkins-col "Return column information for the `checkins` column named by keyword COL." [col] - (case col - :id {:extra_info {} - :target nil - :special_type :id - :base_type (id-field-type) - :description nil - :name (format-name "id") - :display_name "Id" - :preview_display true - :table_id (id :checkins) - :id (id :checkins :id)} - :venue_id {:extra_info (if (fks-supported?) {:target_table_id (id :venues)} - {}) - :target (if (fks-supported?) (-> (venues-col :id) - (dissoc :target :extra_info)) - nil) - :special_type (when (fks-supported?) - :fk) - :base_type :IntegerField - :description nil - :name (format-name "venue_id") - :display_name "Venue Id" - :preview_display true - :table_id (id :checkins) - :id (id :checkins :venue_id)} - :user_id {:extra_info (if (fks-supported?) {:target_table_id (id :users)} - {}) - :target (if (fks-supported?) (-> (users-col :id) - (dissoc :target :extra_info)) - nil) - :special_type (if (fks-supported?) :fk - :category) - :base_type :IntegerField - :description nil - :name (format-name "user_id") - :display_name "User Id" - :preview_display true - :table_id (id :checkins) - :id (id :checkins :user_id)})) + (merge + (col-defaults) + {:table_id (id :checkins) + :id (id :checkins col)} + (case col + :id {:special_type :id + :base_type (id-field-type) + :name (format-name "id") + :display_name "Id"} + :venue_id {:extra_info (if (fks-supported?) {:target_table_id (id :venues)} + {}) + :target (if (fks-supported?) (-> (venues-col :id) + (dissoc :target :extra_info :schema_name)) + nil) + :special_type (when (fks-supported?) + :fk) + :base_type :IntegerField + :name (format-name "venue_id") + :display_name "Venue Id"} + :user_id {:extra_info (if (fks-supported?) {:target_table_id (id :users)} + {}) + :target (if (fks-supported?) (-> (users-col :id) + (dissoc :target :extra_info :schema_name)) + nil) + :special_type (if (fks-supported?) :fk + :category) + :base_type :IntegerField + :name (format-name "user_id") + :display_name "User Id"}))) ;;; #### aggregate columns @@ -236,8 +198,7 @@ :extra_info {} :target nil})) ([ag-col-kw {:keys [base_type special_type]}] - {:pre [base_type - special_type]} + {:pre [base_type special_type]} {:base_type base_type :special_type special_type :id nil diff --git a/test/metabase/test/data.clj b/test/metabase/test/data.clj index a6fc14085940468a702eaa539f38304aa9ef18de..b1fe394c75ba96627d3f366a3f9295c2daf9c039 100644 --- a/test/metabase/test/data.clj +++ b/test/metabase/test/data.clj @@ -56,6 +56,7 @@ (:id (db))) (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*)) diff --git a/test/metabase/test/data/datasets.clj b/test/metabase/test/data/datasets.clj index 0311c108dd0f3a8c5dc532b6827fac8985871a9c..da96c777c4de0fefe45bc5cea504aab659661027 100644 --- a/test/metabase/test/data/datasets.clj +++ b/test/metabase/test/data/datasets.clj @@ -34,6 +34,8 @@ "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] "Transform a lowercase string `Table` or `Field` name in a way appropriate for this dataset (e.g., `h2` would want to upcase these names; `mongo` would want to use `\"_id\"` in place of `\"id\"`.") @@ -75,6 +77,7 @@ table-or-field-name)) (fks-supported? [_] false) + (default-schema [_] nil) (id-field-type [_] :IntegerField) (sum-field-type [_] :IntegerField) (timestamp-field-type [_] :DateField)) @@ -133,6 +136,7 @@ (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)) :id-field-type (constantly :BigIntegerField) @@ -148,6 +152,7 @@ (merge GenericSQLIDatasetMixin {:dataset-loader (fn [_] (postgres/dataset-loader)) + :default-schema (constantly "public") :sum-field-type (constantly :IntegerField)})) @@ -160,6 +165,7 @@ (merge GenericSQLIDatasetMixin {:dataset-loader (fn [_] (mysql/dataset-loader)) + :default-schema (constantly nil) :sum-field-type (constantly :BigIntegerField)}))