Compare commits

...

21 Commits

Author SHA1 Message Date
Cal Herries
1c741adddc + separate tx/supports-base-type-exactly? from tx/supports-base-type-in-definition? 2024-06-21 23:20:38 -06:00
Cal Herries
db54cf19e4 ~ tidy 2024-06-21 21:26:54 -06:00
Cal Herries
0d53e2736f + supports-base-type? 2024-06-21 21:18:12 -06:00
Cal Herries
7ddc4525d3 ~ revert changes to TIMESTAMPTZ handler 2024-06-21 21:14:21 -06:00
Cal Herries
04d9cd7b06 - revert changes to timezones-test and dataset-definitions 2024-06-21 21:11:29 -06:00
Cal Herries
547698a2fd ~ tidy oracle tests 2024-06-21 21:06:59 -06:00
Cal Herries
a72eba04a9 ~ use getNString 2024-06-21 20:42:58 -06:00
Cal Herries
2ef64f572f ~ add oracle tests 2024-06-21 13:10:54 -06:00
Cal Herries
92461b23df + return ZonedDateTime for TIMESTAMPLTZ 2024-06-21 12:48:49 -06:00
Cal Herries
a9c7ea44e8 + restore old oracle behaviour for TIMESTAMPTZ, keeping original offset 2024-06-20 18:05:38 -06:00
Cal Herries
39a943cf38 ~ add oracle test 2024-06-20 18:01:53 -06:00
Cal Herries
23abd17adb ~ add back arglists 2024-06-20 17:53:24 -06:00
Cal Herries
baf86f7da3 + revert changes to config.edn 2024-06-20 17:37:54 -06:00
Cal Herries
a4d15ba6e9 + revert back to .offsetDateTimeValue for TIMESTAMPTZ, and use .timestampValue for TIMESTAMPLTZ 2024-06-20 17:29:27 -06:00
Cal Herries
e1a28fba2a ~ see what happens 2024-06-20 16:33:35 -06:00
Cal Herries
fd124b1a14 ~ fix drop test user 2024-06-20 16:32:48 -06:00
Cal Herries
5dfe03915c ~ fix type hints 2024-06-20 13:42:02 -06:00
Cal Herries
f23d75b06c ~ fix reflection 2024-06-19 21:21:18 -06:00
Cal Herries
2e302014b6 ~ fix reflection 2024-06-19 20:31:08 -06:00
Cal Herries
788b6c37e9 indentation 2024-06-19 20:07:37 -06:00
Cal Herries
6e66e58841 + fix read-column-thunk 2024-06-19 20:01:56 -06:00
11 changed files with 226 additions and 100 deletions

View File

