Shared definitions and helper functions for use across different drivers. | (ns metabase.driver.common (:require [clojure.string :as str] [metabase.driver :as driver] [metabase.models.setting :as setting] [metabase.premium-features.core :as premium-features] [metabase.public-settings :as public-settings] [metabase.util.i18n :refer [deferred-tru]] [metabase.util.log :as log] [metabase.util.malli :as mu])) |
(set! *warn-on-reflection* true) | |
TODO - we should rename these from | |
Map of the db host details field, useful for | (def default-host-details
{:name "host"
:display-name (deferred-tru "Host")
:helper-text (deferred-tru "Your database''s IP address (e.g. 98.137.149.56) or its domain name (e.g. esc.mydatabase.com).")
:placeholder "name.database.com"}) |
Map of the db port details field, useful for | (def default-port-details
{:name "port"
:display-name (deferred-tru "Port")
:type :integer}) |
Map of the db user details field, useful for | (def default-user-details
{:name "user"
:display-name (deferred-tru "Username")
:placeholder (deferred-tru "username")
:required true}) |
Map of the db password details field, useful for | (def default-password-details
{:name "password"
:display-name (deferred-tru "Password")
:type :password
:placeholder "••••••••"}) |
Map of the db name details field, useful for | (def default-dbname-details
{:name "dbname"
:display-name (deferred-tru "Database name")
:placeholder (deferred-tru "birds_of_the_world")
:required true}) |
Map of the db ssl details field, useful for | (def default-ssl-details
{:name "ssl"
:display-name (deferred-tru "Use a secure connection (SSL)")
:type :boolean
:default false}) |
Map of the db | (def additional-options
{:name "additional-options"
:display-name (deferred-tru "Additional JDBC connection string options")
:visible-if {"advanced-options" true}}) |
Configuration parameters to include in the add driver page on drivers that support ssh tunnels | (def ssh-tunnel-preferences
[{:name "tunnel-enabled"
:display-name (deferred-tru "Use an SSH tunnel")
:placeholder (deferred-tru "Enable this SSH tunnel?")
:type :boolean
:default false}
{:name "tunnel-host"
:display-name (deferred-tru "SSH tunnel host")
:helper-text (deferred-tru "The hostname that you use to connect to SSH tunnels.")
:placeholder "hostname"
:required true
:visible-if {"tunnel-enabled" true}}
{:name "tunnel-port"
:display-name (deferred-tru "SSH tunnel port")
:type :integer
:default 22
:required false
:visible-if {"tunnel-enabled" true}}
{:name "tunnel-user"
:display-name (deferred-tru "SSH tunnel username")
:helper-text (deferred-tru "The username you use to login to your SSH tunnel.")
:placeholder "username"
:required true
:visible-if {"tunnel-enabled" true}}
;; this is entirely a UI flag
{:name "tunnel-auth-option"
:display-name (deferred-tru "SSH Authentication")
:type :select
:options [{:name (deferred-tru "SSH Key") :value "ssh-key"}
{:name (deferred-tru "Password") :value "password"}]
:default "ssh-key"
:visible-if {"tunnel-enabled" true}}
{:name "tunnel-pass"
:display-name (deferred-tru "SSH tunnel password")
:type :password
:placeholder "******"
:visible-if {"tunnel-auth-option" "password"}}
{:name "tunnel-private-key"
:display-name (deferred-tru "SSH private key to connect to the tunnel")
:type :string
:placeholder (deferred-tru "Paste the contents of an SSH private key here")
:required true
:visible-if {"tunnel-auth-option" "ssh-key"}}
{:name "tunnel-private-key-passphrase"
:display-name (deferred-tru "Passphrase for SSH private key")
:type :password
:placeholder "******"
:visible-if {"tunnel-auth-option" "ssh-key"}}]) |
Map representing the start of the advanced option section in a DB connection form. Fields in this section should
have their visibility controlled using the | (def advanced-options-start
{:name "advanced-options"
:type :section
:default false}) |
Map representing the | (def auto-run-queries
{:name "auto_run_queries"
:type :boolean
:default true
:display-name (deferred-tru "Rerun queries for simple explorations")
:description (deferred-tru
(str "We execute the underlying query when you explore data using Summarize or Filter. "
"This is on by default but you can turn it off if performance is slow."))
:visible-if {"advanced-options" true}}) |
Map representing the | (def let-user-control-scheduling
{:name "let-user-control-scheduling"
:type :boolean
:display-name (deferred-tru "Choose when syncs and scans happen")
:description (deferred-tru "By default, Metabase does a lightweight hourly sync and an intensive daily scan of field values. If you have a large database, turn this on to make changes.")
:visible-if {"advanced-options" true}}) |
Map representing the | (def metadata-sync-schedule
{:name "schedules.metadata_sync"
:display-name (deferred-tru "Database syncing")
:description (deferred-tru
(str "This is a lightweight process that checks for updates to this database’s schema. "
"In most cases, you should be fine leaving this set to sync hourly."))
:visible-if {"let-user-control-scheduling" true}}) |
Map representing the | (def cache-field-values-schedule
{:name "schedules.cache_field_values"
:display-name (deferred-tru "Scanning for Filter Values")
:description (deferred-tru
(str "Metabase can scan the values present in each field in this database to enable checkbox "
"filters in dashboards and questions. This can be a somewhat resource-intensive process, "
"particularly if you have a very large database. When should Metabase automatically scan "
"and cache field values?"))
:visible-if {"let-user-control-scheduling" true}}) |
Map representing the | (def json-unfolding
{:name "json-unfolding"
:display-name (deferred-tru "Allow unfolding of JSON columns")
:type :boolean
:visible-if {"advanced-options" true}
:description (deferred-tru
(str "This enables unfolding JSON columns into their component fields. "
"Disable unfolding if performance is slow. If enabled, you can still disable unfolding for "
"individual fields in their settings."))
:default true}) |
Map representing the | (def refingerprint
{:name "refingerprint"
:type :boolean
:display-name (deferred-tru "Periodically refingerprint tables")
:description (deferred-tru
(str "This enables Metabase to scan for additional field values during syncs allowing smarter "
"behavior, like improved auto-binning on your bar charts."))
:visible-if {"advanced-options" true}}) |
Vector containing the three most common options present in the advanced option section of the DB connection form. | (def default-advanced-options [auto-run-queries let-user-control-scheduling metadata-sync-schedule cache-field-values-schedule refingerprint]) |
Default options listed above, keyed by name. These keys can be listed in the plugin manifest to specify connection properties for drivers shipped as separate modules, e.g.: connection-properties: - db-name - host See the plugin manifest reference for more details. | (def default-options
{:dbname default-dbname-details
:host default-host-details
:password default-password-details
:port default-port-details
:ssl default-ssl-details
:user default-user-details
:ssh-tunnel ssh-tunnel-preferences
:additional-options additional-options
:advanced-options-start advanced-options-start
:default-advanced-options default-advanced-options}) |
Map of the | (def cloud-ip-address-info
{:name "cloud-ip-address-info"
:type :info
:getter (fn []
(when-let [ips (public-settings/cloud-gateway-ips)]
(str (deferred-tru
(str "If your database is behind a firewall, you may need to allow connections from our Metabase "
"[Cloud IP addresses](https://www.metabase.com/cloud/docs/ip-addresses-to-whitelist.html):"))
"\n"
(str/join " - " ips))))}) |
Default definitions for informational banners that can be included in a database connection form. These keys can be
added to the plugin manifest as connection properties, similar to the keys in the | (def default-connection-info-fields
{:cloud-ip-address-info cloud-ip-address-info}) |
Options for using an auth provider instead of a literal password. | (def auth-provider-options
[{:name "use-auth-provider"
:type :checked-section
:check (fn []
(and
;; Managed Identities only make sense if Metabase is in the same cloud as the DW
(not (premium-features/is-hosted?))
(premium-features/enable-database-auth-providers?)))
:default false}
{:name "auth-provider"
:display-name (deferred-tru "Auth provider")
:type :select
:options [{:name (deferred-tru "Azure Managed Identity")
:value "azure-managed-identity"}
{:name (deferred-tru "OAuth")
:value "oauth"}]
:default "azure-managed-identity"
:visible-if {"use-auth-provider" true}}
{:name "azure-managed-identity-client-id"
:display-name (deferred-tru "Client ID")
:required true
:visible-if {"auth-provider" "azure-managed-identity"}}
{:name "oauth-token-url"
:display-name (deferred-tru "Auth token URL")
:required true
:visible-if {"auth-provider" "oauth"}}
{:name "oauth-token-headers"
:display-name (deferred-tru "Auth token request headers (a JSON map)")
:visible-if {"auth-provider" "oauth"}}]) |
+----------------------------------------------------------------------------------------------------------------+ | Class -> Base Type | +----------------------------------------------------------------------------------------------------------------+ | |
Return the | (defn class->base-type
[klass]
(condp #(isa? %2 %1) klass
Boolean :type/Boolean
Double :type/Float
Float :type/Float
Integer :type/Integer
Long :type/Integer
java.math.BigDecimal :type/Decimal
java.math.BigInteger :type/BigInteger
Number :type/Number
String :type/Text
;; java.sql types should be considered DEPRECATED
java.sql.Date :type/Date
java.sql.Timestamp :type/DateTime
java.util.Date :type/Date
java.util.UUID :type/UUID
clojure.lang.IPersistentMap :type/Dictionary
clojure.lang.IPersistentVector :type/Array
java.time.LocalDate :type/Date
java.time.LocalTime :type/Time
java.time.LocalDateTime :type/DateTime
;; `OffsetTime` and `OffsetDateTime` should be mapped to one of `type/TimeWithLocalTZ`/`type/TimeWithZoneOffset`
;; and `type/DateTimeWithLocalTZ`/`type/DateTimeWithZoneOffset` respectively. We can't really tell how they're
;; stored in the DB based on class alone, so drivers should return more specific types where possible. See
;; discussion in the `metabase.types` namespace.
java.time.OffsetTime :type/TimeWithTZ
java.time.OffsetDateTime :type/DateTimeWithTZ
java.time.ZonedDateTime :type/DateTimeWithZoneID
java.time.Instant :type/Instant
;; TODO - this should go in the Postgres driver implementation of this method rather than here
org.postgresql.util.PGobject :type/*
;; all-NULL columns in DBs like Mongo w/o explicit types
nil :type/*
(do
(log/warnf "Don't know how to map class '%s' to a Field base_type, falling back to :type/*." klass)
:type/*))) |
Number of result rows to sample when when determining base type. | (def ^:private column-info-sample-size 100) |
Transducer that given a sequence of | (defn values->base-type
[]
((comp (filter some?) (take column-info-sample-size) (map class))
(fn
([]
(doto (java.util.HashMap.)
(.put nil 0))) ; fallback to keep `max-key` happy if no values
([^java.util.HashMap freqs, klass]
(.put freqs klass (inc (.getOrDefault freqs klass 0)))
freqs)
([freqs]
(->> freqs
(apply max-key val)
key
class->base-type))))) |
(def ^:private ^clojure.lang.PersistentVector days-of-week [:monday :tuesday :wednesday :thursday :friday :saturday :sunday]) | |
Used to override the [[metabase.public-settings/start-of-week]] settings. Primarily being used to calculate week-of-year in US modes where the start-of-week is always Sunday. More in (defmethod date [:sql :week-of-year-us]). | (def ^:dynamic *start-of-week* nil) |
(mu/defn start-of-week->int :- [:int {:min 0, :max 6, :error/message "Start of week integer"}]
"Returns the int value for the current [[metabase.public-settings/start-of-week]] Setting value, which ranges from
`0` (`:monday`) to `6` (`:sunday`). This is guaranteed to return a value."
{:added "0.42.0"}
[]
(.indexOf days-of-week (or *start-of-week* (setting/get-value-of-type :keyword :start-of-week)))) | |
Like [[start-of-week-offset]] but takes a | (defn start-of-week-offset-for-day
[start-of-week]
(let [db-start-of-week (.indexOf days-of-week start-of-week)
target-start-of-week (start-of-week->int)
delta (int (- target-start-of-week db-start-of-week))]
(* (Integer/signum delta)
(- 7 (Math/abs delta))))) |
(mu/defn start-of-week-offset :- :int "Return the offset needed to adjust a day of the week (in the range 1..7) returned by the `driver`, with `1` corresponding to [[driver/db-start-of-week]], so that `1` corresponds to [[metabase.public-settings/start-of-week]] in results. e.g. If `:my-driver` returns [[driver/db-start-of-week]] as `:sunday` (1 is Sunday, 2 is Monday, and so forth), and [[metabase.public-settings/start-of-week]] is `:monday` (the results should have 1 as Monday, 2 as Tuesday... 7 is Sunday), then the offset should be `-1`, because `:monday` returned by the driver (`2`) minus `1` = `1`." [driver] (start-of-week-offset-for-day (driver/db-start-of-week driver))) | |
Returns true if JSON fields should be unfolded by default for this database, and false otherwise. | (defn json-unfolding-default
[database]
;; This allows adding support for nested-field-columns for drivers in the future and
;; have json-unfolding enabled by default, without
;; needing a migration to add the `json-unfolding=true` key to the database details.
(let [json-unfolding (get-in database [:details :json-unfolding])]
(if (nil? json-unfolding)
true
json-unfolding))) |