diff --git a/src/metabase/driver/ddl/interface.clj b/src/metabase/driver/ddl/interface.clj index c8b57994cb9b062f9b5398b3f88fe7eda992d6f3..3a00b1fb0a52cd57ae5eadd8ae76be48ba6c71e7 100644 --- a/src/metabase/driver/ddl/interface.clj +++ b/src/metabase/driver/ddl/interface.clj @@ -2,7 +2,15 @@ (:require [clojure.string :as str] [metabase.driver :as driver] - [metabase.util.i18n :refer [tru]])) + [metabase.public-settings :as public-settings] + [metabase.util.i18n :refer [tru]]) + (:import + (java.time Instant) + (java.time.format DateTimeFormatter))) + +(set! *warn-on-reflection* true) + + (defn schema-name "Returns a schema name for persisting models. Needs the database to use the db id and the site-uuid to ensure that @@ -41,6 +49,31 @@ (fn [database] (driver/dispatch-on-initialized-driver (:engine database))) :hierarchy #'driver/hierarchy) +(defn create-kv-table-honey-sql-form + "The honeysql form that creates the persisted schema `cache_info` table." + [schema-name] + {:create-table [(keyword schema-name "cache_info") :if-not-exists] + :with-columns [[:key :text] [:value :text]]}) + +(defn kv-table-values + "Version 1 of the values to go in the key/value table `cache_info` table." + [] + [{:key "settings-version" + :value "1"} + {:key "created-at" + ;; "2023-03-29T14:01:27.871697Z" + :value (.format DateTimeFormatter/ISO_INSTANT (Instant/now))} + {:key "instance-uuid" + :value (public-settings/site-uuid)} + {:key "instance-name" + :value (public-settings/site-name)}]) + +(defn populate-kv-table-honey-sql-form + "The honeysql form that populates the persisted schema `cache_info` table." + [schema-name] + {:insert-into [(keyword schema-name "cache_info")] + :values (kv-table-values)}) + (defn error->message "Human readable messages for different connection errors." [error schema] diff --git a/src/metabase/driver/mysql/ddl.clj b/src/metabase/driver/mysql/ddl.clj index 920664aa15bfc2abec829ca80d647c902057a0f0..6086ca0c76dbd00638280a6edfa0f31ab0c0ca3b 100644 --- a/src/metabase/driver/mysql/ddl.clj +++ b/src/metabase/driver/mysql/ddl.clj @@ -3,6 +3,7 @@ [clojure.core.async :as a] [clojure.java.jdbc :as jdbc] [clojure.string :as str] + [honey.sql :as sql] [java-time :as t] [metabase.driver.ddl.interface :as ddl.i] [metabase.driver.sql-jdbc.connection :as sql-jdbc.conn] @@ -117,7 +118,20 @@ (fn delete-table [conn] (sql.ddl/execute! conn [(sql.ddl/drop-table-sql database table-name)])) ;; This will never be called, if the last step fails it does not need to be undone - (constantly nil)]]] + (constantly nil)] + [:persist.check/create-kv-table + (fn create-kv-table [conn] + (sql.ddl/execute! conn [(format "drop table if exists %s.cache_info" + schema-name)]) + (sql.ddl/execute! conn (sql/format + (ddl.i/create-kv-table-honey-sql-form schema-name) + {:dialect :mysql})))] + [:persist.check/populate-kv-table + (fn create-kv-table [conn] + (sql.ddl/execute! conn (sql/format + (ddl.i/populate-kv-table-honey-sql-form + schema-name) + {:dialect :mysql})))]]] ;; Unlike postgres, mysql ddl clauses will not rollback in a transaction. ;; So we keep track of undo-steps to manually rollback previous, completed steps. (jdbc/with-db-connection [conn db-spec] diff --git a/src/metabase/driver/postgres/ddl.clj b/src/metabase/driver/postgres/ddl.clj index aed71ea2e1f13442947a647d29d8c78235027b15..596a55440b9755de49be5200abd11c2ca7840d58 100644 --- a/src/metabase/driver/postgres/ddl.clj +++ b/src/metabase/driver/postgres/ddl.clj @@ -69,17 +69,30 @@ [:persist.check/create-table (fn create-table [conn] (sql.ddl/execute! conn [(sql.ddl/create-table-sql database - {:table-name table-name - :field-definitions [{:field-name "field" - :base-type :type/Text}]} - "select 1")]))] + {:table-name table-name + :field-definitions [{:field-name "field" + :base-type :type/Text}]} + "select 1")]))] [:persist.check/read-table (fn read-table [conn] (sql.ddl/jdbc-query conn [(format "select * from %s.%s" - schema-name table-name)]))] + schema-name table-name)]))] [:persist.check/delete-table (fn delete-table [conn] - (sql.ddl/execute! conn [(sql.ddl/drop-table-sql database table-name)]))]]] + (sql.ddl/execute! conn [(sql.ddl/drop-table-sql database table-name)]))] + [:persist.check/create-kv-table + (fn create-kv-table [conn] + (sql.ddl/execute! conn [(format "drop table if exists %s.cache_info" + schema-name)]) + (sql.ddl/execute! conn (sql/format + (ddl.i/create-kv-table-honey-sql-form schema-name) + {:dialect :ansi})))] + [:persist.check/populate-kv-table + (fn create-kv-table [conn] + (sql.ddl/execute! conn (sql/format + (ddl.i/populate-kv-table-honey-sql-form + schema-name) + {:dialect :ansi})))]]] (jdbc/with-db-connection [conn (sql-jdbc.conn/db->pooled-connection-spec database)] (jdbc/with-db-transaction [tx conn] diff --git a/test/metabase/query_processor/persistence_test.clj b/test/metabase/query_processor/persistence_test.clj index dd9ead9c2344a4c65b28e138a531ef1a7cedbac7..0e1de01bd53fb27832d499578fc560177cebcbde 100644 --- a/test/metabase/query_processor/persistence_test.clj +++ b/test/metabase/query_processor/persistence_test.clj @@ -3,6 +3,7 @@ [clojure.core.async :as a] [clojure.string :as str] [clojure.test :refer :all] + [honey.sql :as sql] [metabase.driver :as driver] [metabase.driver.ddl.interface :as ddl.i] [metabase.models :refer [Card]] @@ -13,7 +14,12 @@ [metabase.query-processor.middleware.fix-bad-references :as fix-bad-refs] [metabase.test :as mt] - [toucan2.core :as t2])) + [toucan2.core :as t2]) + (:import + (java.time Instant) + (java.time.temporal ChronoUnit))) + +(set! *warn-on-reflection* true) (deftest can-persist-test (testing "Can each database that allows for persistence actually persist" @@ -22,7 +28,29 @@ (mt/dataset test-data (let [[success? error] (ddl.i/check-can-persist (mt/db))] (is success? (str "Not able to persist on " driver/*driver*)) - (is (= :persist.check/valid error)))))))) + (is (= :persist.check/valid error))) + (testing "Populates the `cache_info` table with v1 information" + (let [schema-name (ddl.i/schema-name (mt/db) (public-settings/site-uuid)) + query {:query + (first + (sql/format {:select [:key :value] + :from [(keyword schema-name "cache_info")]} + {:dialect (if (= (:engine (mt/db)) :mysql) + :mysql + :ansi)}))} + values (into {} (->> query mt/native-query qp/process-query mt/rows))] + (is (partial= {"settings-version" "1" + "instance-uuid" (public-settings/site-uuid)} + (into {} (->> query mt/native-query qp/process-query mt/rows)))) + (let [[low high] [(.minus (Instant/now) 1 ChronoUnit/MINUTES) + (.plus (Instant/now) 1 ChronoUnit/MINUTES)] + ^Instant created (some-> (get values "created-at") + (java.time.Instant/parse))] + (if created + (is (and (.isAfter created low) (.isBefore created high)) + "Date was not created recently") + (throw (ex-info "Did not find `created-at` in `cache_info` table" + {}))))))))))) (deftest persisted-models-max-rows-test (testing "Persisted models should have the full number of rows of the underlying query,