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