(ns metabase.driver.postgres.ddl (:require [clojure.java.jdbc :as jdbc] [honey.sql :as sql] [java-time.api :as t] [metabase.driver.ddl.interface :as ddl.i] [metabase.driver.sql-jdbc.execute :as sql-jdbc.execute] [metabase.driver.sql.ddl :as sql.ddl] [metabase.public-settings :as public-settings] [metabase.query-processor.compile :as qp.compile] [metabase.util.log :as log])) | |
(set! *warn-on-reflection* true) | |
Must be called within a transaction.
Sets the current transaction This helps to address unexpectedly large/long running queries. | (defn- set-statement-timeout! [tx] (let [existing-timeout (->> #_{:clj-kondo/ignore [:discouraged-var]} (sql/format {:select [:setting] :from [:pg_settings] :where [:= :name "statement_timeout"]} {:quoted false}) (sql.ddl/jdbc-query tx) first :setting parse-long) ten-minutes (.toMillis (t/minutes 10)) new-timeout (if (zero? existing-timeout) ten-minutes (min ten-minutes existing-timeout))] ;; Can't use a prepared parameter with these statements (sql.ddl/execute! tx [(format "SET LOCAL statement_timeout TO '%s'" (str new-timeout))]))) |
(defmethod ddl.i/refresh! :postgres [driver database definition dataset-query] (let [{:keys [query params]} (qp.compile/compile dataset-query)] (sql-jdbc.execute/do-with-connection-with-options driver database {:write? true} (fn [^java.sql.Connection conn] (jdbc/with-db-transaction [tx {:connection conn}] (set-statement-timeout! tx) (sql.ddl/execute! tx [(sql.ddl/drop-table-sql database (:table-name definition))]) (sql.ddl/execute! tx (into [(sql.ddl/create-table-sql database definition query)] params))) {:state :success})))) | |
(defmethod ddl.i/unpersist! :postgres [driver database persisted-info] (sql-jdbc.execute/do-with-connection-with-options driver database {:write? true} (fn [conn] (try (sql.ddl/execute! conn [(sql.ddl/drop-table-sql database (:table_name persisted-info))]) (catch Exception e (log/warn e) (throw e)))))) | |
(defmethod ddl.i/check-can-persist :postgres [{driver :engine, :as database}] (let [schema-name (ddl.i/schema-name database (public-settings/site-uuid)) table-name (format "persistence_check_%s" (rand-int 10000)) steps [[:persist.check/create-schema (fn check-schema [conn] (let [existing-schemas (->> ["select schema_name from information_schema.schemata"] (sql.ddl/jdbc-query conn) (map :schema_name) (into #{}))] (or (contains? existing-schemas schema-name) (sql.ddl/execute! conn [(sql.ddl/create-schema-sql database)]))))] [: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")]))] [:persist.check/read-table (fn read-table [conn] (sql.ddl/jdbc-query conn [(format "select * from %s.%s" schema-name table-name)]))] [:persist.check/delete-table (fn delete-table [conn] (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})))]]] (sql-jdbc.execute/do-with-connection-with-options driver database {:write? true} (fn [^java.sql.Connection conn] (jdbc/with-db-transaction [tx {:connection conn}] (set-statement-timeout! tx) (loop [[[step stepfn] & remaining] steps] (let [result (try (stepfn tx) (log/infof "Step %s was successful for db %s" step (:name database)) ::valid (catch Exception e (log/warnf e "Error in `%s` while checking for model persistence permissions." step) step))] (cond (and (= result ::valid) remaining) (recur remaining) (= result ::valid) [true :persist.check/valid] :else [false step])))))))) | |