From b19928448adc5961bd37a8080b271f57756f67e1 Mon Sep 17 00:00:00 2001 From: Serge Klochkov <3175289+slvrtrn@users.noreply.github.com> Date: Fri, 29 Sep 2023 23:54:46 +0200 Subject: [PATCH] Prepare 1.2.2 (#194) * Remove unnecessary CAST as timestamp calls * Disable impersonation due to cluster setup issues * Remove forward slash from serialized IPv4/IPv6 * Move introspection code out of the main file * Bump versions * Update tests * Add DateTimeWithTZ experimental stuff (commented out for now). --- .docker/clickhouse/single_node_tls/Dockerfile | 2 +- CHANGELOG.md | 6 + README.md | 14 +- build_docker_image.sh | 2 +- docker-compose.yml | 4 +- resources/metabase-plugin.yaml | 2 +- src/metabase/driver/clickhouse.clj | 167 ++------------- .../driver/clickhouse_introspection.clj | 157 ++++++++++++++ src/metabase/driver/clickhouse_qp.clj | 74 ++++--- .../driver/clickhouse_base_types_test.clj | 13 +- .../clickhouse_temporal_bucketing_test.clj | 195 ++++++++++++++++++ test/metabase/driver/clickhouse_test.clj | 151 ++++---------- test/metabase/test/data/clickhouse.clj | 37 ++-- test/metabase/test/data/datasets.sql | 20 +- 14 files changed, 510 insertions(+), 334 deletions(-) create mode 100644 src/metabase/driver/clickhouse_introspection.clj create mode 100644 test/metabase/driver/clickhouse_temporal_bucketing_test.clj diff --git a/.docker/clickhouse/single_node_tls/Dockerfile b/.docker/clickhouse/single_node_tls/Dockerfile index a9cc913..12641cd 100644 --- a/.docker/clickhouse/single_node_tls/Dockerfile +++ b/.docker/clickhouse/single_node_tls/Dockerfile @@ -1,4 +1,4 @@ -FROM clickhouse/clickhouse-server:23.5-alpine +FROM clickhouse/clickhouse-server:23.8-alpine COPY .docker/clickhouse/single_node_tls/certificates /etc/clickhouse-server/certs RUN chown clickhouse:clickhouse -R /etc/clickhouse-server/certs \ && chmod 600 /etc/clickhouse-server/certs/* \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dd83b2..3ba3f42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.2.2 + +### Bug fixes +* Removed forward slash from serialized IPv4/IPv6 columns. NB: IPv4/IPv6 columns are temporarily resolved as `type/TextLike` instead of `type/IPAddress` base type due to an unexpected result in Metabase 0.47 type check. +* Removed superfluous CAST calls from generated queries that use Date* columns and/or intervals + # 1.2.1 ### New features * Use HoneySQL2 in the driver diff --git a/README.md b/README.md index 81c353e..993c9e7 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,11 @@ 6. Make sure you are the in the directory where your `metabase.jar` lives. 7. Run `MB_PLUGINS_DIR=./plugins; java -jar metabase.jar`. -For example [(using Metabase v0.45.3 and ClickHouse driver 1.1.2)](#choosing-the-right-version): +For example [(using Metabase v0.47.2 and ClickHouse driver 1.2.2)](#choosing-the-right-version): ```bash export METABASE_VERSION=v0.47.2 -export METABASE_CLICKHOUSE_DRIVER_VERSION=1.2.1 +export METABASE_CLICKHOUSE_DRIVER_VERSION=1.2.2 mkdir -p mb/plugins && cd mb curl -o metabase.jar https://downloads.metabase.com/$METABASE_VERSION/metabase.jar @@ -50,7 +50,7 @@ Alternatively, if you don't want to run Metabase Jar, you can use a Docker image ```bash export METABASE_DOCKER_VERSION=v0.47.2 -export METABASE_CLICKHOUSE_DRIVER_VERSION=1.2.1 +export METABASE_CLICKHOUSE_DRIVER_VERSION=1.2.2 mkdir -p mb/plugins && cd mb curl -L -o plugins/ch.jar https://github.com/ClickHouse/metabase-clickhouse-driver/releases/download/$METABASE_CLICKHOUSE_DRIVER_VERSION/clickhouse.metabase-driver.jar @@ -74,17 +74,17 @@ docker run -d -p 3000:3000 \ | 0.44.x | 0.9.1 | | 0.45.x | 1.1.0 | | 0.46.x | 1.1.7 | -| 0.47.x | 1.2.1 | +| 0.47.x | 1.2.2 | ## Creating a Metabase Docker image with ClickHouse driver You can use a convenience script `build_docker_image.sh`, which takes three arguments: Metabase version, ClickHouse driver version, and the desired final Docker image tag. ```bash -./build_docker_image.sh v0.44.6 0.8.3 my-metabase-with-clickhouse:v0.0.1 +./build_docker_image.sh v0.47.2 1.2.2 my-metabase-with-clickhouse:v0.0.1 ``` -where `v0.44.6` is Metabase version, `0.8.3` is ClickHouse driver version, and `my-metabase-with-clickhouse:v0.0.1` being the tag. +where `v0.47.2` is Metabase version, `1.2.2` is ClickHouse driver version, and `my-metabase-with-clickhouse:v0.0.1` being the tag. Then you should be able to run it: @@ -98,7 +98,7 @@ or use it with Docker compose, for example: version: '3.8' services: clickhouse: - image: 'clickhouse/clickhouse-server:22.10.2-alpine' + image: 'clickhouse/clickhouse-server:23.8-alpine' container_name: 'metabase-clickhouse-server' ports: - '8123:8123' diff --git a/build_docker_image.sh b/build_docker_image.sh index de113a3..9b7620c 100755 --- a/build_docker_image.sh +++ b/build_docker_image.sh @@ -12,7 +12,7 @@ if [ $# -lt 3 ]; then exit 1 fi -export DOWNLOAD_URL="https://github.com/enqueue/metabase-clickhouse-driver/releases/download/$2/clickhouse.metabase-driver.jar" +export DOWNLOAD_URL="https://github.com/ClickHouse/metabase-clickhouse-driver/releases/download/$2/clickhouse.metabase-driver.jar" echo "Downloading the driver from $DOWNLOAD_URL" cd .build diff --git a/docker-compose.yml b/docker-compose.yml index 7f2011e..643ae7a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.8' services: clickhouse: - image: 'clickhouse/clickhouse-server:${CLICKHOUSE_VERSION-23.5-alpine}' + image: 'clickhouse/clickhouse-server:23.8-alpine' container_name: 'metabase-driver-clickhouse-server' ports: - '8123:8123' @@ -32,7 +32,7 @@ services: hostname: server.clickhouseconnect.test metabase: - image: metabase/metabase:v0.47.0-RC2 + image: metabase/metabase:v0.47.2 container_name: metabase-with-clickhouse-driver environment: 'MB_HTTP_TIMEOUT': '5000' diff --git a/resources/metabase-plugin.yaml b/resources/metabase-plugin.yaml index e82bef3..4a8539f 100644 --- a/resources/metabase-plugin.yaml +++ b/resources/metabase-plugin.yaml @@ -1,6 +1,6 @@ info: name: Metabase ClickHouse Driver - version: 1.2.1 + version: 1.2.2 description: Allows Metabase to connect to ClickHouse databases. contact-info: name: ClickHouse diff --git a/src/metabase/driver/clickhouse.clj b/src/metabase/driver/clickhouse.clj index e8619fe..c12bf70 100644 --- a/src/metabase/driver/clickhouse.clj +++ b/src/metabase/driver/clickhouse.clj @@ -1,74 +1,37 @@ (ns metabase.driver.clickhouse "Driver for ClickHouse databases" - #_{:clj-kondo/ignore [:unsorted-required-namespaces]} (:require [clojure.java.jdbc :as jdbc] [clojure.string :as str] - [metabase [config :as config] [driver :as driver] [util :as u]] - [metabase.driver.clickhouse-qp] + [metabase.driver :as driver] + [metabase.driver.clickhouse-introspection] [metabase.driver.clickhouse-nippy] + [metabase.driver.clickhouse-qp] [metabase.driver.ddl.interface :as ddl.i] [metabase.driver.sql :as driver.sql] [metabase.driver.sql-jdbc [common :as sql-jdbc.common] [connection :as sql-jdbc.conn] - [sync :as sql-jdbc.sync]]) - (:import (java.sql DatabaseMetaData))) + [sync :as sql-jdbc.sync]] + [metabase [config :as config]])) (set! *warn-on-reflection* true) (driver/register! :clickhouse :parent :sql-jdbc) -(def ^:private database-type->base-type - (sql-jdbc.sync/pattern-based-database-type->base-type - [[#"Array" :type/Array] - [#"Bool" :type/Boolean] - ;; TODO: test it with :type/DateTimeWithTZ - [#"DateTime64" :type/DateTime] - [#"DateTime" :type/DateTime] - [#"Date" :type/Date] - [#"Date32" :type/Date] - [#"Decimal" :type/Decimal] - [#"Enum8" :type/Text] - [#"Enum16" :type/Text] - [#"FixedString" :type/TextLike] - [#"Float32" :type/Float] - [#"Float64" :type/Float] - [#"Int8" :type/Integer] - [#"Int16" :type/Integer] - [#"Int32" :type/Integer] - [#"Int64" :type/BigInteger] - [#"IPv4" :type/IPAddress] - [#"IPv6" :type/IPAddress] - [#"Map" :type/Dictionary] - [#"String" :type/Text] - [#"Tuple" :type/*] - [#"UInt8" :type/Integer] - [#"UInt16" :type/Integer] - [#"UInt32" :type/Integer] - [#"UInt64" :type/BigInteger] - [#"UUID" :type/UUID]])) - -;; Enum8(UInt8) -> Enum8, DateTime64(Europe/Amsterdam) -> DateTime64, -;; Nullable(DateTime) -> DateTime, SimpleAggregateFunction(sum, Int64) -> Int64, etc -(defn- ^:private normalize-database-type - [database-type] - (let [db-type (subs (str database-type) 1) ;; keyword->str; `name` call does not work well - normalized (second (re-find #"(?:Nullable\(|LowCardinality\()?(\w+)?\({0,1}.*" db-type))] - ;; slightly different normalization for SimpleAggregateFunction - we need to take the second arg - (or (keyword (if (= normalized "SimpleAggregateFunction") - (second (re-find #"SimpleAggregateFunction\(\w+?, {0,1}(.+)?\)" db-type)) - normalized)) - database-type))) ;; basically, fall back to :type/* later +(defmethod driver/display-name :clickhouse [_] "ClickHouse") +(def ^:private product-name "metabase/1.2.2") -(defmethod sql-jdbc.sync/database-type->base-type :clickhouse - [_ database-type] - (database-type->base-type (normalize-database-type database-type))) +(doseq [[feature supported?] {:standard-deviation-aggregations true + :foreign-keys (not config/is-test?) + :set-timezone false + :convert-timezone false + :test/jvm-timezone-setting false + :connection-impersonation false + :schemas true}] -(def ^:private excluded-schemas #{"system" "information_schema" "INFORMATION_SCHEMA"}) -(defmethod sql-jdbc.sync/excluded-schemas :clickhouse [_] excluded-schemas) + (defmethod driver/database-supports? [:clickhouse feature] [_driver _feature _db] supported?)) (def ^:private default-connection-details - {:user "default", :password "", :dbname "default", :host "localhost", :port "8123"}) -(def ^:private product-name "metabase/1.2.1") + {:user "default" :password "" :dbname "default" :host "localhost" :port "8123"}) (defmethod sql-jdbc.conn/connection-details->spec :clickhouse [_ details] @@ -89,104 +52,6 @@ :product_name product-name} (sql-jdbc.common/handle-additional-options details :separator-style :url)))) -(def ^:private allowed-table-types - (into-array String - ["TABLE" "VIEW" "FOREIGN TABLE" "REMOTE TABLE" "DICTIONARY" - "MATERIALIZED VIEW" "MEMORY TABLE" "LOG TABLE"])) - -(defn- tables-set - [tables] - (set - (for [table tables] - (let [remarks (:remarks table)] - {:name (:table_name table) - :schema (:table_schem table) - :description (when-not (str/blank? remarks) remarks)})))) - -(defn- get-tables-from-metadata - [^DatabaseMetaData metadata schema-pattern] - (.getTables metadata - nil ; catalog - unused in the source code there - schema-pattern - "%" ; tablePattern "%" = match all tables - allowed-table-types)) - -(defn ^:private not-inner-mv-table? - [table] - (not (str/starts-with? (:table_name table) ".inner"))) - -(defn- ->spec - [db] - (if (u/id db) - (sql-jdbc.conn/db->pooled-connection-spec db) db)) - -(defn- get-all-tables - [db] - (jdbc/with-db-metadata [metadata (->spec db)] - (->> (get-tables-from-metadata metadata "%") - (jdbc/metadata-result) - (vec) - (filter #(and - (not (contains? excluded-schemas (:table_schem %))) - (not-inner-mv-table? %))) - (tables-set)))) - -;; Strangely enough, the tests only work with :db keyword, -;; but the actual sync from the UI uses :dbname -(defn- get-db-name - [db] - (or (get-in db [:details :dbname]) - (get-in db [:details :db]))) - -(defn- get-tables-in-dbs [db-or-dbs] - (->> (for [db (as-> (or (get-db-name db-or-dbs) "default") dbs - (str/split dbs #" ") - (remove empty? dbs) - (map (comp #(ddl.i/format-name :clickhouse %) str/trim) dbs))] - (jdbc/with-db-metadata [metadata (->spec db-or-dbs)] - (jdbc/metadata-result - (get-tables-from-metadata metadata db)))) - (apply concat) - (filter not-inner-mv-table?) - (tables-set))) - -(defmethod driver/describe-database :clickhouse - [_ {{:keys [scan-all-databases]} - :details :as db}] - {:tables - (if - (boolean scan-all-databases) - (get-all-tables db) - (get-tables-in-dbs db))}) - -(defn- ^:private is-db-required? - [field] - (not (str/starts-with? (get-in field [:database-type]) "Nullable"))) - -(defmethod driver/describe-table :clickhouse - [_ database table] - (let [table-metadata (sql-jdbc.sync/describe-table :clickhouse database table) - filtered-fields (for [field (:fields table-metadata) - :let [updated-field (update-in field [:database-required] - (fn [_] (is-db-required? field)))] - ;; Skip all AggregateFunction (but keeping SimpleAggregateFunction) columns - ;; JDBC does not support that and it crashes the data browser - :when (not (re-matches #"^AggregateFunction\(.+$" - (get field :database-type)))] - updated-field)] - (merge table-metadata {:fields (set filtered-fields)}))) - -(defmethod driver/display-name :clickhouse [_] "ClickHouse") - -(doseq [[feature supported?] {:standard-deviation-aggregations true - :set-timezone false - :foreign-keys (not config/is-test?) - :test/jvm-timezone-setting false - :connection-impersonation true - :schemas true}] - - (defmethod driver/database-supports? [:clickhouse feature] [_driver _feature _db] supported?)) - (defmethod sql-jdbc.sync/db-default-timezone :clickhouse [_ spec] (let [sql (str "SELECT timezone() AS tz") diff --git a/src/metabase/driver/clickhouse_introspection.clj b/src/metabase/driver/clickhouse_introspection.clj new file mode 100644 index 0000000..799ed25 --- /dev/null +++ b/src/metabase/driver/clickhouse_introspection.clj @@ -0,0 +1,157 @@ +(ns metabase.driver.clickhouse-introspection + (:require [clojure.java.jdbc :as jdbc] + [clojure.string :as str] + [metabase.driver :as driver] + [metabase.driver.ddl.interface :as ddl.i] + [metabase.driver.sql-jdbc.connection :as sql-jdbc.conn] + [metabase.driver.sql-jdbc.sync :as sql-jdbc.sync] + [metabase.util :as u]) + (:import (java.sql DatabaseMetaData))) + +(set! *warn-on-reflection* true) + +(def ^:private database-type->base-type + (sql-jdbc.sync/pattern-based-database-type->base-type + [[#"Array" :type/Array] + [#"Bool" :type/Boolean] + [#"DateTime64" :type/DateTime] + [#"DateTime" :type/DateTime] + [#"Date" :type/Date] + [#"Date32" :type/Date] + [#"Decimal" :type/Decimal] + [#"Enum8" :type/Text] + [#"Enum16" :type/Text] + [#"FixedString" :type/TextLike] + [#"Float32" :type/Float] + [#"Float64" :type/Float] + [#"Int8" :type/Integer] + [#"Int16" :type/Integer] + [#"Int32" :type/Integer] + [#"Int64" :type/BigInteger] + ;; FIXME: set it back to IPAddress when 0.48 is out, as it should resolve IPAddress and other semantic types checks issues + [#"IPv4" :type/TextLike] + [#"IPv6" :type/TextLike] + [#"Map" :type/Dictionary] + [#"String" :type/Text] + [#"Tuple" :type/*] + [#"UInt8" :type/Integer] + [#"UInt16" :type/Integer] + [#"UInt32" :type/Integer] + [#"UInt64" :type/BigInteger] + [#"UUID" :type/UUID]])) + +(def ^:private normalize-db-type-regex + #"(?:Nullable\(|LowCardinality\()?(DateTime64\(\d, {0,1}'.*|DateTime\(.*|\w+)?\({0,1}.*") + +(defn- inner-simple-aggregation-base-type + [db-type] + (database-type->base-type + (keyword (second (re-find #"SimpleAggregateFunction\(\w+?, {0,1}(.+)?\)" db-type))))) + +;; Enum8(UInt8) -> :type/Text, DateTime64(Europe/Amsterdam) -> :type/DateTime, +;; Nullable(DateTime) -> :type/DateTime, SimpleAggregateFunction(sum, Int64) -> :type/BigInteger, etc +(defmethod sql-jdbc.sync/database-type->base-type :clickhouse + [_ database-type] + (let [db-type (subs (str database-type) 1) ;; keyword->str; `name` call does not work well + normalized (second (re-find normalize-db-type-regex db-type))] + (or + (cond + ;; slightly different normalization for SimpleAggregateFunction - we need to take the second arg + (= normalized "SimpleAggregateFunction") (inner-simple-aggregation-base-type db-type) + ;; DateTime/DateTime64 - which can be either TIMESTAMP or TIMESTAMP WITH TIME ZONE + (str/starts-with? normalized "DateTime(") :type/DateTime ;; FIXME: should be type/DateTimeWithTZ + (str/starts-with? normalized "DateTime64(") :type/DateTime ;; FIXME: should be type/DateTimeWithTZ + ;; DateTime/DateTime64 without timezone will use just :type/DateTime from this map + ;; other types do not need additional processing as well + :else (database-type->base-type (keyword normalized))) + :type/*))) + +(defmethod sql-jdbc.sync/excluded-schemas :clickhouse [_] + #{"system" "information_schema" "INFORMATION_SCHEMA"}) + +(def ^:private allowed-table-types + (into-array String + ["TABLE" "VIEW" "FOREIGN TABLE" "REMOTE TABLE" "DICTIONARY" + "MATERIALIZED VIEW" "MEMORY TABLE" "LOG TABLE"])) + +(defn- tables-set + [tables] + (set + (for [table tables] + (let [remarks (:remarks table)] + {:name (:table_name table) + :schema (:table_schem table) + :description (when-not (str/blank? remarks) remarks)})))) + +(defn- get-tables-from-metadata + [^DatabaseMetaData metadata schema-pattern] + (.getTables metadata + nil ; catalog - unused in the source code there + schema-pattern + "%" ; tablePattern "%" = match all tables + allowed-table-types)) + +(defn- not-inner-mv-table? + [table] + (not (str/starts-with? (:table_name table) ".inner"))) + +(defn- ->spec + [db] + (if (u/id db) + (sql-jdbc.conn/db->pooled-connection-spec db) db)) + +(defn- get-all-tables + [db] + (jdbc/with-db-metadata [metadata (->spec db)] + (->> (get-tables-from-metadata metadata "%") + (jdbc/metadata-result) + (vec) + (filter #(and + (not (contains? (sql-jdbc.sync/excluded-schemas :clickhouse) (:table_schem %))) + (not-inner-mv-table? %))) + (tables-set)))) + +;; Strangely enough, the tests only work with :db keyword, +;; but the actual sync from the UI uses :dbname +(defn- get-db-name + [db] + (or (get-in db [:details :dbname]) + (get-in db [:details :db]))) + +(defn- get-tables-in-dbs [db-or-dbs] + (->> (for [db (as-> (or (get-db-name db-or-dbs) "default") dbs + (str/split dbs #" ") + (remove empty? dbs) + (map (comp #(ddl.i/format-name :clickhouse %) str/trim) dbs))] + (jdbc/with-db-metadata [metadata (->spec db-or-dbs)] + (jdbc/metadata-result + (get-tables-from-metadata metadata db)))) + (apply concat) + (filter not-inner-mv-table?) + (tables-set))) + +(defmethod driver/describe-database :clickhouse + [_ {{:keys [scan-all-databases]} + :details :as db}] + {:tables + (if + (boolean scan-all-databases) + (get-all-tables db) + (get-tables-in-dbs db))}) + +(defn- ^:private is-db-required? + [field] + (not (str/starts-with? (get-in field [:database-type]) "Nullable"))) + +(defmethod driver/describe-table :clickhouse + [_ database table] + (let [table-metadata (sql-jdbc.sync/describe-table :clickhouse database table) + filtered-fields (for [field (:fields table-metadata) + :let [updated-field (update-in field [:database-required] + (fn [_] (is-db-required? field)))] + ;; Skip all AggregateFunction (but keeping SimpleAggregateFunction) columns + ;; JDBC does not support that and it crashes the data browser + :when (not (re-matches #"^AggregateFunction\(.+$" + (get field :database-type)))] + updated-field)] + (merge table-metadata {:fields (set filtered-fields)}))) diff --git a/src/metabase/driver/clickhouse_qp.clj b/src/metabase/driver/clickhouse_qp.clj index e5772cf..8d2528f 100644 --- a/src/metabase/driver/clickhouse_qp.clj +++ b/src/metabase/driver/clickhouse_qp.clj @@ -30,10 +30,6 @@ (defmethod sql.qp/quote-style :clickhouse [_] :mysql) (defmethod sql.qp/honey-sql-version :clickhouse [_] 2) -(defn- clickhouse-datetime-fn - [fn-name expr] - [fn-name (h2x/->datetime expr)]) - (defmethod sql.qp/date [:clickhouse :day-of-week] [_ _ expr] ;; a tick in the function name prevents HSQL2 to make the function call UPPERCASE @@ -46,66 +42,62 @@ (defmethod sql.qp/date [:clickhouse :minute] [_ _ expr] - (clickhouse-datetime-fn :'toStartOfMinute expr)) + [:'toStartOfMinute expr]) (defmethod sql.qp/date [:clickhouse :minute-of-hour] [_ _ expr] - (clickhouse-datetime-fn :'toMinute expr)) + [:'toMinute expr]) (defmethod sql.qp/date [:clickhouse :hour] [_ _ expr] - (clickhouse-datetime-fn :'toStartOfHour expr)) + [:'toStartOfHour expr]) (defmethod sql.qp/date [:clickhouse :hour-of-day] [_ _ expr] - (clickhouse-datetime-fn :'toHour expr)) + [:'toHour expr]) (defmethod sql.qp/date [:clickhouse :day-of-month] [_ _ expr] - (clickhouse-datetime-fn :'toDayOfMonth expr)) + [:'toDayOfMonth expr]) (defn- to-start-of-week [expr] - ;; ClickHouse weeks usually start on Monday - (clickhouse-datetime-fn :'toMonday expr)) + [:'toMonday expr]) (defn- to-start-of-year [expr] - (clickhouse-datetime-fn :'toStartOfYear expr)) + [:'toStartOfYear expr]) (defn- to-relative-day-num [expr] - (clickhouse-datetime-fn :'toRelativeDayNum expr)) - -(defn- to-day-of-year - [expr] - (h2x/+ (h2x/- (to-relative-day-num expr) - (to-relative-day-num (to-start-of-year expr))) - 1)) + [:'toRelativeDayNum expr]) (defmethod sql.qp/date [:clickhouse :day-of-year] [_ _ expr] - (to-day-of-year expr)) + (h2x/+ + (h2x/- (to-relative-day-num expr) + (to-relative-day-num (to-start-of-year expr))) + 1)) (defmethod sql.qp/date [:clickhouse :week-of-year-iso] [_ _ expr] - (clickhouse-datetime-fn :'toISOWeek expr)) + [:'toISOWeek expr]) (defmethod sql.qp/date [:clickhouse :month] [_ _ expr] - (clickhouse-datetime-fn :'toStartOfMonth expr)) + [:'toStartOfMonth expr]) (defmethod sql.qp/date [:clickhouse :month-of-year] [_ _ expr] - (clickhouse-datetime-fn :'toMonth expr)) + [:'toMonth expr]) (defmethod sql.qp/date [:clickhouse :quarter-of-year] [_ _ expr] - (clickhouse-datetime-fn :'toQuarter expr)) + [:'toQuarter expr]) (defmethod sql.qp/date [:clickhouse :year] [_ _ expr] - (clickhouse-datetime-fn :'toStartOfYear expr)) + [:'toStartOfYear expr]) (defmethod sql.qp/date [:clickhouse :day] [_ _ expr] @@ -117,7 +109,7 @@ (defmethod sql.qp/date [:clickhouse :quarter] [_ _ expr] - (clickhouse-datetime-fn :'toStartOfQuarter expr)) + [:'toStartOfQuarter expr]) (defmethod sql.qp/unix-timestamp->honeysql [:clickhouse :seconds] [_ _ expr] @@ -288,8 +280,7 @@ (defmethod sql.qp/add-interval-honeysql-form :clickhouse [_ dt amount unit] - (h2x/+ (h2x/->timestamp dt) - [:raw (format "INTERVAL %d %s" (int amount) (name unit))])) + (h2x/+ dt [:raw (format "INTERVAL %d %s" (int amount) (name unit))])) ;; The following lines make sure we call lowerUTF8 instead of lower (defn- ch-like-clause @@ -326,14 +317,23 @@ [_ [_ field value options]] (clickhouse-string-fn :'endsWith field value options)) +;; FIXME: there are still many failing tests that prevent us from turning this feature on +;; (defmethod sql.qp/->honeysql [:clickhouse :convert-timezone] +;; [driver [_ arg target-timezone source-timezone]] +;; (let [expr (sql.qp/->honeysql driver (cond-> arg (string? arg) u.date/parse)) +;; with-tz-info? (h2x/is-of-type? expr #"(?:nullable\(|lowcardinality\()?(datetime64\(\d, {0,1}'.*|datetime\(.*)") +;; _ (sql.u/validate-convert-timezone-args with-tz-info? target-timezone source-timezone) +;; inner (if (not with-tz-info?) [:'toTimeZone expr source-timezone] expr)] +;; [:'toTimeZone inner target-timezone])) + ;; We do not have Time data types, so we cheat a little bit (defmethod sql.qp/cast-temporal-string [:clickhouse :Coercion/ISO8601->Time] [_driver _special_type expr] - (h2x/->timestamp [:'parseDateTimeBestEffort [:'concat "1970-01-01T" expr]])) + [:'parseDateTimeBestEffort [:'concat "1970-01-01T" expr]]) (defmethod sql.qp/cast-temporal-byte [:clickhouse :Coercion/ISO8601->Time] [_driver _special_type expr] - (h2x/->timestamp expr)) + expr) (defmethod sql-jdbc.execute/read-column-thunk [:clickhouse Types/TINYINT] [_ ^ResultSet rs ^ResultSetMetaData _ ^Integer i] @@ -370,6 +370,8 @@ (= (.toLocalDate r) (t/local-date 1970 1 1)) (.toLocalTime r) :else r)))) +;; FIXME: should be just (.getObject rs i OffsetDateTime) +;; still blocked by many failing tests (see `sql.qp/->honeysql [:clickhouse :convert-timezone]` as well) (defmethod sql-jdbc.execute/read-column-thunk [:clickhouse Types/TIMESTAMP_WITH_TIMEZONE] [_ ^ResultSet rs ^ResultSetMetaData _ ^Integer i] (fn [] @@ -407,6 +409,18 @@ :else (.asString (ClickHouseArrayValue/of inner))))))) +(defn- ip-column->string + [^ResultSet rs ^Integer i] + (when-let [inet-address (.getObject rs i java.net.InetAddress)] + (.getHostAddress inet-address))) + +(defmethod sql-jdbc.execute/read-column-thunk [:clickhouse Types/VARCHAR] + [_ ^ResultSet rs ^ResultSetMetaData rsmeta ^Integer i] + (fn [] + (cond + (str/starts-with? (.getColumnTypeName rsmeta i) "IPv") (ip-column->string rs i) + :else (.getString rs i)))) + (defmethod unprepare/unprepare-value [:clickhouse LocalDate] [_ t] (format "toDate('%s')" (t/format "yyyy-MM-dd" t))) diff --git a/test/metabase/driver/clickhouse_base_types_test.clj b/test/metabase/driver/clickhouse_base_types_test.clj index b6de5b9..498577e 100644 --- a/test/metabase/driver/clickhouse_base_types_test.clj +++ b/test/metabase/driver/clickhouse_base_types_test.clj @@ -6,11 +6,10 @@ [metabase.test :as mt] [metabase.test.data.clickhouse :as ctd])) -(def ^:private describe-keys [:name :database-type :base-type :database-required]) (defn- desc-table [table-name] - (into #{} (map #(select-keys % describe-keys) - (:fields (ctd/do-with-metabase-test-db + (into #{} (map #(select-keys % [:name :database-type :base-type :database-required]) + (:fields (ctd/do-with-test-db #(driver/describe-table :clickhouse % {:name table-name})))))) (deftest clickhouse-base-types-test @@ -247,11 +246,11 @@ :database-required true, :database-type "UUID", :name "c2"} - {:base-type :type/IPAddress, + {:base-type :type/TextLike, :database-required true, :database-type "IPv4", :name "c3"} - {:base-type :type/IPAddress, + {:base-type :type/TextLike, :database-required true, :database-type "IPv6", :name "c4"} @@ -267,11 +266,11 @@ :database-required false, :database-type "Nullable(UUID)", :name "c7"} - {:base-type :type/IPAddress, + {:base-type :type/TextLike, :database-required false, :database-type "Nullable(IPv4)", :name "c8"} - {:base-type :type/IPAddress, + {:base-type :type/TextLike, :database-required false, :database-type "Nullable(IPv6)", :name "c9"} diff --git a/test/metabase/driver/clickhouse_temporal_bucketing_test.clj b/test/metabase/driver/clickhouse_temporal_bucketing_test.clj new file mode 100644 index 0000000..052d992 --- /dev/null +++ b/test/metabase/driver/clickhouse_temporal_bucketing_test.clj @@ -0,0 +1,195 @@ +(ns metabase.driver.clickhouse-test + #_{:clj-kondo/ignore [:unsorted-required-namespaces]} + (:require + [clojure.test :refer :all] + [metabase.query-processor-test :as qp.test] + [metabase.test :as mt] + [metabase.test.data :as data] + [metabase.test.data.clickhouse :as ctd])) + +;; See temporal_bucketing table definition +;; Fields values are (both in server and column timezones): +;; start_of_year == '2022-01-01 00:00:00' +;; mid_of_year == '2022-06-20 06:32:54' +;; end_of_year == '2022-12-31 23:59:59' +(deftest clickhouse-temporal-bucketing-server-tz + (mt/test-driver + :clickhouse + (defn- start-of-year [unit] + (qp.test/rows + (ctd/do-with-test-db + (fn [db] + (data/with-db db + (data/run-mbql-query + temporal_bucketing_server_tz + {:breakout [[:field %start_of_year {:temporal-unit unit}]]})))))) + (defn- mid-year [unit] + (qp.test/rows + (ctd/do-with-test-db + (fn [db] + (data/with-db db + (data/run-mbql-query + temporal_bucketing_server_tz + {:breakout [[:field %mid_of_year {:temporal-unit unit}]]})))))) + (defn- end-of-year [unit] + (qp.test/rows + (ctd/do-with-test-db + (fn [db] + (data/with-db db + (data/run-mbql-query + temporal_bucketing_server_tz + {:breakout [[:field %end_of_year {:temporal-unit unit}]]})))))) + (testing "truncate to" + (testing "minute" + (is (= [["2022-06-20T06:32:00Z"]] + (mid-year :minute)))) + (testing "hour" + (is (= [["2022-06-20T06:00:00Z"]] + (mid-year :hour)))) + (testing "day" + (is (= [["2022-06-20T00:00:00Z"]] + (mid-year :day)))) + (testing "month" + (is (= [["2022-06-01T00:00:00Z"]] + (mid-year :month)))) + (testing "quarter" + (is (= [["2022-04-01T00:00:00Z"]] + (mid-year :quarter)))) + (testing "year" + (is (= [["2022-01-01T00:00:00Z"]] + (mid-year :year))))) + (testing "extract" + (testing "minute of hour" + (is (= [[0]] + (start-of-year :minute-of-hour))) + (is (= [[32]] + (mid-year :minute-of-hour))) + (is (= [[59]] + (end-of-year :minute-of-hour)))) + (testing "hour of day" + (is (= [[0]] + (start-of-year :hour-of-day))) + (is (= [[6]] + (mid-year :hour-of-day))) + (is (= [[23]] + (end-of-year :hour-of-day)))) + (testing "day of month" + (is (= [[1]] + (start-of-year :day-of-month))) + (is (= [[20]] + (mid-year :day-of-month))) + (is (= [[31]] + (end-of-year :day-of-month)))) + (testing "day of year" + (is (= [[1]] + (start-of-year :day-of-year))) + (is (= [[171]] + (mid-year :day-of-year))) + (is (= [[365]] + (end-of-year :day-of-year)))) + (testing "month of year" + (is (= [[1]] + (start-of-year :month-of-year))) + (is (= [[6]] + (mid-year :month-of-year))) + (is (= [[12]] + (end-of-year :month-of-year)))) + (testing "quarter of year" + (is (= [[1]] + (start-of-year :quarter-of-year))) + (is (= [[2]] + (mid-year :quarter-of-year))) + (is (= [[4]] + (end-of-year :quarter-of-year))))))) + +(deftest clickhouse-temporal-bucketing-column-tz + (mt/test-driver + :clickhouse + (defn- start-of-year [unit] + (qp.test/rows + (ctd/do-with-test-db + (fn [db] + (data/with-db db + (data/run-mbql-query + temporal_bucketing_column_tz + {:breakout [[:field %start_of_year {:temporal-unit unit}]]})))))) + (defn- mid-year [unit] + (qp.test/rows + (ctd/do-with-test-db + (fn [db] + (data/with-db db + (data/run-mbql-query + temporal_bucketing_column_tz + {:breakout [[:field %mid_of_year {:temporal-unit unit}]]})))))) + (defn- end-of-year [unit] + (qp.test/rows + (ctd/do-with-test-db + (fn [db] + (data/with-db db + (data/run-mbql-query + temporal_bucketing_column_tz + {:breakout [[:field %end_of_year {:temporal-unit unit}]]})))))) + (testing "truncate to" + (testing "minute" + ;; it's actually not in UTC as the suffix suggests + ;; however, it is still rendered correctly on the UI + (is (= [["2022-06-20T06:32:00Z"]] + (mid-year :minute)))) + (testing "hour" + (is (= [["2022-06-20T06:00:00Z"]] + (mid-year :hour)))) + (testing "day" + (is (= [["2022-06-20T00:00:00Z"]] + (mid-year :day)))) + (testing "month" + (is (= [["2022-06-01T00:00:00Z"]] + (mid-year :month)))) + (testing "quarter" + (is (= [["2022-04-01T00:00:00Z"]] + (mid-year :quarter)))) + (testing "year" + (is (= [["2022-01-01T00:00:00Z"]] + (mid-year :year))))) + (testing "extract" + (testing "minute of hour" + (is (= [[0]] + (start-of-year :minute-of-hour))) + (is (= [[32]] + (mid-year :minute-of-hour))) + (is (= [[59]] + (end-of-year :minute-of-hour)))) + (testing "hour of day" + (is (= [[0]] + (start-of-year :hour-of-day))) + (is (= [[6]] + (mid-year :hour-of-day))) + (is (= [[23]] + (end-of-year :hour-of-day)))) + (testing "day of month" + (is (= [[1]] + (start-of-year :day-of-month))) + (is (= [[20]] + (mid-year :day-of-month))) + (is (= [[31]] + (end-of-year :day-of-month)))) + (testing "day of year" + (is (= [[1]] + (start-of-year :day-of-year))) + (is (= [[171]] + (mid-year :day-of-year))) + (is (= [[365]] + (end-of-year :day-of-year)))) + (testing "month of year" + (is (= [[1]] + (start-of-year :month-of-year))) + (is (= [[6]] + (mid-year :month-of-year))) + (is (= [[12]] + (end-of-year :month-of-year)))) + (testing "quarter of year" + (is (= [[1]] + (start-of-year :quarter-of-year))) + (is (= [[2]] + (mid-year :quarter-of-year))) + (is (= [[4]] + (end-of-year :quarter-of-year))))))) diff --git a/test/metabase/driver/clickhouse_test.clj b/test/metabase/driver/clickhouse_test.clj index a7254f7..4d1a889 100644 --- a/test/metabase/driver/clickhouse_test.clj +++ b/test/metabase/driver/clickhouse_test.clj @@ -6,7 +6,10 @@ [cljc.java-time.offset-date-time :as offset-date-time] [cljc.java-time.temporal.chrono-unit :as chrono-unit] [clojure.test :refer :all] + [metabase.db.query :as mdb.query] [metabase.driver :as driver] + [metabase.driver.clickhouse-base-types-test] + [metabase.driver.clickhouse-temporal-bucketing-test] [metabase.driver.common :as driver.common] [metabase.driver.sql :as driver.sql] [metabase.driver.sql-jdbc.connection :as sql-jdbc.conn] @@ -19,8 +22,7 @@ [metabase.test.data [interface :as tx]] [metabase.test.data.clickhouse :as ctd] [taoensso.nippy :as nippy] - [toucan2.tools.with-temp :as t2.with-temp] - [metabase.driver.clickhouse-base-types-test])) + [toucan2.tools.with-temp :as t2.with-temp])) (deftest clickhouse-server-timezone (mt/test-driver @@ -416,7 +418,7 @@ (qp.test/formatted-rows [str str str] :format-nil-values - (ctd/do-with-metabase-test-db + (ctd/do-with-test-db (fn [db] (data/with-db db (data/run-mbql-query @@ -427,7 +429,7 @@ (qp.test/formatted-rows [str] :format-nil-values - (ctd/do-with-metabase-test-db + (ctd/do-with-test-db (fn [db] (data/with-db db (data/run-mbql-query @@ -445,7 +447,7 @@ (qp.test/formatted-rows [int] :format-nil-values - (ctd/do-with-metabase-test-db + (ctd/do-with-test-db (fn [db] (data/with-db db (data/run-mbql-query @@ -453,6 +455,17 @@ {:filter [:= $ipvfour "127.0.0.1"] :aggregation [:count]}))))))))) +(deftest clickhouse-ip-serialization-test + (mt/test-driver + :clickhouse + (is (= [["127.0.0.1" "0:0:0:0:0:ffff:7f00:1"] + ["0.0.0.0" "0:0:0:0:0:ffff:0:0"] + [nil nil]] + (qp.test/formatted-rows + [str str] + (ctd/do-with-test-db + (fn [db] (data/with-db db (data/run-mbql-query ipaddress_test {}))))))))) + (defn- map-as-string [^java.util.LinkedHashMap m] (.toString m)) (deftest clickhouse-simple-map-test (mt/test-driver @@ -461,7 +474,7 @@ (qp.test/formatted-rows [map-as-string] :format-nil-values - (ctd/do-with-metabase-test-db + (ctd/do-with-test-db (fn [db] (data/with-db db (data/run-mbql-query @@ -544,7 +557,7 @@ :database-type "SimpleAggregateFunction(sum, Int64)" :base-type :type/BigInteger :database-position 3})}} - (ctd/do-with-metabase-test-db + (ctd/do-with-test-db (fn [db] (driver/describe-table :clickhouse db {:name "aggregate_functions_filter_test"})))))) (testing "from the result set" @@ -552,7 +565,7 @@ (qp.test/formatted-rows [int int int] :format-nil-values - (ctd/do-with-metabase-test-db + (ctd/do-with-test-db (fn [db] (data/with-db db (data/run-mbql-query @@ -624,7 +637,7 @@ (qp.test/formatted-rows [int] :format-nil-values - (ctd/do-with-metabase-test-db + (ctd/do-with-test-db (fn [db] (data/with-db db (data/run-mbql-query @@ -635,7 +648,7 @@ (qp.test/formatted-rows [int] :format-nil-values - (ctd/do-with-metabase-test-db + (ctd/do-with-test-db (fn [db] (data/with-db db (data/run-mbql-query @@ -646,7 +659,7 @@ (qp.test/formatted-rows [double] :format-nil-values - (ctd/do-with-metabase-test-db + (ctd/do-with-test-db (fn [db] (data/with-db db (data/run-mbql-query @@ -657,7 +670,7 @@ (qp.test/formatted-rows [double] :format-nil-values - (ctd/do-with-metabase-test-db + (ctd/do-with-test-db (fn [db] (data/with-db db (data/run-mbql-query @@ -680,107 +693,6 @@ (let [value (com.clickhouse.data.value.UnsignedLong/valueOf "84467440737095")] (is (= value (nippy/thaw (nippy/freeze value)))))))) -(defn- temporal-bucketing-query-start-of-year-field - [unit] - (qp.test/rows - (ctd/do-with-metabase-test-db - (fn [db] - (data/with-db db - (data/run-mbql-query - temporal_bucketing - {:breakout [[:field %start_of_year {:temporal-unit unit}]]})))))) - -(defn- temporal-bucketing-query-mid-year-field - [unit] - (qp.test/rows - (ctd/do-with-metabase-test-db - (fn [db] - (data/with-db db - (data/run-mbql-query - temporal_bucketing - {:breakout [[:field %mid_of_year {:temporal-unit unit}]]})))))) - -(defn- temporal-bucketing-query-end-of-year-field - [unit] - (qp.test/rows - (ctd/do-with-metabase-test-db - (fn [db] - (data/with-db db - (data/run-mbql-query - temporal_bucketing - {:breakout [[:field %end_of_year {:temporal-unit unit}]]})))))) - -;; See temporal_bucketing table definition -;; Fields values are: -;; start_of_year == '2022-01-01 00:00:00' -;; mid_of_year == '2022-06-20 06:32:54' -;; end_of_year == '2022-12-31 23:59:59' -(deftest clickhouse-temporal-bucketing - (mt/test-driver - :clickhouse - (testing "truncate to" - (testing "minute" - (is (= [["2022-06-20T06:32:00Z"]] - (temporal-bucketing-query-mid-year-field :minute)))) - (testing "hour" - (is (= [["2022-06-20T06:00:00Z"]] - (temporal-bucketing-query-mid-year-field :hour)))) - (testing "day" - (is (= [["2022-06-20T00:00:00Z"]] - (temporal-bucketing-query-mid-year-field :day)))) - (testing "month" - (is (= [["2022-06-01T00:00:00Z"]] - (temporal-bucketing-query-mid-year-field :month)))) - (testing "quarter" - (is (= [["2022-04-01T00:00:00Z"]] - (temporal-bucketing-query-mid-year-field :quarter)))) - (testing "year" - (is (= [["2022-01-01T00:00:00Z"]] - (temporal-bucketing-query-mid-year-field :year))))) - (testing "extract" - (testing "minute of hour" - (is (= [[0]] - (temporal-bucketing-query-start-of-year-field :minute-of-hour))) - (is (= [[32]] - (temporal-bucketing-query-mid-year-field :minute-of-hour))) - (is (= [[59]] - (temporal-bucketing-query-end-of-year-field :minute-of-hour)))) - (testing "hour of day" - (is (= [[0]] - (temporal-bucketing-query-start-of-year-field :hour-of-day))) - (is (= [[6]] - (temporal-bucketing-query-mid-year-field :hour-of-day))) - (is (= [[23]] - (temporal-bucketing-query-end-of-year-field :hour-of-day)))) - (testing "day of month" - (is (= [[1]] - (temporal-bucketing-query-start-of-year-field :day-of-month))) - (is (= [[20]] - (temporal-bucketing-query-mid-year-field :day-of-month))) - (is (= [[31]] - (temporal-bucketing-query-end-of-year-field :day-of-month)))) - (testing "day of year" - (is (= [[1]] - (temporal-bucketing-query-start-of-year-field :day-of-year))) - (is (= [[171]] - (temporal-bucketing-query-mid-year-field :day-of-year))) - (is (= [[365]] - (temporal-bucketing-query-end-of-year-field :day-of-year)))) - (testing "month of year" - (is (= [[1]] - (temporal-bucketing-query-start-of-year-field :month-of-year))) - (is (= [[6]] - (temporal-bucketing-query-mid-year-field :month-of-year))) - (is (= [[12]] - (temporal-bucketing-query-end-of-year-field :month-of-year)))) - (testing "quarter of year" - (is (= [[1]] - (temporal-bucketing-query-start-of-year-field :quarter-of-year))) - (is (= [[2]] - (temporal-bucketing-query-mid-year-field :quarter-of-year))) - (is (= [[4]] - (temporal-bucketing-query-end-of-year-field :quarter-of-year))))))) - (deftest clickhouse-set-role (mt/test-driver :clickhouse @@ -807,3 +719,16 @@ (fn [^java.sql.Connection conn] (driver/set-role! :clickhouse conn "asdf"))))))))) +(deftest clickhouse-query-formatting + (mt/test-driver + :clickhouse + (let [query (data/mbql-query venues {:fields [$id] :order-by [[:asc $id]] :limit 5}) + {compiled :query} (qp/compile-and-splice-parameters query) + _pretty (mdb.query/format-sql compiled :clickhouse)] + (testing "compiled" + (is (= "SELECT `test_data`.`venues`.`id` AS `id` FROM `test_data`.`venues` ORDER BY `test_data`.`venues`.`id` ASC LIMIT 5" compiled))) + ;; Ignored due to Metabase bug, see https://github.com/metabase/metabase/issues/34235 + ;; FIXME: uncomment once it is resolved + ;; (testing "pretty" + ;; (is (= "SELECT\n `test_data`.`venues`.`id` AS `id`\nFROM `test_data`.`venues`\nORDER BY\n `test_data`.`venues`.`id` ASC\nLIMIT\n 5" pretty))) + ))) diff --git a/test/metabase/test/data/clickhouse.clj b/test/metabase/test/data/clickhouse.clj index 38fecee..e9a522e 100644 --- a/test/metabase/test/data/clickhouse.clj +++ b/test/metabase/test/data/clickhouse.clj @@ -81,46 +81,47 @@ (defmethod tx/supports-time-type? :clickhouse [_driver] false) -(def default-connection-params {:classname "com.clickhouse.jdbc.ClickHouseDriver" - :subprotocol "clickhouse" - :subname "//localhost:8123/default" - :user "default" - :password "" - :ssl false - :use_no_proxy false - :use_server_time_zone_for_dates true - :product_name "metabase/1.2.1"}) +(def default-connection-params + {:classname "com.clickhouse.jdbc.ClickHouseDriver" + :subprotocol "clickhouse" + :subname "//localhost:8123/default" + :user "default" + :password "" + :ssl false + :use_no_proxy false + :use_server_time_zone_for_dates true + :product_name "metabase/1.2.2"}) (defn rows-without-index "Remove the Metabase index which is the first column in the result set" [query-result] (map #(drop 1 %) (qp.test/rows query-result))) -(defn- metabase-test-db-details +(defn- test-db-details [] {:engine :clickhouse :details (tx/dbdef->connection-details :clickhouse :db {:database-name "metabase_test"})}) -(def db-initialized? (atom false)) -(defn- create-metabase-test-db! +(def test-db-initialized? (atom false)) +(defn- create-test-db! "Create a ClickHouse database called `metabase_test` and initialize some test data" [] (jdbc/with-db-connection - [conn (sql-jdbc.conn/connection-details->spec :clickhouse (metabase-test-db-details))] + [conn (sql-jdbc.conn/connection-details->spec :clickhouse (test-db-details))] (let [statements (as-> (slurp "modules/drivers/clickhouse/test/metabase/test/data/datasets.sql") s (str/split s #";") (map str/trim s) (filter seq s))] (jdbc/db-do-commands conn statements) - (reset! db-initialized? true)))) + (reset! test-db-initialized? true)))) -(defn do-with-metabase-test-db - "Execute a test function using the test dataset from Metabase itself" +(defn do-with-test-db + "Execute a test function using the test dataset" {:style/indent 0} [f] - (when (not @db-initialized?) (create-metabase-test-db!)) + (when (not @test-db-initialized?) (create-test-db!)) (t2.with-temp/with-temp - [Database database (metabase-test-db-details)] + [Database database (test-db-details)] (sync-metadata/sync-db-metadata! database) (f database))) diff --git a/test/metabase/test/data/datasets.sql b/test/metabase/test/data/datasets.sql index b4de740..d98a54b 100644 --- a/test/metabase/test/data/datasets.sql +++ b/test/metabase/test/data/datasets.sql @@ -106,15 +106,29 @@ VALUES (1, 1.1, 'foo'), (4, 5.77, 'bar'); -- Temporal bucketing tests -CREATE TABLE `metabase_test`.`temporal_bucketing` +CREATE TABLE `metabase_test`.`temporal_bucketing_server_tz` ( start_of_year DateTime, mid_of_year DateTime, end_of_year DateTime ) ENGINE = Memory; -INSERT INTO `metabase_test`.`temporal_bucketing` -VALUES ('2022-01-01 00:00:00', '2022-06-20 06:32:54', '2022-12-31 23:59:59'); +INSERT INTO `metabase_test`.`temporal_bucketing_server_tz` +VALUES ('2022-01-01 00:00:00', + '2022-06-20 06:32:54', + '2022-12-31 23:59:59'); + +CREATE TABLE `metabase_test`.`temporal_bucketing_column_tz` +( + start_of_year DateTime('America/Los_Angeles'), + mid_of_year DateTime('America/Los_Angeles'), + end_of_year DateTime('America/Los_Angeles') +) ENGINE = Memory; + +INSERT INTO `metabase_test`.`temporal_bucketing_column_tz` +VALUES (toDateTime('2022-01-01 00:00:00', 'America/Los_Angeles'), + toDateTime('2022-06-20 06:32:54', 'America/Los_Angeles'), + toDateTime('2022-12-31 23:59:59', 'America/Los_Angeles')); DROP DATABASE IF EXISTS `metabase_db_scan_test`; CREATE DATABASE `metabase_db_scan_test`;