diff --git a/project.clj b/project.clj index a8e81fdd03b8c780125e85a696ebfc2a157e1fbd..94e1048316d66fde5047c13aafbe35255f35fd66 100644 --- a/project.clj +++ b/project.clj @@ -64,13 +64,13 @@ [org.yaml/snakeyaml "1.17"] ; YAML parser (required by liquibase) [org.xerial/sqlite-jdbc "3.8.11.2"] ; SQLite driver [postgresql "9.3-1102.jdbc41"] ; Postgres driver + [io.crate/crate-jdbc "1.11.0"] ; Crate JDBC driver + [io.crate/crate-client "0.54.7"] ; Crate Java client (used by Crate JDBC) [prismatic/schema "1.1.1"] ; Data schema declaration and validation library [ring/ring-jetty-adapter "1.4.0"] ; Ring adapter using Jetty webserver (used to run a Ring server for unit tests) [ring/ring-json "0.4.0"] ; Ring middleware for reading/writing JSON automatically [stencil "0.5.0"] ; Mustache templates for Clojure - [swiss-arrows "1.0.0"] ; 'Magic wand' macro -<>, etc. - [io.crate/crate-jdbc "1.11.0"] - [io.crate/crate-client "0.54.7"]] + [swiss-arrows "1.0.0"]] ; 'Magic wand' macro -<>, etc. :repositories [["bintray" "https://dl.bintray.com/crate/crate"]] :plugins [[lein-environ "1.0.2"] ; easy access to environment variables [lein-ring "0.9.7" ; start the HTTP server with 'lein ring server' diff --git a/src/metabase/driver/crate.clj b/src/metabase/driver/crate.clj index 7a48b03bc74e316399327e77a26da1324da5af59..a0bc3d81790b231538615b8b7c71ceb75ee9c19a 100644 --- a/src/metabase/driver/crate.clj +++ b/src/metabase/driver/crate.clj @@ -59,7 +59,7 @@ (dissoc opts :hosts))) (defn- connection-details->spec [_ details] - (-> details crate-spec)) + (crate-spec details)) (defn- can-connect [driver details] (let [connection (connection-details->spec driver details)] diff --git a/src/metabase/driver/crate/native.clj b/src/metabase/driver/crate/native.clj index 3b00feb46169ec0e33d2d59e08b0caed3213e1bf..f06f5a7da42a0487e269cb883b0ceb36089bea8d 100644 --- a/src/metabase/driver/crate/native.clj +++ b/src/metabase/driver/crate/native.clj @@ -13,16 +13,16 @@ (try (let [database (sel :one :fields [Database :engine :details] :id database-id) db-conn (sql/db->jdbc-connection-spec database)] (jdbc/with-db-connection [t-conn db-conn] - (let [^java.sql.Connection jdbc-connection (:connection t-conn)] - (try - ;; Now run the query itself - (log/debug (u/format-color 'green "%s" sql)) - (let [[columns & [first-row :as rows]] (jdbc/query t-conn sql, :as-arrays? true)] - {:rows rows - :columns columns - :cols (for [[column first-value] (partition 2 (interleave columns first-row))] - {:name column - :base_type (n/value->base-type first-value)})}))))) + (let [^java.sql.Connection jdbc-connection (:connection t-conn)] + (try + ;; Now run the query itself + (log/debug (u/format-color 'green "%s" sql)) + (let [[columns & [first-row :as rows]] (jdbc/query t-conn sql, :as-arrays? true)] + {:rows rows + :columns columns + :cols (for [[column first-value] (partition 2 (interleave columns first-row))] + {:name column + :base_type (n/value->base-type first-value)})}))))) (catch java.sql.SQLException e (let [^String message (or (->> (.getMessage e) ; error message comes back like 'Column "ZID" not found; SQL statement: ... [error-code]' sometimes (re-find #"^(.*);") ; the user already knows the SQL, and error code is meaningless diff --git a/src/metabase/driver/crate/query_processor.clj b/src/metabase/driver/crate/query_processor.clj index df88e8adb706f6bb90e46e290a39432c3aa04577..a818c5800773de6d89fa096353afb57a93ab3457 100644 --- a/src/metabase/driver/crate/query_processor.clj +++ b/src/metabase/driver/crate/query_processor.clj @@ -2,14 +2,15 @@ (:require [korma.core :as k] [metabase.driver.generic-sql.query-processor :as qp] [korma.sql.fns :as kfns] - [korma.sql.engine :as kengine]) + [korma.sql.engine :as kengine] + [metabase.query-processor.interface :as i]) (:import (metabase.query_processor.interface ComparisonFilter CompoundFilter))) (defn- rewrite-between "Rewrite [:between <field> <min> <max>] -> [:and [:>= <field> <min>] [:<= <field> <max>]]" [clause] - (CompoundFilter. :and [(ComparisonFilter. :>= (:field clause) (:min-val clause)) - (ComparisonFilter. :<= (:field clause) (:max-val clause))])) + (i/strict-map->CompoundFilter {:compound-type :and :subclauses [(ComparisonFilter. :>= (:field clause) (:min-val clause)) + (ComparisonFilter. :<= (:field clause) (:max-val clause))]})) (defn resolve-subclauses "resolve filters recursively" diff --git a/src/metabase/driver/crate/util.clj b/src/metabase/driver/crate/util.clj index 5bd2bcbb5fa2d9c7f8b04de962d6aadeea87928c..dd73dd8d9036f4cd50d35a3a0754f3243e734b65 100644 --- a/src/metabase/driver/crate/util.clj +++ b/src/metabase/driver/crate/util.clj @@ -3,7 +3,7 @@ [korma.sql.utils :as kutils] [korma.core :as k] [metabase.util :as u] - [metabase.driver :as driver]) + [metabase.driver.generic-sql.query-processor :as qp]) (:import (java.sql Timestamp))) (defn unix-timestamp->timestamp [_ expr seconds-or-milliseconds] @@ -14,30 +14,35 @@ (defn- date-trunc [unit expr] "date_trunc('interval', timezone, timestamp): truncates a timestamp to a given interval" - (if (or (nil? (driver/report-timezone)) (= (count (driver/report-timezone)) 0)) - (k/sqlfn :DATE_TRUNC (kx/literal unit) expr) - (k/sqlfn :DATE_TRUNC (kx/literal unit) (driver/report-timezone) expr))) + (let [timezone (get-in qp/*query* [:settings :report-timezone])] + (if (= (nil? timezone) true) + (k/sqlfn :DATE_TRUNC (kx/literal unit) expr) + (k/sqlfn :DATE_TRUNC (kx/literal unit) timezone expr)))) (defn- date-format [format expr] "date_format('format_string', timezone, timestamp): formats the timestamp as string" - (if (or (nil? (driver/report-timezone)) (= (count (driver/report-timezone)) 0)) - (k/sqlfn :DATE_FORMAT format expr) - (k/sqlfn :DATE_FORMAT format (driver/report-timezone) expr))) + (let [timezone (get-in qp/*query* [:settings :report-timezone])] + (if (nil? timezone) + (k/sqlfn :DATE_FORMAT format expr) + (k/sqlfn :DATE_FORMAT format timezone expr)))) (defn- extract [unit expr] "extract(field from expr): extracts subfields of a timestamp" - (kutils/func (format "EXTRACT(%s FROM %%s)" (name unit)) [expr])) + (case unit + ;; Crate DOW starts with Monday (1) to Sunday (7) + :day_of_week (kx/+ (kx/mod (kutils/func (format "EXTRACT(%s FROM %%s)" (name unit)) [expr]) 7) 1) + (kutils/func (format "EXTRACT(%s FROM %%s)" (name unit)) [expr]))) (def ^:private extract-integer (comp kx/->integer extract)) -(def ^:private YEAR (constantly 31536000000)) -(def ^:private MONTH (constantly 2628000000)) -(def ^:private WEEK (constantly 604800000)) -(def ^:private DAY (constantly 86400000)) -(def ^:private HOUR (constantly 3600000)) -(def ^:private MINUTE (constantly 60000)) -(def ^:private SECOND (constantly 1000)) +(def ^:private ^:const second 1000) +(def ^:private ^:const minute (* 60 second)) +(def ^:private ^:const hour (* 60 minute)) +(def ^:private ^:const day (* 24 hour)) +(def ^:private ^:const week (* 7 day)) +(def ^:private ^:const year (* 365 day)) +(def ^:private ^:const month (Math/round (float (/ year 12)))) (defn date [_ unit expr] (let [v (if (instance? Timestamp expr) @@ -54,7 +59,8 @@ :day-of-week (extract-integer :day_of_week v) :day-of-month (extract-integer :day_of_month v) :day-of-year (extract-integer :day_of_year v) - :week (date-format (str "%Y-%m-%d") (date-trunc :week v)) + ;; Crate weeks start on Monday, so shift this date into the proper bucket and then decrement the resulting day + :week (date-format (str "%Y-%m-%d") (kx/- (date-trunc :week (kx/+ v day)) day)) :week-of-year (extract-integer :week v) :month (date-format (str "%Y-%m-%d") (date-trunc :month v)) :month-of-year (extract-integer :month v) @@ -63,17 +69,16 @@ :year (extract-integer :year v)))) (defn- sql-interval [unit amount] - (format "CURRENT_TIMESTAMP + %d" (* (unit) amount))) + (format "CURRENT_TIMESTAMP + %d" (* unit amount))) (defn date-interval [_ unit amount] "defines the sql command required for date-interval calculation" (case unit :quarter (recur nil :month (kx/* amount 3)) - :year (k/raw (sql-interval YEAR amount)) - :month (k/raw (sql-interval MONTH amount)) - :week (k/raw (sql-interval WEEK amount)) - :day (k/raw (sql-interval DAY amount)) - :hour (k/raw (sql-interval HOUR amount)) - :minute (k/raw (sql-interval MINUTE amount)) - :second (k/raw (sql-interval SECOND amount)) - )) + :year (k/raw (sql-interval year amount)) + :month (k/raw (sql-interval month amount)) + :week (k/raw (sql-interval week amount)) + :day (k/raw (sql-interval day amount)) + :hour (k/raw (sql-interval hour amount)) + :minute (k/raw (sql-interval minute amount)) + :second (k/raw (sql-interval second amount)))) diff --git a/src/metabase/driver/generic_sql.clj b/src/metabase/driver/generic_sql.clj index 9148c11a9a29ff5352846e03d286198b9c46c313..dec9ea23feac358d70779c3d96f2618a0db783d8 100644 --- a/src/metabase/driver/generic_sql.clj +++ b/src/metabase/driver/generic_sql.clj @@ -88,7 +88,7 @@ (extend-protocol jdbc/IResultSetReadColumn (class (object-array [])) - (result-set-read-column [x _2 _3] (PersistentVector/adopt x))) + (result-set-read-column [x _ _] (PersistentVector/adopt x))) (def ^:dynamic ^:private connection-pools "A map of our currently open connection pools, keyed by DATABASE `:id`." diff --git a/test/metabase/query_processor_test.clj b/test/metabase/query_processor_test.clj index ba7619b5252729988dd8b2d17b113af592bbbeea..6a01a53094414771ec5b2cddfa4b7f0c620b9d3a 100644 --- a/test/metabase/query_processor_test.clj +++ b/test/metabase/query_processor_test.clj @@ -842,10 +842,10 @@ (datasets/expect-with-engines (engines-that-support :standard-deviation-aggregations) {:columns [(format-name "price") "stddev"] - :rows [[3 (if (or (= *engine* :mysql) (= *engine* :crate)) 25 26)] + :rows [[3 (if (contains? #{:mysql :crate} *engine*) 25 26)] [1 24] [2 21] - [4 (if (or (= *engine* :mysql) (= *engine* :crate)) 14 15)]] + [4 (if (contains? #{:mysql :crate} *engine*) 14 15)]] :cols [(venues-col :price) (aggregate-col :stddev (venues-col :category_id))]} (->> (run-query venues diff --git a/test/metabase/test/data/crate.clj b/test/metabase/test/data/crate.clj index ef6da3c4222e7b699b9ef705161bbb278e9cd473..661a9c7b07eb4d9138029e46dbd783ac19f9ec3e 100644 --- a/test/metabase/test/data/crate.clj +++ b/test/metabase/test/data/crate.clj @@ -1,13 +1,13 @@ (ns metabase.test.data.crate "Code for creating / destroying a Crate database from a `DatabaseDefinition`." - (:require [environ.core :refer [env]] - metabase.driver.crate - (metabase.test.data [generic-sql :as generic] - [interface :as i]) + (:require [clojure.java.jdbc :as jdbc] + [clojure.string :as s] [metabase.driver.generic-sql :as sql] - [clojure.java.jdbc :as jdbc] [metabase.util :as u] - [clojure.string :as s]) + (metabase.test.data [generic-sql :as generic] + [interface :as i]) + [clojure.tools.logging :as log] + [korma.core :as k]) (:import metabase.driver.crate.CrateDriver)) (def ^:private ^:const field-base-type->sql-type @@ -25,9 +25,9 @@ (defn- timestamp->CrateDateTime [value] - (if (= (instance? java.sql.Timestamp value) true) + (if (instance? java.sql.Timestamp value) (.getTime (u/->Timestamp value)) - (if (= (and (= (instance? clojure.lang.PersistentArrayMap value) true) (contains? value :korma.sql.utils/generated)) true) + (if (and (instance? clojure.lang.PersistentArrayMap value) (contains? value :korma.sql.utils/generated)) (+ (read-string (s/replace (:korma.sql.utils/generated value) #"CURRENT_TIMESTAMP \+" "")) (.getTime (u/new-sql-timestamp))) value))) @@ -47,15 +47,15 @@ (let [insert! ((apply comp wrap-insert-fns) (fn [row-or-rows] (apply jdbc/insert! (generic/database->spec driver :db dbdef) - (keyword (get tabledef :table-name)) + (keyword (:table-name tabledef)) :transaction? false (escape-field-names row-or-rows)))) rows (apply list (generic/load-data-get-rows driver dbdef tabledef))] (insert! rows)))) -(defn- database->connection-details [_ _ {:keys [_ _]}] - (merge {:host "localhost" - :port 4300})) +(def ^:private database->connection-details + (constantly {:host "localhost" + :port 4300})) (extend CrateDriver generic/IGenericSQLDatasetLoader