Metabase Drivers handle various things we need to do with connected data warehouse databases, including things like introspecting their schemas and processing and running MBQL queries. Drivers must implement some or all of the multimethods defined below, and register themselves with a call to [[metabase.driver/register!]]. SQL-based drivers can use the | (ns metabase.driver (:require [clojure.set :as set] [clojure.string :as str] [java-time.api :as t] [metabase.auth-provider :as auth-provider] [metabase.driver.impl :as driver.impl] [metabase.models.setting :as setting :refer [defsetting]] [metabase.plugins.classloader :as classloader] [metabase.query-processor.error-type :as qp.error-type] [metabase.util :as u] [metabase.util.i18n :refer [deferred-tru tru]] [metabase.util.log :as log] [metabase.util.malli :as mu] [potemkin :as p] [toucan2.core :as t2])) |
(set! *warn-on-reflection* true) | |
(declare notify-database-updated) | |
Send notification that all Databases should immediately release cached resources (i.e., connection pools). Currently only used below by [[report-timezone]] setter (i.e., only used when report timezone changes). Reusing
pooled connections with the old session timezone can have weird effects, especially if report timezone is changed to
| (defn- notify-all-databases-updated [] (doseq [{driver :engine, id :id, :as database} (t2/select 'Database)] (try (notify-database-updated driver database) (catch Throwable e (log/errorf e "Failed to notify %s Database %s updated" driver id))))) |
(defn- short-timezone-name [timezone-id] (let [^java.time.ZoneId zone (if (seq timezone-id) (t/zone-id timezone-id) (t/zone-id))] (.getDisplayName zone java.time.format.TextStyle/SHORT (java.util.Locale/getDefault)))) | |
(defn- long-timezone-name [timezone-id] (if (seq timezone-id) timezone-id (str (t/zone-id)))) | |
(defn- update-send-pulse-triggers-timezone! [] (classloader/require 'metabase.task.send-pulses) ((resolve 'metabase.task.send-pulses/update-send-pulse-triggers-timezone!))) | |
(defsetting report-timezone (deferred-tru "Connection timezone to use when executing queries. Defaults to system timezone.") :encryption :no :visibility :settings-manager :export? true :audit :getter :setter (fn [new-value] (setting/set-value-of-type! :string :report-timezone new-value) (notify-all-databases-updated) (update-send-pulse-triggers-timezone!))) | |
Current report timezone abbreviation | (defsetting report-timezone-short :visibility :public :export? true :setter :none :getter (fn [] (short-timezone-name (report-timezone))) :doc false) |
Current report timezone string | (defsetting report-timezone-long :visibility :public :export? true :setter :none :getter (fn [] (long-timezone-name (report-timezone))) :doc false) |
+----------------------------------------------------------------------------------------------------------------+ | Current Driver | +----------------------------------------------------------------------------------------------------------------+ | |
Current driver (a keyword such as | (def ^:dynamic *driver* nil) |
(declare the-driver) | |
Impl for | (defn do-with-driver [driver f] {:pre [(keyword? driver)]} (binding [*driver* (the-driver driver)] (f))) |
Bind current driver to (driver/with-driver :postgres ...) | (defmacro with-driver {:style/indent 1} [driver & body] `(do-with-driver ~driver (fn [] ~@body))) |
+----------------------------------------------------------------------------------------------------------------+ | Driver Registration / Hierarchy / Multimethod Dispatch | +----------------------------------------------------------------------------------------------------------------+ | |
(p/import-vars [driver.impl hierarchy register! initialized?]) | |
(add-watch #'hierarchy nil (fn [_key _ref _old-state _new-state] (when (not= hierarchy driver.impl/hierarchy) ;; this is a dev-facing error so no need to i18n it. (throw (Exception. (str "Don't alter #'metabase.driver/hierarchy directly, since it is imported from " "metabase.driver.impl. Alter #'metabase.driver.impl/hierarchy instead if you need to " "alter the var directly.")))))) | |
Is this driver available for use? (i.e. should we show it as an option when adding a new database?) This is Note that an available driver is not necessarily initialized yet; for example lazy-loaded drivers are registered
when Metabase starts up (meaning this will return | (defn available? [driver] ((every-pred driver.impl/registered? driver.impl/concrete?) driver)) |
Like [[clojure.core/the-ns]]. Converts argument to a keyword, then loads and registers the driver if not already done, throwing an Exception if it fails or is invalid. Returns keyword. Note that this does not neccessarily mean the driver is initialized (e.g., its full implementation and deps might not be loaded into memory) -- see also [[the-initialized-driver]]. This is useful in several cases: ;; Ensuring a driver is loaded & registered (isa? driver/hierarchy (the-driver :postgres) (the-driver :sql-jdbc) ;; Accepting either strings or keywords (e.g., in API endpoints) (the-driver "h2") ; -> :h2 ;; Ensuring a driver you are passed is valid (t2/insert! Database :engine (name (the-driver driver))) (the-driver :postgres) ; -> :postgres (the-driver :baby) ; -> Exception | (defn the-driver [driver] {:pre [((some-fn keyword? string?) driver)]} (let [driver (keyword driver)] (driver.impl/load-driver-namespace-if-needed! driver) driver)) |
Add a new parent to | (defn add-parent! [driver new-parent] (when-not *compile-files* (driver.impl/load-driver-namespace-if-needed! driver) (driver.impl/load-driver-namespace-if-needed! new-parent) (alter-var-root #'driver.impl/hierarchy derive driver new-parent))) |
Dispatch function to use for driver multimethods. Dispatches on first arg, a driver keyword; loads that driver's namespace if not already done. DOES NOT INITIALIZE THE DRIVER. Driver multimethods for abstract drivers like | (defn- dispatch-on-uninitialized-driver [driver & _] (the-driver driver)) |
(declare initialize!) | |
Like [[the-driver]], but also initializes the driver if not already initialized. | (defn the-initialized-driver [driver] (let [driver (keyword driver)] ;; Fastpath: an initialized driver `driver` is always already registered. Checking for `initialized?` is faster ;; than doing the `registered?` check inside `load-driver-namespace-if-needed!`. (when-not (driver.impl/initialized? driver) (driver.impl/load-driver-namespace-if-needed! driver) (driver.impl/initialize-if-needed! driver initialize!)) driver)) |
Like [[dispatch-on-uninitialized-driver]], but guarantees a driver is initialized before dispatch. Prefer [[the-driver]] for trivial methods that should do not require the driver to be initialized (e.g., ones that simply return information about the driver, but do not actually connect to any databases.) | (defn dispatch-on-initialized-driver [driver & _] (the-initialized-driver driver)) |
+----------------------------------------------------------------------------------------------------------------+ | Interface (Multimethod Defintions) | +----------------------------------------------------------------------------------------------------------------+ | |
Methods a driver can implement. Not all of these are required; some have default implementations immediately below them. SOME TIPS: To call the Clojure equivalent of the superclass implementation of a method, use (driver/register-driver! :my-driver, :parent :sql-jdbc) (defmethod driver/describe-table :my-driver [driver database table] (-> ((get-method driver/describe-table :sql-jdbc) driver databse table) (update :tables add-materialized-views))) Make sure to pass along the | |
DO NOT CALL THIS METHOD DIRECTLY. Called automatically once and only once the first time a non-trivial driver method is called; implementers should do one-time initialization as needed (for example, registering JDBC drivers used internally by the driver.) 'Trivial' methods include a tiny handful of ones like [[connection-properties]] that simply provide information about the driver, but do not connect to databases; these can be be supplied, for example, by a Metabase plugin manifest file (which is supplied for lazy-loaded drivers). Methods that require connecting to a database dispatch off of [[the-initialized-driver]], which will initialize a driver if not already done so. You will rarely need to write an implentation for this method yourself. A lazy-loaded driver (like most of the
Metabase drivers in v1.0 and above) are automatiaclly given an implentation of this method that performs the
If you do need to implement this method yourself, you do not need to call parent implementations. We'll take care of that for you. | (defmulti initialize! {:added "0.32.0" :arglists '([driver])} dispatch-on-uninitialized-driver) |
VERY IMPORTANT: Unlike all other driver multimethods, we DO NOT use the driver hierarchy for dispatch here. Why?
We do not want a driver to inherit parent drivers' implementations and have those implementations end up getting
called multiple times. If a driver does not implement
| |
(defmethod initialize! :default [_]) ; no-op | |
A nice name for the driver that we'll display to in the admin panel, e.g. "PostgreSQL" for When writing a driver that you plan to ship as a separate, lazy-loading plugin (including core drivers packaged this
way, like SQLite), you do not need to implement this method; instead, specifiy it in your plugin manifest, and
| (defmulti display-name {:added "0.32.0" :arglists '([driver])} dispatch-on-uninitialized-driver :hierarchy #'hierarchy) |
(defmethod display-name :default [driver] (str/capitalize (name driver))) | |
The contact information for the driver | (defmulti contact-info {:changelog-test/ignore true :added "0.43.0" :arglists '([driver])} dispatch-on-uninitialized-driver :hierarchy #'hierarchy) |
(defmethod contact-info :default [_] nil) | |
Dispatch on initialized driver, except checks for | (defn dispatch-on-initialized-driver-safe-keys [driver details-map] (let [invalid-keys #{"classname" "subprotocol" "connection-uri"} ks (->> details-map keys (map name) (map u/lower-case-en) set)] (when (seq (set/intersection ks invalid-keys)) (throw (ex-info "Cannot specify subname, protocol, or connection-uri in details map" {:invalid-keys (set/intersection ks invalid-keys)}))) (dispatch-on-initialized-driver driver))) |
Check whether we can connect to a | (defmulti can-connect? {:added "0.32.0" :arglists '([driver details])} dispatch-on-initialized-driver-safe-keys :hierarchy #'hierarchy) |
Return a map containing information that describes the version of the DBMS. This typically includes a
| (defmulti dbms-version {:changelog-test/ignore true :added "0.46.0" :arglists '([driver database])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Some drivers like BigQuery or Snowflake cannot provide a meaningful stable version. | (defmethod dbms-version :default [_ _] nil) |
Return a map containing information that describes all of the tables in a | (defmulti describe-database {:added "0.32.0" :arglists '([driver database])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Return a map containing a single field | (defmulti describe-table {:added "0.32.0" :arglists '([driver database table])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Returns a reducible collection of maps, each containing information about fields. It includes which keys are primary keys, but not foreign keys. It does not include nested fields (e.g. fields within a JSON column). Takes keyword arguments to narrow down the results to a set of
Results match [[metabase.sync.interface/FieldMetadataEntry]].
Results are optionally filtered by | (defmulti describe-fields {:added "0.49.1" :arglists '([driver database & {:keys [schema-names table-names]}])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Returns a set of map containing information about the indexes of a table. Currently we only sync single column indexes or the first column of a composite index. Results should match the [[metabase.sync.interface/TableIndexMetadata]] schema. | (defmulti describe-table-indexes {:added "0.49.0" :arglists '([driver database table])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Returns a reducible collection of maps, each containing information about the indexes of a database. Currently we only sync single column indexes or the first column of a composite index. We currently only support indexes on unnested fields (i.e., where parent_id is null). Takes keyword arguments to narrow down the results to a set of
Results match [[metabase.sync.interface/FieldIndexMetadata]].
Results are optionally filtered by | (defmulti describe-indexes {:added "0.51.4" :arglists '([driver database & {:keys [schema-names table-names]}])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
escaping for when calling For example, oracle treats slashes differently when querying versus when used with | (defmulti escape-entity-name-for-metadata {:arglists '([driver entity-name]), :added "0.37.0"} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod escape-entity-name-for-metadata :default [_driver table-name] table-name) | |
Return information about the foreign keys in a | (defmulti describe-table-fks {:added "0.32.0" :deprecated "0.49.0" :arglists '([driver database table])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
#_{:clj-kondo/ignore [:deprecated-var]} (defmethod describe-table-fks ::driver [_ _ _] nil) | |
Returns a reducible collection of maps, each containing information about foreign keys.
Takes optional keyword arguments to narrow down the results to a set of Results match [[metabase.sync.interface/FKMetadataEntry]].
Results are optionally filtered by Required for drivers that support | (defmulti describe-fks {:added "0.49.0" :arglists '([driver database & {:keys [schema-names table-names]}])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod describe-fks ::driver [_ _] nil) | |
this is no longer used but we can leave it around for not for documentation purposes. Maybe we can actually do something useful with it like write a test that validates that drivers return correct connection details? | |
Return information about the connection properties that should be exposed to the user for databases that will use
this driver. This information is used to build the UI for editing a Database There are several definitions for common properties available in the [[metabase.driver.common]] namespace, such as
Like | #_(def ConnectionDetailsProperty "Schema for a map containing information about a connection property we should ask the user to supply when setting up a new database, as returned by an implementation of `connection-properties`." (s/constrained {;; The key that should be used to store this property in the `details` map. :name su/NonBlankString ;; Human-readable name that should be displayed to the User in UI for editing this field. :display-name su/NonBlankString ;; Human-readable text that gives context about a field's input. (s/optional-key :helper-text) s/Str ;; Type of this property. Defaults to `:string` if unspecified. ;; `:select` is a `String` in the backend. (s/optional-key :type) (s/enum :string :integer :boolean :password :select :text) ;; A default value for this field if the user hasn't set an explicit value. This is shown in the UI as a ;; placeholder. (s/optional-key :default) s/Any ;; Placeholder value to show in the UI if user hasn't set an explicit value. Similar to `:default`, but this value ;; is *not* saved to `:details` if no explicit value is set. Since `:default` values are also shown as ;; placeholders, you cannot specify both `:default` and `:placeholder`. (s/optional-key :placeholder) s/Any ;; Is this property required? Defaults to `false`. (s/optional-key :required?) s/Bool ;; Any options for `:select` types (s/optional-key :options) {s/Keyword s/Str}} (complement (every-pred #(contains? % :default) #(contains? % :placeholder))) "connection details that does not have both default and placeholder")) (defmulti connection-properties {:added "0.32.0" :arglists '([driver])} dispatch-on-uninitialized-driver :hierarchy #'hierarchy) |
Execute a native query against that database and return rows that can be reduced using Pass metadata about the columns and the reducible object to (respond results-metadata rows) You can use [[metabase.query-processor.reducible/reducible-rows]] to create reducible, streaming results.
Example impl: (defmethod reducible-query :my-driver [_ query context respond] (with-open [results (run-query! query)] (respond {:cols [{:name "my_col"}]} (qp.reducible/reducible-rows (get-row results) (context/canceled-chan context))))) | (defmulti execute-reducible-query {:added "0.35.0", :arglists '([driver query context respond])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Optional. Efficiently calculate metadata about the columns that would be returned if we were to run a
(query-results-metadata :postgres {:lib/type :mbql/query :stages [{:lib/type :mbql.stage/native :native "SELECT * FROM venues WHERE id = ?" :args [1]}] ...}) => [{:lib/type :metadata/column :name "ID" :database-type "BIGINT" :base-type :type/BigInteger} {:lib/type :metadata/column :name "NAME" :database-type "CHARACTER VARYING" :base-type :type/Text} ...] Metadata should be returned as a sequence of column maps matching the This is needed in certain circumstances such as saving native queries before they have been run; metadata for MBQL-only queries can usually be determined by looking at the query itself without any driver involvement. If this method does need to be invoked, ideally it can calculate this information without actually having to run the
query in question; it that is not possible, ideally we'd run a faster version of the query with the equivalent of
A naive default implementation of this method lives in [[metabase.query-processor.metadata]] that runs the query in
question with a The There is no guarantee that | (defmulti query-result-metadata {:added "0.51.0", :arglists '([driver query])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Set of all features a driver can support. | (def features #{;; Does this database track and enforce primary key and foreign key constraints in the schema? ;; Is the database capable of reporting columns as PK or FK? (Relevant during sync.) ;; ;; Not to be confused with Metabase's notion of foreign key columns. Those are user definable and power eg. ;; implicit joins. :metadata/key-constraints ;; Does this database support nested fields for any and every field except primary key (e.g. Mongo)? :nested-fields ;; Does this database support nested fields but only for certain field types (e.g. Postgres and JSON / JSONB columns)? :nested-field-columns ;; Does this driver support setting a timezone for the query? :set-timezone ;; Does the driver support *basic* aggregations like `:count` and `:sum`? (Currently, everything besides standard ;; deviation is considered \"basic\"; only GA doesn't support this). ;; ;; DEFAULTS TO TRUE. :basic-aggregations ;; Does this driver support standard deviation and variance aggregations? Note that if variance is not supported ;; directly, you can calculate it manually by taking the square of the standard deviation. See the MongoDB driver ;; for example. :standard-deviation-aggregations ;; Does this driver support expressions (e.g. adding the values of 2 columns together)? :expressions ;; Does this driver support parameter substitution in native queries, where parameter expressions are replaced ;; with a single value? e.g. ;; ;; SELECT * FROM table WHERE field = {{param}} ;; -> ;; SELECT * FROM table WHERE field = 1 :native-parameters ;; Does the driver support using expressions inside aggregations? e.g. something like \"sum(x) + count(y)\" or ;; \"avg(x + y)\" :expression-aggregations ;; Does the driver support using a query as the `:source-query` of another MBQL query? Examples are CTEs or ;; subselects in SQL queries. :nested-queries ;; Does this driver support native template tag parameters of type `:card`, e.g. in a native query like ;; ;; SELECT * FROM {{card}} ;; ;; do we support substituting `{{card}}` with another compiled (nested) query? ;; ;; By default, this is true for drivers that support `:native-parameters` and `:nested-queries`, but drivers can opt ;; out if they do not support Card ID template tag parameters. :native-parameter-card-reference ;; Does the driver support persisting models :persist-models ;; Is persisting enabled? :persist-models-enabled ;; Does the driver support binning as specified by the `binning-strategy` clause? :binning ;; Does this driver not let you specify whether or not our string search filter clauses (`:contains`, ;; `:starts-with`, and `:ends-with`, collectively the equivalent of SQL `LIKE`) are case-senstive or not? This ;; informs whether we should present you with the 'Case Sensitive' checkbox in the UI. At the time of this writing ;; SQLite, SQLServer, and MySQL do not support this -- `LIKE` clauses are always case-insensitive. ;; ;; DEFAULTS TO TRUE. :case-sensitivity-string-filter-options ;; Implicit joins require :left-join (only) to work. :left-join :right-join :inner-join :full-join :regex ;; Does the driver support advanced math expressions such as log, power, ... :advanced-math-expressions ;; Does the driver support percentile calculations (including median) :percentile-aggregations ;; Does the driver support date extraction functions? (i.e get year component of a datetime column) ;; DEFAULTS TO TRUE :temporal-extract ;; Does the driver support doing math with datetime? (i.e Adding 1 year to a datetime column) ;; DEFAULTS TO TRUE :date-arithmetics ;; Does the driver support the :now function :now ;; Does the driver support converting timezone? ;; DEFAULTS TO FALSE :convert-timezone ;; Does the driver support :datetime-diff functions :datetime-diff ;; Does the driver support experimental "writeback" actions like "delete this row" or "insert a new row" from 44+? :actions ;; Does the driver support storing table privileges in the application database for the current user? :table-privileges ;; Does the driver support uploading files :uploads ;; Does the driver support schemas (aka namespaces) for tables ;; DEFAULTS TO TRUE :schemas ;; Does the driver support custom writeback actions. Drivers that support this must ;; implement [[execute-write-query!]] :actions/custom ;; Does changing the JVM timezone allow producing correct results? (See #27876 for details.) :test/jvm-timezone-setting ;; Does the driver support connection impersonation (i.e. overriding the role used for individual queries)? :connection-impersonation ;; Does the driver require specifying the default connection role for connection impersonation to work? :connection-impersonation-requires-role ;; Does the driver require specifying a collection (table) for native queries? (mongo) :native-requires-specified-collection ;; Does the driver support column(s) support storing index info :index-info ;; Does the driver support a faster `sync-fks` step by fetching all FK metadata in a single collection? ;; if so, `metabase.driver/describe-fks` must be implemented instead of `metabase.driver/describe-table-fks` :describe-fks ;; Does the driver support a faster `sync-fields` step by fetching all FK metadata in a single collection? ;; if so, `metabase.driver/describe-fields` must be implemented instead of `metabase.driver/describe-table` :describe-fields ;; Does the driver support a faster `sync-indexes` step by fetching all index metadata in a single collection? ;; If true, `metabase.driver/describe-indexes` must be implemented instead of `metabase.driver/describe-table-indexes` :describe-indexes ;; Does the driver support automatically adding a primary key column to a table for uploads? ;; If so, Metabase will add an auto-incrementing primary key column called `_mb_row_id` for any table created or ;; updated with CSV uploads, and ignore any `_mb_row_id` column in the CSV file. ;; DEFAULTS TO TRUE :upload-with-auto-pk ;; Does the driver support fingerprint the fields. Default is true :fingerprint ;; Does a connection to this driver correspond to a single database (false), or to multiple databases (true)? ;; Default is false; ie. a single database. This is common for classic relational DBs and some cloud databases. ;; Some have access to many databases from one connection; eg. Athena connects to an S3 bucket which might have ;; many databases in it. :connection/multiple-databases ;; Does the driver support identifiers for tables and columns that contain spaces. Defaults to `false`. :identifiers-with-spaces ;; Does this driver support UUID type :uuid-type ;; True if this driver requires `:temporal-unit :default` on all temporal field refs, even if no temporal ;; bucketing was specified in the query. ;; Generally false, but a few time-series based analytics databases (eg. Druid) require it. :temporal/requires-default-unit ;; Does this driver support window functions like cumulative count and cumulative sum? (default: false) :window-functions/cumulative ;; Does this driver support the new `:offset` MBQL clause added in 50? (i.e. SQL `lag` and `lead` or equivalent ;; functions) :window-functions/offset ;; Does this driver support parameterized sql, eg. in prepared statements? :parameterized-sql ;; Whether the driver supports loading dynamic test datasets on each test run. Eg. datasets with names like ;; `checkins:4-per-minute` are created dynamically in each test run. This should be truthy for every driver we test ;; against except for Athena and Databricks which currently require test data to be loaded separately. :test/dynamic-dataset-loading}) |
Does this driver and specific instance of a database support a certain Database is guaranteed to be a Database instance. Most drivers can always return true or always return false for a given feature (e.g., :left-join is not supported by any version of Mongo DB). In some cases, a feature may only be supported by certain versions of the database engine.
In this case, after implementing (database-supports? :mongo :set-timezone mongo-db) ; -> true | (defmulti database-supports? {:arglists '([driver feature database]), :added "0.41.0"} (fn [driver feature _database] ;; only make sure unqualified keywords are explicitly defined in [[features]]. (when (simple-keyword? feature) (when-not (features feature) (throw (ex-info (tru "Invalid driver feature: {0}" feature) {:feature feature})))) [(dispatch-on-initialized-driver driver) feature]) :hierarchy #'hierarchy) |
(defmethod database-supports? :default [_driver _feature _] false) | |
(doseq [[feature supported?] {:convert-timezone false :basic-aggregations true :case-sensitivity-string-filter-options true :date-arithmetics true :parameterized-sql false :temporal-extract true :schemas true :test/jvm-timezone-setting true :fingerprint true :upload-with-auto-pk true :test/dynamic-dataset-loading true}] (defmethod database-supports? [::driver feature] [_driver _feature _db] supported?)) | |
By default a driver supports | (defmethod database-supports? [::driver :native-parameter-card-reference] [driver _feature database] (and (database-supports? driver :native-parameters database) (database-supports? driver :nested-queries database))) |
Escape a These aliases can be dynamically generated in [[metabase.query-processor.util.add-alias-info]] or elsewhere
(usually based on underlying table or column names) but can also be specified in the MBQL query itself for explicit
joins. For The default impl of [[escape-alias]] calls [[metabase.driver.impl/truncate-alias]] and truncates the alias to [[metabase.driver.impl/default-alias-max-length-bytes]]. You can call this function with a different max length if you need to generate shorter aliases. That method is currently only used drivers that derive from | (defmulti ^String escape-alias {:added "0.42.0", :arglists '([driver column-or-table-alias])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(mu/defmethod escape-alias ::driver :- :string [_driver alias-name :- :string] (driver.impl/truncate-alias alias-name)) | |
Return a humanized (user-facing) version of an connection error message.
Generic error messages provided in [[metabase.driver.util/connection-error-messages]]; should be returned
as keywords whenever possible. This provides for both unified error messages and categories which let us point
users to the erroneous input fields.
Error messages can also be strings, or localized strings, as returned by [[metabase.util.i18n/trs]] and
| (defmulti humanize-connection-error-message {:added "0.32.0" :arglists '([this message])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod humanize-connection-error-message ::driver [_ message] message) | |
Transpile an MBQL query into the appropriate native query form. If the underlying query language supports remarks or comments, the driver should
use [[metabase.query-processor.util/query->remark]] to generate an appropriate message and include that in an
appropriate place; alternatively a driver might directly include the query's The result of this function will be passed directly into calls to [[execute-reducible-query]]. For example, a driver like Postgres would build a valid SQL expression and return a map such as: {:query "-- Metabase card: 10 user: 5 SELECT * FROM my_table"} In 0.51.0 and above, drivers should look the value of [[compile-with-inline-parameters]] and output a query with all parameters inline when it is truthy. | (defmulti mbql->native {:added "0.32.0", :arglists '([driver query])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Pretty-format native form presumably coming from compiled query.
Used eg. in the API endpoint How to use and extend this method?At the time of writing, this method acts as identity for nosql drivers. However, story with sql drivers is a bit
different. To extend it for sql drivers, developers could use [[metabase.driver.sql.util/format-sql]]. Function
in question is implemented in a way, that developers, implemnting this multimethod can:
- Avoid implementing it completely, if their driver keyword representation corresponds to key in
[[metabase.driver.sql.util/dialects]] (eg. | (defmulti prettify-native-form {:added "0.47.0", :arglists '([driver native-form])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod prettify-native-form ::driver [_ native-form] native-form) | |
Whether to compile an MBQL query to native with parameters spliced inline (as opposed to using placeholders like WHERE bird_type = 'cockatiel' instead of WHERE bird_type = ? so we bind this to Drivers that have some notion of parameterized queries (e.g. | (def ^:dynamic ^{:added "0.51.0"} *compile-with-inline-parameters* false) |
Deprecated and unused in 0.51.0+; multimethod declaration left here so drivers implementing it can still compile until we remove this method completely in 0.54.0 or later. Instead of implementing this method, you should instead look at the value of [[metabase.driver/compile-with-inline-parameters]] in your implementation of [[metabase.driver/mbql->native]] and adjust behavior accordingly. | (defmulti splice-parameters-into-native-query {:added "0.32.0", :arglists '([driver inner-query]), :deprecated "0.51.0"} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
#_{:clj-kondo/ignore [:deprecated-var]} (defmethod splice-parameters-into-native-query ::driver [_driver _query] (throw (ex-info (str "metabase.driver/splice-parameters-into-native-query is deprecated, bind" " metabase.driver/*compile-with-inline-parameters* during query compilation instead.") {:type ::qp.error-type/driver}))) | |
Notify the driver that the attributes of a TODO -- shouldn't this be called | (defmulti notify-database-updated {:added "0.32.0" :arglists '([driver database])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod notify-database-updated ::driver [_ _] nil) ; no-op | |
Drivers may provide this function if they need to do special setup before a sync operation such as
(defn sync-in-context [driver database f] (with-connection [_ database] (f))) | (defmulti sync-in-context {:added "0.32.0", :arglists '([driver database f])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod sync-in-context ::driver [_ _ f] (f)) | |
Return a sequence of all the rows in a given This method is currently only used by the H2 driver to load the Sample Database, so it is not neccesary for any other drivers to implement it at this time. | (defmulti table-rows-seq {:added "0.32.0" :arglists '([driver database table])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Return the system timezone ID name of this database, i.e. the timezone that local dates/times/datetimes are
considered to be in by default. Ideally, this method should return a timezone ID like This is currently used only when syncing the
Database (see [[metabase.sync.sync-metadata.sync-timezone/sync-timezone!]]) -- the result of this method is stored
in the In theory this method should probably not return This method should return a [[String]], a [[java.time.ZoneId]], or a [[java.time.ZoneOffset]]. | (defmulti db-default-timezone {:added "0.34.0", :arglists '([driver database])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod db-default-timezone ::driver [_driver _database] nil) | |
For drivers that support {:query "SELECT count(*) FROM table WHERE id = {{param}}" :template-tags {:param {:name "param", :display-name "Param", :type :number}} :parameters [{:type :number :target [:variable [:template-tag "param"]] :value 2}]} -> {:query "SELECT count(*) FROM table WHERE id = 2"} Much of the implementation for this method is shared across drivers and lives in the
| (defmulti substitute-native-parameters {:added "0.34.0" :arglists '([driver inner-query])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Return how fields should be sorted by default for this database. | (defmulti default-field-order {:added "0.36.0" :arglists '([driver])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod default-field-order ::driver [_] :database) | |
Return the day that is considered to be the start of week by TODO -- this can vary based on session variables or connection options Issue: https://github.com/metabase/metabase/pull/39386 | (defmulti db-start-of-week {:added "0.37.0" :arglists '([driver])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
A multimethod for driver-specific behavior required to incorporate details for an opened SSH tunnel into the DB details. In most cases, this will simply involve updating the :host and :port (to point to the tunnel entry point, instead of the backing database server), but some drivers may have more specific behavior. WARNING! Implementations of this method may create new SSH tunnels, which need to be cleaned up. DO NOT USE THIS METHOD DIRECTLY UNLESS YOU ARE GOING TO BE CLEANING UP ANY CREATED TUNNELS! Instead, you probably want to use [[metabase.util.ssh/with-ssh-tunnel]]. See #24445 for more information. | (defmulti incorporate-ssh-tunnel-details {:added "0.39.0" :arglists '([driver db-details])} dispatch-on-uninitialized-driver :hierarchy #'hierarchy) |
A multimethod for driver specific behavior required to incorporate response of an auth-provider into the DB details. In most cases this means setting the :password and/or :username based on the auth-provider and its response. | (defmulti incorporate-auth-provider-details {:added "0.50.17" :arglists '([driver auth-provider auth-provider-response details])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod incorporate-auth-provider-details :default [_driver _auth-provider _auth-provider-response details] details) | |
(defmethod incorporate-auth-provider-details :sql-jdbc [_driver auth-provider auth-provider-response details] (case auth-provider (:oauth :azure-managed-identity) (let [{:keys [access_token expires_in]} auth-provider-response] (cond-> (assoc details :password access_token) expires_in (assoc :password-expiry-timestamp (+ (System/currentTimeMillis) (* (- (parse-long expires_in) auth-provider/azure-auth-token-renew-slack-seconds) 1000))))) (merge details auth-provider-response))) | |
Normalizes db-details for the given driver. This is to handle migrations that are too difficult to perform via
regular Liquibase queries. This multimethod will be called from a TODO:
| (defmulti normalize-db-details {:added "0.41.0" :arglists '([driver database])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod normalize-db-details ::driver [_ database] ;; no normalization by default database) | |
Returns the driver that supersedes the given This is currently only used on the frontend for the purpose of showing/hiding deprecated drivers. A driver can make
use of this facility by adding a top-level | (defmulti superseded-by {:added "0.41.0" :arglists '([driver])} dispatch-on-uninitialized-driver :hierarchy #'hierarchy) |
(defmethod superseded-by :default [_] nil) | |
Execute a writeback query e.g. one powering a custom | (defmulti execute-write-query! {:changelog-test/ignore true, :added "0.44.0", :arglists '([driver query])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Processes a sample of rows produced by | (defmulti table-rows-sample {:arglists '([driver table fields rff opts]), :added "0.46.0"} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Sets the database role used on a connection. Called prior to query execution for drivers that support connection impersonation (an EE-only feature). | (defmulti set-role! {:added "0.47.0" :arglists '([driver conn role])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
+----------------------------------------------------------------------------------------------------------------+ | Upload | +----------------------------------------------------------------------------------------------------------------+ | |
The number of rows to insert at a time when uploading data to a database. This can be bound for testing purposes. | (def ^:dynamic *insert-chunk-rows* nil) |
Return the maximum number of bytes allowed in a table name, or | (defmulti table-name-length-limit {:changelog-test/ignore true, :added "0.47.0", :arglists '([driver])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Return the maximum number of bytes allowed in a column name, or | (defmulti column-name-length-limit {:changelog-test/ignore true, :added "0.49.19", :arglists '([driver])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod column-name-length-limit :default [driver] ;; For most databases, the same limit is used for all identifier types. (table-name-length-limit driver)) | |
Create a table named | (defmulti create-table! {:added "0.47.0", :arglists '([driver database-id table-name column-definitions & args])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Drop a table named schema.table | (defmulti drop-table! {:added "0.47.0", :arglists '([driver db-id ^String table-name])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Delete the current contents of | (defmulti truncate! {:added "0.50.0", :arglists '([driver db-id table-name])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Insert The types in | (defmulti insert-into! {:added "0.47.0", :arglists '([driver db-id table-name column-names values])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Add columns given by | (defmulti add-columns! {:added "0.49.0", :arglists '([driver db-id table-name column-definitions & args])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Alter columns given by | (defmulti alter-columns! {:added "0.49.0", :arglists '([driver db-id table-name column-definitions])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Returns the set of syncable schemas in the database (as strings). | (defmulti syncable-schemas {:added "0.47.0", :arglists '([driver database])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod syncable-schemas ::driver [_ _] #{}) | |
Returns the database type for a given
| (defmulti upload-type->database-type {:changelog-test/ignore true, :added "0.47.0", :arglists '([driver upload-type])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
Returns true if the driver should create an auto-incrementing primary key column when appending CSV data to an existing upload table. This is because we want to add auto-pk columns for drivers that supported uploads before auto-pk columns were introduced by metabase#36249. It should return false if the driver supported the uploads feature in version 48 or later. | (defmulti create-auto-pk-with-append-csv? {:added "0.49.0" :arglists '([driver])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |
(defmethod create-auto-pk-with-append-csv? ::driver [_] false) | |
Returns the rows of data as arrays needed to populate the table_privileges table
with the DB connection's current user privileges.
The data contains the privileges that the user has on the given The rows have the following keys and value types: - role :- [:maybe :string] - schema :- [:maybe :string] - table :- :string - select :- :boolean - update :- :boolean - insert :- :boolean - delete :- :boolean Either: (1) role is null, corresponding to the privileges of the DB connection's current user (2) role is not null, corresponding to the privileges of the role | (defmulti current-user-table-privileges {:added "0.48.0", :arglists '([driver database & args])} dispatch-on-initialized-driver :hierarchy #'hierarchy) |