(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])))))))) | |