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 regsiter!
.
SQL-based drivers can use the :sql
driver as a parent, and JDBC-based SQL drivers can use :sql-jdbc
. Both of
these drivers define additional multimethods that child drivers should implement; see metabase.driver.sql
and
metabase.driver.sql-jdbc
for more details.
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 `regsiter!`. SQL-based drivers can use the `:sql` driver as a parent, and JDBC-based SQL drivers can use `:sql-jdbc`. Both of these drivers define additional multimethods that child drivers should implement; see `metabase.driver.sql` and `metabase.driver.sql-jdbc` for more details.
Current driver (a keyword such as :postgres
) in use by the Query Processor/tests/etc. Bind this with with-driver
below. The QP binds the driver this way in the bind-driver
middleware.
Current driver (a keyword such as `:postgres`) in use by the Query Processor/tests/etc. Bind this with `with-driver` below. The QP binds the driver this way in the `bind-driver` middleware.
(abstract? driver)
Is driver
an abstract "base class"?
Is `driver` an abstract "base class"?
(add-parent! driver new-parent)
Add a new parent to driver
.
Add a new parent to `driver`.
(available? driver)
Is this driver available for use? (i.e. should we show it as an option when adding a new database?) This is true
by
default for all non-abstract driver types and false for abstract ones; some drivers might want to override this to
return false even if the driver isn't abstract -- for example, the Oracle driver might return false if the JDBC
driver it depends on is not available.
This method is also used in tests to determine whether the standard set of Query Processor tests should
automatically against it; for one-off test drivers to test specific functionality, you should return false
.
DEPRECATED -- drivers that require external dependencies are now shipping as plugins; they can declare dependencies on external classes, and we can skip loading them entirely if the dependencies are not available. Please do not implement this method; it will most likely be removed before version 1.0 ships.
Is this driver available for use? (i.e. should we show it as an option when adding a new database?) This is `true` by default for all non-abstract driver types and false for abstract ones; some drivers might want to override this to return false even if the driver isn't abstract -- for example, the Oracle driver might return false if the JDBC driver it depends on is not available. This method is also used in tests to determine whether the standard set of Query Processor tests should automatically against it; for one-off test drivers to test specific functionality, you should return `false`. DEPRECATED -- drivers that require external dependencies are now shipping as plugins; they can declare dependencies on external classes, and we can skip loading them entirely if the dependencies are not available. Please do not implement this method; it will most likely be removed before version 1.0 ships.
(can-connect? driver details)
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
.
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`.
(connection-properties driver)
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 details
map, and for validating it on
the backend. It should include things like host
, port
, and other driver-specific parameters. Each property must
conform to the ConnectionDetailsProperty
schema above.
There are several definitions for common properties available in the metabase.driver.common
namespace, such as
default-host-details
and default-port-details
. Prefer using these if possible.
Like display-name
, lazy-loaded drivers should specify this in their plugin manifest; lazy-loaded-driver
will
automatically create an implementation for you.
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 `details` map, and for validating it on the backend. It should include things like `host`, `port`, and other driver-specific parameters. Each property must conform to the `ConnectionDetailsProperty` schema above. There are several definitions for common properties available in the `metabase.driver.common` namespace, such as `default-host-details` and `default-port-details`. Prefer using these if possible. Like `display-name`, lazy-loaded drivers should specify this in their plugin manifest; `lazy-loaded-driver` will automatically create an implementation for you.
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
.
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`.
(current-db-time driver database)
Return the current time and timezone from the perspective of database
. You can use
metabase.driver.common/current-db-time
to implement this. This should return a Joda-Time DateTime
.
Return the current time and timezone from the perspective of `database`. You can use `metabase.driver.common/current-db-time` to implement this. This should return a Joda-Time `DateTime`.
(date-interval driver unit amount)
Return an driver-appropriate representation of a moment relative to the current moment in time. By default, this
returns an Timestamp
by calling metabase.util.date/relative-date
; but when possible drivers should return a
native form so we can be sure the correct timezone is applied. For example, SQL drivers should return a HoneySQL
form to call the appropriate SQL fns:
(date-interval :postgres :month 1) -> (hsql/call :+ :%now (hsql/raw "INTERVAL '1 month'"))
Return an driver-appropriate representation of a moment relative to the current moment in time. By default, this returns an `Timestamp` by calling `metabase.util.date/relative-date`; but when possible drivers should return a native form so we can be sure the correct timezone is applied. For example, SQL drivers should return a HoneySQL form to call the appropriate SQL fns: (date-interval :postgres :month 1) -> (hsql/call :+ :%now (hsql/raw "INTERVAL '1 month'"))
(describe-database driver database)
Return a map containing information that describes all of the tables in a database
, an instance of the Database
model. It is expected that this function will be peformant and avoid draining meaningful resources of the database.
Results should match the metabase.sync.interface/DatabaseMetadata
schema.
Return a map containing information that describes all of the tables in a `database`, an instance of the `Database` model. It is expected that this function will be peformant and avoid draining meaningful resources of the database. Results should match the `metabase.sync.interface/DatabaseMetadata` schema.
(describe-table driver database table)
Return a map containing information that describes the physical schema of table
(i.e. the fields contained
therein). database
will be an instance of the Database
model; and table
, an instance of the Table
model. It is
expected that this function will be peformant and avoid draining meaningful resources of the database. Results
should match the metabase.sync.interface/TableMetadata
schema.
Return a map containing information that describes the physical schema of `table` (i.e. the fields contained therein). `database` will be an instance of the `Database` model; and `table`, an instance of the `Table` model. It is expected that this function will be peformant and avoid draining meaningful resources of the database. Results should match the `metabase.sync.interface/TableMetadata` schema.
(describe-table-fks this database table)
Return information about the foreign keys in a table
. Required for drivers that support :foreign-keys
. Results
should match the metabase.sync.interface/FKMetadata
schema.
Return information about the foreign keys in a `table`. Required for drivers that support `:foreign-keys`. Results should match the `metabase.sync.interface/FKMetadata` schema.
(dispatch-on-initialized-driver 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.)
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.)
(display-name driver)
A nice name for the driver that we'll display to in the admin panel, e.g. "PostgreSQL" for :postgres
. Default
implementation capitializes the name of the driver, e.g. :presto
becomes "Presto".
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
lazy-loaded-driver
will create an implementation for you. Probably best if we only have one place where we set
values for this.
A nice name for the driver that we'll display to in the admin panel, e.g. "PostgreSQL" for `:postgres`. Default implementation capitializes the name of the driver, e.g. `:presto` becomes "Presto". 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 `lazy-loaded-driver` will create an implementation for you. Probably best if we only have one place where we set values for this.
Set of all features a driver can support.
Set of all features a driver can support.
(execute-query driver query)
Execute a native query against the database and return the results.
The query passed in will conform to the schema in metabase.mbql.schema/Query
. MBQL queries are transformed to
native queries via the mbql->native
QP middleware, which in turn calls this driver's implementation of
mbql->native
before reaching this method.
Results should look like:
{:columns ["id", "name"] :rows [[1 "Lucky Bird"] [2 "Rasta Can"]]}
Execute a *native* query against the database and return the results. The query passed in will conform to the schema in `metabase.mbql.schema/Query`. MBQL queries are transformed to native queries via the `mbql->native` QP middleware, which in turn calls this driver's implementation of `mbql->native` before reaching this method. Results should look like: {:columns ["id", "name"] :rows [[1 "Lucky Bird"] [2 "Rasta Can"]]}
(format-custom-field-name driver custom-field-name)
Return the custom name passed via an MBQL :named
clause so it matches the way it is returned in the results. This
is used by the post-processing annotation stage to find the correct metadata to include with fields in the results.
The default implementation is identity
, meaning the resulting field will have exactly the same name as passed to
the :named
clause. Certain drivers like Redshift always lowercase these names, so this method is provided for
those situations.
Return the custom name passed via an MBQL `:named` clause so it matches the way it is returned in the results. This is used by the post-processing annotation stage to find the correct metadata to include with fields in the results. The default implementation is `identity`, meaning the resulting field will have exactly the same name as passed to the `:named` clause. Certain drivers like Redshift always lowercase these names, so this method is provided for those situations.
Driver hierarchy. Used by driver multimethods for dispatch. Add new drivers with regsiter!
.
Driver hierarchy. Used by driver multimethods for dispatch. Add new drivers with `regsiter!`.
(humanize-connection-error-message this message)
Return a humanized (user-facing) version of an connection error message string. Generic error messages are provided
in metabase.driver.common/connection-error-messages
; return one of these whenever possible.
Return a humanized (user-facing) version of an connection error message string. Generic error messages are provided in `metabase.driver.common/connection-error-messages`; return one of these whenever possible.
(initialize! driver)
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
init-steps
specified in the plugin manifest (such as loading namespaces in question).
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.
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 `init-steps` specified in the plugin manifest (such as loading namespaces in question). 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.
(initialized? driver)
Has driver
been initialized? (See initialize!
below for a discussion of what exactly this means.)
Has `driver` been initialized? (See `initialize!` below for a discussion of what exactly this means.)
(mbql->native driver query)
Transpile an MBQL query into the appropriate native query form. query
will match the schema for an MBQL query in
metabase.mbql.schema/Query
; this function should return a native query that conforms to that schema.
If the underlying query language supports remarks or comments, the driver should use query->remark
to generate an
appropriate message and include that in an appropriate place; alternatively a driver might directly include the
query's :info
dictionary if the underlying language is JSON-based.
The result of this function will be passed directly into calls to execute-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"}
Transpile an MBQL query into the appropriate native query form. `query` will match the schema for an MBQL query in `metabase.mbql.schema/Query`; this function should return a native query that conforms to that schema. If the underlying query language supports remarks or comments, the driver should use `query->remark` to generate an appropriate message and include that in an appropriate place; alternatively a driver might directly include the query's `:info` dictionary if the underlying language is JSON-based. The result of this function will be passed directly into calls to `execute-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"}
(notify-database-updated driver database)
Notify the driver that the attributes of a database
have changed, or that `database was deleted. This is
specifically relevant in the event that the driver was doing some caching or connection pooling; the driver should
release ALL related resources when this is called.
Notify the driver that the attributes of a `database` have changed, or that `database was deleted. This is specifically relevant in the event that the driver was doing some caching or connection pooling; the driver should release ALL related resources when this is called.
(process-query-in-context driver qp)
Similar to sync-in-context
, but for running queries rather than syncing. This should be used to do things like
open DB connections that need to remain open for the duration of post-processing. This function follows a middleware
pattern and is injected into the QP middleware stack immediately after the Query Expander; in other words, it will
receive the expanded query. See the Mongo and H2 drivers for examples of how this is intended to be used.
(defn process-query-in-context [driver qp]
(fn [query]
(qp query)))
Similar to `sync-in-context`, but for running queries rather than syncing. This should be used to do things like open DB connections that need to remain open for the duration of post-processing. This function follows a middleware pattern and is injected into the QP middleware stack immediately after the Query Expander; in other words, it will receive the expanded query. See the Mongo and H2 drivers for examples of how this is intended to be used. (defn process-query-in-context [driver qp] (fn [query] (qp query)))
(register! driver & {:keys [parent abstract?]})
Register a driver.
(register! :sql, :abstract? true)
(register! :postgres, :parent :sql-jdbc)
Valid options are:
:parent
(default = none)Parent driver(s) to derive from. Drivers inherit method implementations from their parents similar to the way inheritance works in OOP. Specify multiple direct parents by passing a collection of parents.
You can add additional parents to a driver using add-parent!
below; this is how test extensions are implemented.
:abstract?
(default = false)Is this an abstract driver (i.e. should we hide it in the admin interface, and disallow running queries with it)?
Note that because concreteness is implemented as part of our keyword hierarchy it is not currently possible to
create an abstract driver with a concrete driver as its parent, since it would still ultimately derive from
::concrete
.
Register a driver. (register! :sql, :abstract? true) (register! :postgres, :parent :sql-jdbc) Valid options are: ###### `:parent` (default = none) Parent driver(s) to derive from. Drivers inherit method implementations from their parents similar to the way inheritance works in OOP. Specify multiple direct parents by passing a collection of parents. You can add additional parents to a driver using `add-parent!` below; this is how test extensions are implemented. ###### `:abstract?` (default = false) Is this an abstract driver (i.e. should we hide it in the admin interface, and disallow running queries with it)? Note that because concreteness is implemented as part of our keyword hierarchy it is not currently possible to create an abstract driver with a concrete driver as its parent, since it would still ultimately derive from `::concrete`.
(registered? driver)
Is driver
a valid registered driver?
Is `driver` a valid registered driver?
(report-timezone)
(report-timezone new-value)
Connection timezone to use when executing queries. Defaults to system timezone.
report-timezone
is a string Setting. You can get its value by calling:
(report-timezone)
and set its value by calling:
(report-timezone <new-value>)
You can also set its value with the env var MB_REPORT_TIMEZONE
.
Clear its value by calling:
(report-timezone nil)
Its default value is nil
.
Connection timezone to use when executing queries. Defaults to system timezone. `report-timezone` is a string Setting. You can get its value by calling: (report-timezone) and set its value by calling: (report-timezone <new-value>) You can also set its value with the env var `MB_REPORT_TIMEZONE`. Clear its value by calling: (report-timezone nil) Its default value is `nil`.
(splice-parameters-into-native-query driver query)
For a native query that has separate parameters, such as a JDBC prepared statement, e.g.
{:query "SELECT * FROM birds WHERE name = ?", :params ["Reggae"]}
splice the parameters in to the native query as literals so it can be executed by the user, e.g.
{:query "SELECT * FROM birds WHERE name = 'Reggae'"}
This is used to power features such as 'Convert this Question to SQL' in the Query Builder. Normally when executing the query we'd like to leave the statement as a prepared one and pass parameters that way instead of splicing them in as literals so as to avoid SQL injection vulnerabilities. Thus the results of this method are not normally executed by the Query Processor when processing an MBQL query. However when people convert a question to SQL they can see what they will be executing and edit the query as needed.
Input to this function follows the same shape as output of mbql->native
-- that is, it will be a so-called 'inner'
native query, with :query
and :params
keys, as in the example code above; output should be of the same format.
This method might be called even if no splicing needs to take place, e.g. if :params
is empty; implementations
should be sure to handle this situation correctly.
For databases that do not feature concepts like 'prepared statements', this method need not be implemented; the default implementation is an identity function.
For a native query that has separate parameters, such as a JDBC prepared statement, e.g. {:query "SELECT * FROM birds WHERE name = ?", :params ["Reggae"]} splice the parameters in to the native query as literals so it can be executed by the user, e.g. {:query "SELECT * FROM birds WHERE name = 'Reggae'"} This is used to power features such as 'Convert this Question to SQL' in the Query Builder. Normally when executing the query we'd like to leave the statement as a prepared one and pass parameters that way instead of splicing them in as literals so as to avoid SQL injection vulnerabilities. Thus the results of this method are not normally executed by the Query Processor when processing an MBQL query. However when people convert a question to SQL they can see what they will be executing and edit the query as needed. Input to this function follows the same shape as output of `mbql->native` -- that is, it will be a so-called 'inner' native query, with `:query` and `:params` keys, as in the example code above; output should be of the same format. This method might be called even if no splicing needs to take place, e.g. if `:params` is empty; implementations should be sure to handle this situation correctly. For databases that do not feature concepts like 'prepared statements', this method need not be implemented; the default implementation is an identity function.
(supports? driver feature)
Does this driver support a certain feature
? (A feature is a keyword, and can be any of the ones listed above in
driver-features
.)
(supports? :postgres :set-timezone) ; -> true
Does this driver support a certain `feature`? (A feature is a keyword, and can be any of the ones listed above in `driver-features`.) (supports? :postgres :set-timezone) ; -> true
(sync-in-context driver database f)
Drivers may provide this function if they need to do special setup before a sync operation such as
sync-database!
. The sync operation itself is encapsulated as the lambda f
, which must be called with no arguments.
(defn sync-in-context [driver database f] (with-connection [_ database] (f)))
Drivers may provide this function if they need to do special setup before a sync operation such as `sync-database!`. The sync operation itself is encapsulated as the lambda `f`, which must be called with no arguments. (defn sync-in-context [driver database f] (with-connection [_ database] (f)))
(table-rows-seq driver database table)
Return a sequence of all the rows in a given TABLE, which is guaranteed to have at least :name
and :schema
keys. (It is guaranteed to satisfy the DatabaseMetadataTable
schema in metabase.sync.interface
.) Currently, this
is only used for iterating over the values in a _metabase_metadata
table. As such, the results are not expected to
be returned lazily. There is no expectation that the results be returned in any given order.
This method is currently only used by the H2 driver to load the Sample Dataset, so it is not neccesary for any other drivers to implement it at this time.
Return a sequence of *all* the rows in a given TABLE, which is guaranteed to have at least `:name` and `:schema` keys. (It is guaranteed to satisfy the `DatabaseMetadataTable` schema in `metabase.sync.interface`.) Currently, this is only used for iterating over the values in a `_metabase_metadata` table. As such, the results are not expected to be returned lazily. There is no expectation that the results be returned in any given order. This method is currently only used by the H2 driver to load the Sample Dataset, so it is not neccesary for any other drivers to implement it at this time.
(the-driver driver)
Inputs: [driver :- (s/cond-pre s/Str s/Keyword)]
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.
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 (db/insert! Database :engine (name (the-driver driver)))
(the-driver :postgres) ; -> :postgres (the-driver :baby) ; -> Exception
Inputs: [driver :- (s/cond-pre s/Str s/Keyword)] 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. 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 (db/insert! Database :engine (name (the-driver driver))) (the-driver :postgres) ; -> :postgres (the-driver :baby) ; -> Exception
(the-initialized-driver driver)
Like the-driver
, but also initializes the driver if not already initialized.
Like `the-driver`, but also initializes the driver if not already initialized.
(with-driver driver & body)
Bind current driver to driver
and execute body
.
(driver/with-driver :postgres ...)
Bind current driver to `driver` and execute `body`. (driver/with-driver :postgres ...)
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close