@@ -23,6 +23,7 @@
[metabase.driver.sql.util.unprepare :as unprepare]
[metabase.models.secret :as secret]
[metabase.query-processor.timezone :as qp.timezone]
[metabase.util.date-2 :as u.date]
[metabase.util.honey-sql-2 :as h2x]
[metabase.util.log :as log]
[metabase.util.ssh :as ssh])
@@ -48,43 +49,47 @@
[_ native-form]
(sql.u/format-sql-and-fix-params :plsql native-form))
(def ^:private database-type->base-type
(sql-jdbc.sync/pattern-based-database-type->base-type
[;; Any types -- see http://docs.oracle.com/cd/B28359_01/server.111/b28286/sql_elements001.htm#i107578
[#"ANYDATA" :type/*] ; Instance of a given type with data plus a description of the type (?)
[#"ANYTYPE" :type/*] ; Can be any named SQL type or an unnamed transient type
[#"ARRAY" :type/*]
[#"BFILE" :type/*]
[#"BLOB" :type/*]
[#"RAW" :type/*]
[#"CHAR" :type/Text]
[#"CLOB" :type/Text]
[#"DATE" :type/Date]
[#"DOUBLE" :type/Float]
;; Expression filter type
[#"^EXPRESSION" :type/*]
[#"FLOAT" :type/Float]
;; Does this make sense?
[#"INTERVAL" :type/DateTime]
[#"LONG RAW" :type/*]
[#"LONG" :type/Text]
;; Media types -- http://docs.oracle.com/cd/B28359_01/server.111/b28286/sql_elements001.htm#i121058
[#"^ORD" :type/*]
[#"NUMBER" :type/Decimal]
[#"REAL" :type/Float]
[#"REF" :type/*]
[#"ROWID" :type/*]
;; Spatial types -- see http://docs.oracle.com/cd/B28359_01/server.111/b28286/sql_elements001.htm#i107588
[#"^SDO_" :type/*]
[#"STRUCT" :type/*]
[#"TIMESTAMP(\(\d\))? WITH TIME ZONE" :type/DateTimeWithTZ]
[#"TIMESTAMP" :type/DateTime]
[#"URI" :type/Text]
[#"XML" :type/*]]))
(def ^:private database-type-pattern->base-type
[;; Any types -- see http://docs.oracle.com/cd/B28359_01/server.111/b28286/sql_elements001.htm#i107578
[#"ANYDATA" :type/*] ; Instance of a given type with data plus a description of the type (?)
[#"ANYTYPE" :type/*] ; Can be any named SQL type or an unnamed transient type
[#"ARRAY" :type/*]
[#"BFILE" :type/*]
[#"BLOB" :type/*]
[#"RAW" :type/*]
[#"CHAR" :type/Text]
[#"CLOB" :type/Text]
[#"DATE" :type/Date]
[#"DOUBLE" :type/Float]
;; Expression filter type
[#"^EXPRESSION" :type/*]
[#"FLOAT" :type/Float]
;; Does this make sense?
[#"INTERVAL" :type/DateTime]
[#"LONG RAW" :type/*]
[#"LONG" :type/Text]
;; Media types -- http://docs.oracle.com/cd/B28359_01/server.111/b28286/sql_elements001.htm#i121058
[#"^ORD" :type/*]
[#"INTEGER" :type/Integer]
[#"NUMBER\((\*|\d+),\s*0\)" :type/BigInteger]
[#"NUMBER" :type/Decimal]
[#"DECIMAL" :type/Decimal]
[#"REAL" :type/Float]
[#"REF" :type/*]
[#"ROWID" :type/*]
;; Spatial types -- see http://docs.oracle.com/cd/B28359_01/server.111/b28286/sql_elements001.htm#i107588
[#"^SDO_" :type/*]
[#"STRUCT" :type/*]
[#"TIMESTAMP(\(\d\))? WITH TIME ZONE" :type/DateTimeWithTZ]
[#"TIMESTAMP(\(\d\))? WITH LOCAL TIME ZONE" :type/DateTimeWithLocalTZ]
[#"TIMESTAMP" :type/DateTime]
[#"URI" :type/Text]
[#"XML" :type/*]])
(defmethod sql-jdbc.sync/database-type->base-type :oracle
[_ column-type]
(database-type->base-type column-type))
[_ database-type]
(let [database-type->base-type (sql-jdbc.sync/pattern-based-database-type->base-type database-type-pattern->base-type)]
(database-type->base-type database-type)))
(defn- non-ssl-spec [_details spec host port sid service-name]
(assoc spec :subname (str "@" host
@@ -610,12 +615,16 @@
(when-let [^TIMESTAMPTZ t (.getObject rs i TIMESTAMPTZ)]
(let [^C3P0ProxyConnection proxy-conn (.. rs getStatement getConnection)
conn (.unwrap proxy-conn OracleConnection)]
;; TIMEZONE FIXME - we need to warn if the Oracle JDBC driver is `ojdbc7.jar`, which probably won't have this
;; method
;;
;; I think we can call `(oracle.jdbc.OracleDriver/getJDBCVersion)` and check whether it returns 4.2+
(.offsetDateTimeValue t conn)))))
(defmethod sql-jdbc.execute/read-column-thunk [:oracle OracleTypes/TIMESTAMPLTZ]
[_driver ^ResultSet rs _rsmeta ^Integer i]
;; `.offsetDateTimeValue` with TIMESTAMPLTZ returns the incorrect value with daylight savings time, so instead of
;; trusting Oracle to get time zones right we assume the string value from `.getNString` is correct and is in a format
;; we can parse.
(fn []
(u.date/parse (.getNString rs i))))
(defmethod unprepare/unprepare-value [:oracle OffsetDateTime]
[_ t]
;; Oracle doesn't like `Z` to mean UTC

View File

@@ -30,6 +30,7 @@
[metabase.test.data.sql :as sql.tx]
[metabase.test.data.sql.ddl :as ddl]
[metabase.util :as u]
[metabase.util.date-2 :as u.date]
[metabase.util.honey-sql-2 :as h2x]
[metabase.util.log :as log]
[toucan2.core :as t2]
@@ -461,3 +462,41 @@
(testing "Oracle should strip double quotes and null characters from identifiers"
(is (= "ABC_D_E__FG_H"
(driver/escape-alias :oracle "ABC\"D\"E\"\u0000FG\u0000H")))))
(deftest read-timestamp-with-tz-test
(mt/test-driver :oracle
(testing "We should return TIMESTAMP WITH TIME ZONE columns correctly"
(let [test-date "2024-12-20 16:05:00 US/Eastern"
sql (format "SELECT TIMESTAMP '%s' AS timestamp_tz FROM dual" test-date)
query (-> (mt/native-query {:query sql})
(assoc-in [:middleware :format-rows?] false))]
(doseq [report-tz ["UTC" "US/Pacific"]]
(mt/with-temporary-setting-values [report-timezone report-tz]
(mt/with-native-query-testing-context query
(testing "The value should come back from driver with original zone info, regardless of report timezone"
(= (t/offset-date-time (u.date/parse test-date) "US/Eastern")
(ffirst (mt/rows (qp/process-query query))))))))))))
(deftest read-timestamp-with-local-tz-test
(mt/test-driver :oracle
(testing "We should return TIMESTAMP WITH LOCAL TIME ZONE columns correctly"
(let [test-date "2024-12-20 16:05:00 US/Pacific"
sql (format "SELECT CAST(TIMESTAMP '%s' AS TIMESTAMP WITH LOCAL TIME ZONE) AS timestamp_tz FROM dual" test-date)
query (-> (mt/native-query {:query sql})
(assoc-in [:middleware :format-rows?] false))
spec #(sql-jdbc.conn/db->pooled-connection-spec (mt/db))
old-format (-> (jdbc/query (spec) "SELECT value FROM NLS_SESSION_PARAMETERS WHERE PARAMETER = 'NLS_TIMESTAMP_TZ_FORMAT'")
ffirst
val)
do-with-temp-tz-format (fn [thunk]
;; verify that the value is independent of NLS_TIMESTAMP_TZ_FORMAT
(jdbc/execute! (spec) "ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH:MI'")
(thunk)
(jdbc/execute! (spec) (format "ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = '%s'" old-format)))]
(do-with-temp-tz-format
(fn []
(doseq [report-tz ["UTC" "US/Pacific"]]
(mt/with-temporary-setting-values [report-timezone report-tz]
(mt/with-native-query-testing-context query
(is (= (t/zoned-date-time (u.date/parse test-date) report-tz)
(ffirst (mt/rows (qp/process-query query))))))))))))))

View File

@@ -74,7 +74,6 @@
(doseq [[base-type sql-type] {:type/BigInteger "NUMBER(*,0)"
:type/Boolean "NUMBER(1)"
:type/Date "DATE"
:type/Temporal "TIMESTAMP"
:type/DateTime "TIMESTAMP"
:type/DateTimeWithTZ "TIMESTAMP WITH TIME ZONE"
:type/DateTimeWithLocalTZ "TIMESTAMP WITH LOCAL TIME ZONE"
@@ -86,11 +85,6 @@
:type/Text "VARCHAR2(4000)"}]
(defmethod sql.tx/field-base-type->sql-type [:oracle base-type] [_ _] sql-type))
;; If someone tries to run Time column tests with Oracle give them a heads up that Oracle does not support it
(defmethod sql.tx/field-base-type->sql-type [:oracle :type/Time]
[_ _]
(throw (UnsupportedOperationException. "Oracle does not have a TIME data type.")))
(defmethod sql.tx/drop-table-if-exists-sql :oracle
[_ {:keys [database-name]} {:keys [table-name]}]
;; ⅋ replaced with `;` in the actual executed SQL; `;` itself is automatically removed Missing IN or OUT parameter

View File

@@ -31,25 +31,23 @@
;; extensions
;; Time, UUID types aren't supported by redshift
(doseq [[base-type database-type] {:type/BigInteger "BIGINT"
:type/Boolean "BOOL"
:type/Date "DATE"
:type/DateTime "TIMESTAMP"
:type/Decimal "DECIMAL"
:type/Float "FLOAT8"
:type/Integer "INTEGER"
(doseq [[base-type database-type] {:type/BigInteger "BIGINT"
:type/Boolean "BOOL"
:type/Date "DATE"
:type/DateTime "TIMESTAMP"
:type/DateTimeWithLocalTZ "TIMESTAMP WITH TIME ZONE"
:type/Time "TIME"
:type/TimeWithLocalTZ "TIME WITH TIME ZONE"
:type/Decimal "DECIMAL"
:type/Float "FLOAT8"
:type/Integer "INTEGER"
;; Use VARCHAR because TEXT in Redshift is VARCHAR(256)
;; https://docs.aws.amazon.com/redshift/latest/dg/r_Character_types.html#r_Character_types-varchar-or-character-varying
;; But don't use VARCHAR(MAX) either because of performance impact
;; https://docs.aws.amazon.com/redshift/latest/dg/c_best-practices-smallest-column-size.html
:type/Text "VARCHAR(1024)"}]
:type/Text "VARCHAR(1024)"}]
(defmethod sql.tx/field-base-type->sql-type [:redshift base-type] [_ _] database-type))
;; If someone tries to run Time column tests with Redshift give them a heads up that Redshift does not support it
(defmethod sql.tx/field-base-type->sql-type [:redshift :type/Time]
[_ _]
(throw (UnsupportedOperationException. "Redshift does not have a TIME data type.")))
(defn unique-session-schema []
(str (sql.tu.unique-prefix/unique-prefix) "schema"))

View File

@@ -37,10 +37,6 @@
:type/Text "STRING"}]
(defmethod sql.tx/field-base-type->sql-type [:sparksql base-type] [_ _] database-type))
;; If someone tries to run Time column tests with SparkSQL give them a heads up that SparkSQL does not support it
(defmethod sql.tx/field-base-type->sql-type [:sparksql :type/Time] [_ _]
(throw (UnsupportedOperationException. "SparkSQL does not have a TIME data type.")))
(defmethod ddl.i/format-name :sparksql
[_ s]
(str/replace s #"-" "_"))

View File

@@ -431,21 +431,17 @@
:exclusive (add t resolution -1)))}
:= (range t unit options))))
;; Moving the type hints to the arg lists makes clj-kondo happy, but breaks eastwood (and maybe causes reflection
;; warnings) at the call sites.
#_{:clj-kondo/ignore [:non-arg-vec-return-type-hint]}
(defn ^PeriodDuration period-duration
(defn period-duration
"Return the Duration between two temporal values `x` and `y`."
{:arglists '([s] [period] [duration] [period duration] [start end])}
([x]
(^PeriodDuration [x]
(when x
(condp instance? x
PeriodDuration x
CharSequence (PeriodDuration/parse x)
Period (PeriodDuration/of ^Period x)
Duration (PeriodDuration/of ^Duration x))))
([x y]
(^PeriodDuration [x y]
(cond
(and (instance? Period x) (instance? Duration y))
(PeriodDuration/of x y)

View File

@@ -4,6 +4,7 @@
[clojure.string :as str]
[clojure.test :refer :all]
[java-time.api :as t]
[medley.core :as m]
[metabase.driver :as driver]
[metabase.driver.sql.query-processor :as sql.qp]
[metabase.driver.util :as driver.u]
@@ -11,6 +12,7 @@
[metabase.query-processor :as qp]
[metabase.query-processor.store :as qp.store]
[metabase.test :as mt]
[metabase.test.data.interface :as tx]
[metabase.test.data.sql :as sql.tx]
[metabase.util.date-2 :as u.date]))
@@ -207,8 +209,8 @@
"True if the current distinguishes between two base types when loading data in test datasets.
TODO — how is this supposed to work for MongoDB?"
[base-type-1 base-type-2]
(not= (sql.tx/field-base-type->sql-type driver/*driver* base-type-1)
(sql.tx/field-base-type->sql-type driver/*driver* base-type-2)))
(not= (sql.tx/base-type->sql-type driver/*driver* base-type-1)
(sql.tx/base-type->sql-type driver/*driver* base-type-2)))
(defn- supports-time-with-time-zone? [] (driver-distinguishes-between-base-types? :type/TimeWithTZ :type/Time))
(defn- supports-time-with-offset? [] (driver-distinguishes-between-base-types? :type/TimeWithZoneOffset :type/TimeWithTZ))
@@ -230,10 +232,30 @@
(when (supports-datetime-with-zone-id?)
{:datetime_tz_id (t/zoned-date-time "2019-11-01T00:23:18.331-07:00[America/Los_Angeles]")})))
(deftest sql-datetime-timezone-handling-test
(mt/test-drivers (filter #(isa? driver/hierarchy % :sql) (mt/normal-drivers-with-feature :set-timezone))
(mt/dataset attempted-murders
(let [original-value #t "2019-11-01T00:23:18.331-07:00[America/Los_Angeles]"]
(doseq [timezone ["UTC" "US/Pacific" "US/Eastern" "Asia/Hong_Kong"]]
(mt/with-temporary-setting-values [report-timezone timezone]
(let [supported-type? {:datetime_ltz (tx/supports-base-type-exactly? driver/*driver* :type/DateTimeWithLocalTZ)
;; TODO: extend dataset to include both base types
:datetime_tz (or (tx/supports-base-type-exactly? driver/*driver* :type/DateTimeWithZoneOffset)
(tx/supports-base-type-exactly? driver/*driver* :type/DateTimeWithTZ))
:datetime_tz_id (tx/supports-base-type-exactly? driver/*driver* :type/DateTimeWithZoneID)}
results (attempts)]
(doseq [[field-name read-value] (m/filter-keys supported-type? results)]
(testing (str field-name '" should have an offset or time zone")
(is (some #(isa? (type read-value) %) [java.time.OffsetDateTime java.time.ZonedDateTime])))
(testing (str field-name '" should be the same instant as the inserted value")
(is (= (t/instant original-value)
(t/instant read-value))))))))))))
(deftest sql-time-timezone-handling-test
;; Actual value : "2019-11-01T00:23:18.331-07:00[America/Los_Angeles]"
;; Oracle doesn't have a time type
(mt/test-drivers (filter #(isa? driver/hierarchy % :sql) (set-timezone-drivers))
(mt/test-drivers (filter #(and (isa? driver/hierarchy % :sql)
(tx/supports-base-type-exactly? driver/*driver* :type/Time))
(mt/normal-drivers-with-feature :set-timezone))
(mt/dataset attempted-murders
(doseq [timezone [nil "US/Pacific" "US/Eastern" "Asia/Hong_Kong"]]
(mt/with-temporary-setting-values [report-timezone timezone]

View File

@@ -34,19 +34,22 @@
(There are several variations of this macro; see documentation below for more details.)"
(:require
[clojure.test :as t]
[clojure.test]
[colorize.core :as colorize]
[java-time.api :as t]
[mb.hawk.init]
[metabase.db :as mdb]
[metabase.db.schema-migrations-test.impl
:as schema-migrations-test.impl]
[metabase.driver :as driver]
[metabase.driver.ddl.interface :as ddl.i]
[metabase.models.permissions-group :as perms-group]
[metabase.query-processor :as qp]
[metabase.test.data.impl :as data.impl]
[metabase.test.data.interface :as tx]
[metabase.test.data.mbql-query-impl :as mbql-query-impl]
[metabase.util.malli :as mu]))
[metabase.util.malli :as mu]
[toucan2.core :as t2]))
(set! *warn-on-reflection* true)
@@ -243,10 +246,10 @@
(data/dataset (get-dataset-definition) ...)"
{:style/indent 1}
[dataset & body]
`(t/testing (colorize/magenta ~(str (if (symbol? dataset)
(format "using %s dataset" dataset)
"using inline dataset")
\newline))
`(clojure.test/testing (colorize/magenta ~(str (if (symbol? dataset)
(format "using %s dataset" dataset)
"using inline dataset")
\newline))
(data.impl/do-with-dataset ~(if (and (symbol? dataset)
(not (get &env dataset)))
`(data.impl/resolve-dataset-definition '~(ns-name *ns*) '~dataset)
@@ -274,3 +277,39 @@
perms-group/admin (#'perms-group/magic-group perms-group/admin-group-name)]
(mdb/setup-db! :create-sample-content? false) ; skip sample content for speedy tests. this doesn't reflect production
~@body)))
(tx/defdataset every-base-type
"A dataset for testing every base-type. This should be used primarily for implementing `dataset-base-types`."
[["everything"
[{:field-name "date", :base-type :type/Date}
{:field-name "datetime", :base-type :type/DateTime}
{:field-name "datetime_tz", :base-type :type/DateTimeWithTZ}
{:field-name "datetime_ltz", :base-type :type/DateTimeWithLocalTZ}
{:field-name "datetime_tz_zo", :base-type :type/DateTimeWithZoneOffset}
{:field-name "datetime_tz_id", :base-type :type/DateTimeWithZoneID}
{:field-name "time", :base-type :type/Time}
{:field-name "time_ltz", :base-type :type/TimeWithLocalTZ}
{:field-name "time_tz", :base-type :type/TimeWithZoneOffset}
{:field-name "num_crows", :base-type :type/Integer}]
(for [[cnt t] [[6 #t "2019-11-01T00:23:18.331-07:00[America/Los_Angeles]"]]]
[(t/local-date t) ; date
(t/local-date-time t) ; datetime
(t/offset-date-time t) ; datetime-tz
(t/offset-date-time t) ; datetime-ltz
(t/offset-date-time t) ; datetime-tz-zo
t ; datetime-tz-id
(t/local-time t) ; time
(t/offset-time t) ; time-ltz
(t/offset-time t) ; time-tz
cnt])]])
(defn dataset-base-types
"Returns the set of base types available to create in a dataset."
[driver]
(driver/with-driver driver
(dataset every-base-type
(t2/select-fn-set :base_type :model/Field))))
(defmethod tx/supports-base-type-exactly? ::tx/test-extensions
[driver base-type]
(contains? (dataset-base-types driver) base-type))

View File

@@ -402,6 +402,20 @@
(defmethod sorts-nil-first? ::test-extensions [_ _] true)
(defmulti supports-base-type-in-definition?
"Whether this driver supports creating a field in a field definition with `base-type`. Note this does not guarantee
the field will have the `base-type` when synced."
{:arglists '([driver base-type])}
dispatch-on-driver-with-test-extensions
:hierarchy #'driver/hierarchy)
(defmulti supports-base-type-exactly?
"Whether this driver supports columns in the test database that when synced, they will have `base-type`."
{:arglists '([driver base-type])}
dispatch-on-driver-with-test-extensions
:hierarchy #'driver/hierarchy)
;; TODO: replace this with supports-base-type?
(defmulti supports-time-type?
"Whether this database supports a `TIME` data type or equivalent."
{:arglists '([driver])}
@@ -410,6 +424,7 @@
(defmethod supports-time-type? ::test-extensions [_driver] true)
;; TODO: replace this with supports-base-type?
(defmulti supports-timestamptz-type?
"Whether this database supports a `timestamp with time zone` data type or equivalent."
{:arglists '([driver])}

View File

@@ -9,6 +9,7 @@
[metabase.query-processor.compile :as qp.compile]
[metabase.test.data :as data]
[metabase.test.data.interface :as tx]
[metabase.util :as u]
[metabase.util.log :as log]))
(comment metabase.driver.sql/keep-me)
@@ -142,25 +143,31 @@
;;; +----------------------------------------------------------------------------------------------------------------+
(defmulti field-base-type->sql-type
"Return a native SQL type that should be used for fields of `base-type`."
"Return a native SQL type that should be used for fields of `base-type`.
Should be implemented for a `base-type` if the driver supports creating columns that when synced, they will have
`base-type`."
{:arglists '([driver base-type])}
(fn [driver base-type] [(tx/dispatch-on-driver-with-test-extensions driver) base-type])
:hierarchy #'driver/hierarchy)
(defmethod field-base-type->sql-type :default
(defn base-type->sql-type
"Like [[field-base-type->sql-type]], but uses the type hierarchy to fall back to an ancestor base type if there is
no exact match for the base-type."
[driver base-type]
(or (some
(fn [ancestor-type]
(when-not (= ancestor-type :type/*)
(when-let [method (get (methods field-base-type->sql-type) [driver ancestor-type])]
(log/infof "No test data type mapping for driver %s for base type %s, falling back to ancestor base type %s"
driver base-type ancestor-type)
(method driver base-type))))
(ancestors base-type))
(throw
(Exception.
(format "No test data type mapping for driver %s for base type %s; add an impl for field-base-type->sql-type."
driver base-type)))))
(or
(u/ignore-exceptions (field-base-type->sql-type driver base-type))
(some
(fn [ancestor-type]
(when-not (= ancestor-type :type/*)
(when-let [sql-type (u/ignore-exceptions (field-base-type->sql-type driver ancestor-type))]
(log/infof "No test data type mapping for driver %s for base type %s, falling back to ancestor base type %s"
driver base-type ancestor-type)
sql-type)))
(ancestors base-type))))
(defmethod tx/supports-base-type-in-definition? :sql/test-extensions
[driver base-type]
(some? (base-type->sql-type driver base-type)))
(defmulti pk-sql-type
"SQL type of a primary key field."
@@ -187,7 +194,8 @@
(format "DROP DATABASE IF EXISTS %s;" (qualify-and-quote driver database-name)))
(defmulti create-table-sql
"Return a `CREATE TABLE` statement."
"Return a `CREATE TABLE` statement. If `base-type` is not supported by the driver,
create the column as if had a base-type of `type/Text`."
{:arglists '([driver dbdef tabledef])}
tx/dispatch-on-driver-with-test-extensions
:hierarchy #'driver/hierarchy)
@@ -227,8 +235,12 @@
(and (map? base-type) (contains? base-type :natives))
(get-in base-type [:natives driver])
;; if the base type is not supported in a dataset definition, use the text type
(false? (tx/supports-base-type-in-definition? driver base-type))
(base-type->sql-type driver :type/Text)
base-type
(field-base-type->sql-type driver base-type))
(base-type->sql-type driver base-type))
(throw (ex-info (format "Missing datatype for field %s for driver: %s"
field-name driver)
{:field field-definition

View File

@@ -96,13 +96,19 @@
;;; -------------------------------- Making a load-data! impl with make-load-data-fn ---------------------------------
(defn load-data-get-rows
"Used by `make-load-data-fn`; get a sequence of row maps for use in a `insert!` when loading table data."
[_driver _dbdef tabledef]
(let [fields-for-insert (mapv (comp keyword :field-name)
(:field-definitions tabledef))]
"Used by `make-load-data-fn`; get a sequence of row maps for use in a `insert!` when loading table data.
If the driver supports the base type of a field, the value is used as is. Otherwise, the value is set to nil."
[driver _dbdef tabledef]
(let [field-defs (:field-definitions tabledef)]
;; TIMEZONE FIXME
(for [row (:rows tabledef)]
(zipmap fields-for-insert row))))
(into {}
(map (fn [[field-def row-value]]
(let [k (keyword (:field-name field-def))
v (when (tx/supports-base-type-in-definition? driver (:base-type field-def))
row-value)]
[k v])))
(map vector field-defs row)))))
(defn- make-insert!
"Used by `make-load-data-fn`; creates the actual `insert!` function that gets passed to the `insert-middleware-fns`