Code owners
Assign users and groups as approvers for specific file changes. Learn more.
alternative_date_test.clj 15.82 KiB
(ns metabase.query-processor-test.alternative-date-test
"Tests for columns that mimic dates: integral types as UNIX timestamps and string columns as ISO8601DateTimeString and
related types."
(:require [clojure.test :refer :all]
[metabase.driver :as driver]
[metabase.driver.sql-jdbc.test-util :as sql-jdbc.tu]
[metabase.driver.sql.query-processor :as sql.qp]
[metabase.query-processor :as qp]
[metabase.query-processor-test :as qp.test]
[metabase.test :as mt]
[metabase.util :as u])
(:import java.time.OffsetDateTime))
(deftest semantic-type->unix-timestamp-unit-test
(testing "every descendant of `:Coercion/UNIXTime->Temporal` has a unit associated with it"
(doseq [semantic-type (descendants :Coercion/UNIXTime->Temporal)]
(is (sql.qp/semantic-type->unix-timestamp-unit semantic-type))))
(testing "throws if argument is not a descendant of `:Coercion/UNIXTime->Temporal`"
(is (thrown-with-msg?
clojure.lang.ExceptionInfo
#"Semantic type must be a UNIXTimestamp"
(sql.qp/semantic-type->unix-timestamp-unit :type/Integer)))))
(mt/defdataset toucan-microsecond-incidents
[["incidents" [{:field-name "severity"
:base-type :type/Integer}
{:field-name "timestamp"
:base-type :type/BigInteger
:effective-type :type/DateTime
:coercion-strategy :Coercion/UNIXMicroSeconds->DateTime}]
[[4 1433587200000000]
[0 1433965860000000]]]])
(deftest microseconds-test
(mt/test-drivers (disj (mt/normal-drivers) :sqlite)
(let [results (get {:sqlite #{[1 4 "2015-06-06 10:40:00"] [2 0 "2015-06-10 19:51:00"]}
:oracle #{[1M 4M "2015-06-06T10:40:00Z"] [2M 0M "2015-06-10T19:51:00Z"]}}
driver/*driver*
;; default result shape
#{[1 4 "2015-06-06T10:40:00Z"] [2 0 "2015-06-10T19:51:00Z"]})]
(is (= results
(set (mt/rows (mt/dataset toucan-microsecond-incidents
(mt/run-mbql-query incidents)))))))))
(deftest filter-test
(mt/test-drivers (mt/normal-drivers)
(mt/dataset sad-toucan-incidents
(let [query (mt/mbql-query incidents
{:filter [:= [:datetime-field $timestamp :day] "2015-06-02"]
:order-by [[:asc $timestamp]]})]
;; There's a race condition with this test. If we happen to grab a connection that is in a session with the
;; timezone set to pacific, we'll get 9 results even when the above if statement is true. It seems to be pretty
;; rare, but explicitly specifying UTC will make the issue go away
(mt/with-temporary-setting-values [report-timezone "UTC"]
(testing "There were 10 'sad toucan incidents' on 2015-06-02 in UTC"
(mt/with-native-query-testing-context query
(is (= 10
(count (mt/rows (qp/process-query query))))))))))))
(deftest results-test
(mt/test-drivers (mt/normal-drivers)
(is (= (cond
(= :sqlite driver/*driver*)
[["2015-06-01" 6]
["2015-06-02" 10]
["2015-06-03" 4]
["2015-06-04" 9]
["2015-06-05" 9]
["2015-06-06" 8]
["2015-06-07" 8]
["2015-06-08" 9]
["2015-06-09" 7]
["2015-06-10" 9]]
(qp.test/tz-shifted-driver-bug? driver/*driver*)
[["2015-06-01T00:00:00-07:00" 6]
["2015-06-02T00:00:00-07:00" 10]
["2015-06-03T00:00:00-07:00" 4]
["2015-06-04T00:00:00-07:00" 9]
["2015-06-05T00:00:00-07:00" 9]
["2015-06-06T00:00:00-07:00" 8]
["2015-06-07T00:00:00-07:00" 8]
["2015-06-08T00:00:00-07:00" 9]
["2015-06-09T00:00:00-07:00" 7]
["2015-06-10T00:00:00-07:00" 9]]
(qp.test/supports-report-timezone? driver/*driver*)
[["2015-06-01T00:00:00-07:00" 8]
["2015-06-02T00:00:00-07:00" 9]
["2015-06-03T00:00:00-07:00" 9]
["2015-06-04T00:00:00-07:00" 4]
["2015-06-05T00:00:00-07:00" 11]
["2015-06-06T00:00:00-07:00" 8]
["2015-06-07T00:00:00-07:00" 6]
["2015-06-08T00:00:00-07:00" 10]
["2015-06-09T00:00:00-07:00" 6]
["2015-06-10T00:00:00-07:00" 10]]
:else
[["2015-06-01T00:00:00Z" 6]
["2015-06-02T00:00:00Z" 10]
["2015-06-03T00:00:00Z" 4]
["2015-06-04T00:00:00Z" 9]
["2015-06-05T00:00:00Z" 9]
["2015-06-06T00:00:00Z" 8]
["2015-06-07T00:00:00Z" 8]
["2015-06-08T00:00:00Z" 9]
["2015-06-09T00:00:00Z" 7]
["2015-06-10T00:00:00Z" 9]])
(mt/with-temporary-setting-values [report-timezone "America/Los_Angeles"]
(->> (mt/dataset sad-toucan-incidents
(mt/run-mbql-query incidents
{:aggregation [[:count]]
:breakout [$timestamp]
:limit 10}))
mt/rows (mt/format-rows-by [identity int])))))))
(deftest substitute-native-parameters-test
(mt/test-drivers (mt/normal-drivers-with-feature :native-parameters)
(testing "Make sure `:date/range` SQL field filters work correctly with UNIX timestamps (#11934)"
(mt/dataset tupac-sightings
(let [query (mt/native-query
(merge (mt/count-with-field-filter-query driver/*driver* :sightings :timestamp)
(mt/$ids sightings
{:template-tags {"timestamp" {:name "timestamp"
:display-name "Sighting Timestamp"
:type :dimension
:dimension $timestamp
:widget-type :date/range}}
:parameters [{:type :date/range
:target [:dimension [:template-tag "timestamp"]]
:value "2014-02-01~2015-02-29"}]})))]
(testing (format "\nquery = %s" (u/pprint-to-str query))
(is (= [[41]]
(mt/formatted-rows [int]
(qp/process-query query))))))))))
;;; :type/ISO8601DateTimeString tests
(mt/defdataset just-dates
[["just_dates" [{:field-name "name"
:base-type :type/Text
:effective-type :type/Text}
{:field-name "ts"
:base-type :type/Text
:effective-type :type/DateTime
:coercion-strategy :Coercion/ISO8601->DateTime}
{:field-name "d"
:base-type :type/Text
:effective-type :type/Date
:coercion-strategy :Coercion/ISO8601->Date}]
[["foo" "2004-10-19 10:23:54" "2004-10-19"]
["bar" "2008-10-19 10:23:54" "2008-10-19"]
["baz" "2012-10-19 10:23:54" "2012-10-19"]]]])
(mt/defdataset string-times
[["times" [{:field-name "name"
:effective-type :type/Text
:base-type :type/Text}
{:field-name "ts"
:base-type :type/Text
:effective-type :type/DateTime
:coercion-strategy :Coercion/ISO8601->DateTime}
{:field-name "d"
:base-type :type/Text
:effective-type :type/Date
:coercion-strategy :Coercion/ISO8601->Date}
{:field-name "t"
:base-type :type/Text
:effective-type :type/Time
:coercion-strategy :Coercion/ISO8601->Time}]
[["foo" "2004-10-19 10:23:54" "2004-10-19" "10:23:54"]
["bar" "2008-10-19 10:23:54" "2008-10-19" "10:23:54"]
["baz" "2012-10-19 10:23:54" "2012-10-19" "10:23:54"]]]])
(deftest iso-8601-text-fields
(testing "text fields with semantic_type :type/ISO8601DateTimeString"
(testing "return as dates"
(mt/test-drivers (disj (sql-jdbc.tu/sql-jdbc-drivers) :sqlite :oracle :sparksql)
(is (= [[1 "foo" #t "2004-10-19T10:23:54" #t "2004-10-19" #t "10:23:54"]
[2 "bar" #t "2008-10-19T10:23:54" #t "2008-10-19" #t "10:23:54"]
[3 "baz" #t "2012-10-19T10:23:54" #t "2012-10-19" #t "10:23:54"]]
;; string-times dataset has three text fields, ts, d, t for timestamp, date, and time
(mt/rows (mt/dataset string-times
(qp/process-query
(assoc (mt/mbql-query times)
:middleware {:format-rows? false})))))))
(testing "sparksql adds UTC"
(mt/test-drivers #{:sparksql}
(is (= #{[1 "foo" #t "2004-10-19T10:23:54Z[UTC]" #t "2004-10-19T00:00Z[UTC]"]
[3 "baz" #t "2012-10-19T10:23:54Z[UTC]" #t "2012-10-19T00:00Z[UTC]"]
[2 "bar" #t "2008-10-19T10:23:54Z[UTC]" #t "2008-10-19T00:00Z[UTC]"]}
;; order seems to be nondeterministic
(set (mt/rows (mt/dataset just-dates
(qp/process-query
(assoc (mt/mbql-query just-dates)
:middleware {:format-rows? false})))))))))
(testing "oracle doesn't have a time type"
(mt/test-drivers #{:oracle}
(is (= [[1M "foo" #t "2004-10-19T10:23:54" #t "2004-10-19T00:00"]
[2M "bar" #t "2008-10-19T10:23:54" #t "2008-10-19T00:00"]
[3M "baz" #t "2012-10-19T10:23:54" #t "2012-10-19T00:00"]]
;; string-times dataset has three text fields, ts, d, t for timestamp, date, and time
(mt/rows (mt/dataset just-dates
(qp/process-query
(assoc (mt/mbql-query just-dates)
:middleware {:format-rows? false}))))))))
(testing "sqlite returns as strings"
(mt/test-drivers #{:sqlite}
(is (= [[1 "foo" "2004-10-19 10:23:54" "2004-10-19" "10:23:54"]
[2 "bar" "2008-10-19 10:23:54" "2008-10-19" "10:23:54"]
[3 "baz" "2012-10-19 10:23:54" "2012-10-19" "10:23:54"]]
;; string-times dataset has three text fields, ts, d, t for timestamp, date, and time
(mt/rows (mt/dataset string-times
(qp/process-query
(assoc (mt/mbql-query times)
:middleware {:format-rows? false}))))))))
(testing "bigquery adds UTC"
(mt/test-drivers #{:bigquery-cloud-sdk}
(is (= [[1 "foo" #t "2004-10-19T10:23:54Z[UTC]" #t "2004-10-19T00:00Z[UTC]" #t "10:23:54"]
[2 "bar" #t "2008-10-19T10:23:54Z[UTC]" #t "2008-10-19T00:00Z[UTC]" #t "10:23:54"]
[3 "baz" #t "2012-10-19T10:23:54Z[UTC]" #t "2012-10-19T00:00Z[UTC]" #t "10:23:54"]]
;; string-times dataset has three text fields, ts, d, t for timestamp, date, and time
(mt/rows (mt/dataset string-times
(qp/process-query
(assoc (mt/mbql-query times)
:middleware {:format-rows? false})))))))))
(testing "are queryable as dates"
(testing "a datetime field"
;; TODO: why does this fail on oracle? gives a NPE
(mt/test-drivers (disj (sql-jdbc.tu/sql-jdbc-drivers) :oracle :sparksql)
(is (= 1
(count (mt/rows (mt/dataset string-times
(mt/run-mbql-query times
{:filter [:= [:datetime-field $ts :day] "2008-10-19"]}))))))))
(testing "a date field"
(mt/test-drivers (disj (sql-jdbc.tu/sql-jdbc-drivers) :oracle :sparksql)
(is (= 1
(count (mt/rows (mt/dataset string-times
(mt/run-mbql-query times
{:filter [:= [:datetime-field $d :day] "2008-10-19"]})))))))))))
(mt/defdataset yyyymmddhhss-times
[["times" [{:field-name "name"
:effective-type :type/Text
:base-type :type/Text}
{:field-name "as_text"
:base-type :type/Text
:effective-type :type/DateTime
:coercion-strategy :Coercion/YYYYMMDDHHMMSSString->Temporal}]
[["foo" "20190421164300"]
["bar" "20200421164300"]
["baz" "20210421164300"]]]])
(mt/defdataset yyyymmddhhss-binary-times
[["times" [{:field-name "name"
:effective-type :type/Text
:base-type :type/Text}
{:field-name "as_bytes"
:base-type {:natives {:postgres "BYTEA"
:h2 "BYTEA"
:mysql "VARBINARY(100)"}}
:effective-type :type/DateTime
:coercion-strategy :Coercion/YYYYMMDDHHMMSSBytes->Temporal}]
[["foo" (.getBytes "20190421164300")]
["bar" (.getBytes "20200421164300")]
["baz" (.getBytes "20210421164300")]]]])
(deftest yyyymmddhhmmss-binary-dates
(mt/test-drivers #{:postgres :h2 :mysql}
(is (= (case driver/*driver*
:postgres
[[1 "foo" (OffsetDateTime/from #t "2019-04-21T16:43Z")]
[2 "bar" (OffsetDateTime/from #t "2020-04-21T16:43Z")]
[3 "baz" (OffsetDateTime/from #t "2021-04-21T16:43Z")]]
(:h2 :mysql :sqlserver)
[[1 "foo" #t "2019-04-21T16:43"]
[2 "bar" #t "2020-04-21T16:43"]
[3 "baz" #t "2021-04-21T16:43"]]
[])
(sort-by
first
(mt/rows (mt/dataset yyyymmddhhss-binary-times
(qp/process-query
(assoc (mt/mbql-query times)
:middleware {:format-rows? false})))))))))
(deftest yyyymmddhhmmss-dates
(mt/test-drivers #{:mongo :oracle :postgres :h2 :mysql :bigquery-cloud-sdk :snowflake :redshift :sqlserver :presto}
(is (= (case driver/*driver*
:mongo
[[1 "foo" (.toInstant #t "2019-04-21T16:43:00Z")]
[2 "bar" (.toInstant #t "2020-04-21T16:43:00Z")]
[3 "baz" (.toInstant #t "2021-04-21T16:43:00Z")]]
(:h2 :mysql :sqlserver)
[[1 "foo" #t "2019-04-21T16:43"]
[2 "bar" #t "2020-04-21T16:43"]
[3 "baz" #t "2021-04-21T16:43"]]
(:bigquery-cloud-sdk :redshift :presto)
[[1 "foo" #t "2019-04-21T16:43Z[UTC]"]
[2 "bar" #t "2020-04-21T16:43Z[UTC]"]
[3 "baz" #t "2021-04-21T16:43Z[UTC]"]]
:postgres
[[1 "foo" (OffsetDateTime/from #t "2019-04-21T16:43Z")]
[2 "bar" (OffsetDateTime/from #t "2020-04-21T16:43Z")]
[3 "baz" (OffsetDateTime/from #t "2021-04-21T16:43Z")]]
:oracle
[[1M "foo" #t "2019-04-21T16:43"]
[2M "bar" #t "2020-04-21T16:43"]
[3M "baz" #t "2021-04-21T16:43"]]
:snowflake
[[1 "foo" #t "2609-10-23T10:19:24.300"]
[2 "bar" #t "2610-02-16T04:06:04.300"]
[3 "baz" #t "2610-06-11T21:52:44.300"]])
;; string-times dataset has three text fields, ts, d, t for timestamp, date, and time
(sort-by
first
(mt/rows (mt/dataset yyyymmddhhss-times
(qp/process-query
(assoc (mt/mbql-query times)
:middleware {:format-rows? false})))))))))