From 1672a5b6c0d5c699acacac302f57b70bbe87647a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cam=20Sa=C3=BCl?= <cammsaul@gmail.com> Date: Fri, 2 Jun 2017 12:32:59 -0700 Subject: [PATCH] =?UTF-8?q?Re=C3=B6rganize=20parameter-expanding=20QP=20mi?= =?UTF-8?q?ddleware=20a=20bit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../query_processor/middleware/parameters.clj | 23 +++- .../parameters/dates.clj} | 83 +------------ .../middleware/parameters/mbql.clj | 44 +++++++ .../parameters/sql.clj} | 9 +- test/metabase/api/database_test.clj | 10 +- .../middleware/parameters/date_test.clj | 44 +++++++ .../parameters/mbql_test.clj} | 55 +-------- .../parameters/sql_test.clj} | 110 +++++++++--------- 8 files changed, 184 insertions(+), 194 deletions(-) rename src/metabase/query_processor/{parameters.clj => middleware/parameters/dates.clj} (71%) create mode 100644 src/metabase/query_processor/middleware/parameters/mbql.clj rename src/metabase/query_processor/{sql_parameters.clj => middleware/parameters/sql.clj} (98%) create mode 100644 test/metabase/query_processor/middleware/parameters/date_test.clj rename test/metabase/query_processor/{parameters_test.clj => middleware/parameters/mbql_test.clj} (65%) rename test/metabase/query_processor/{sql_parameters_test.clj => middleware/parameters/sql_test.clj} (79%) diff --git a/src/metabase/query_processor/middleware/parameters.clj b/src/metabase/query_processor/middleware/parameters.clj index 88cf73a453b..53d25787bb1 100644 --- a/src/metabase/query_processor/middleware/parameters.clj +++ b/src/metabase/query_processor/middleware/parameters.clj @@ -2,20 +2,35 @@ "Middleware for substituting parameters in queries." (:require [clojure.data :as data] [clojure.tools.logging :as log] - (metabase.query-processor [interface :as i] - [parameters :as params]) + [metabase.query-processor.interface :as i] + [metabase.query-processor.middleware.parameters + [mbql :as mbql-params] + [sql :as sql-params]] [metabase.util :as u])) +(defn- expand-parameters + "Expand any :parameters set on the QUERY-DICT and apply them to the query definition. + This function removes the :parameters attribute from the QUERY-DICT as part of its execution." + [{:keys [parameters], :as query-dict}] + ;; params in native queries are currently only supported for SQL drivers + (if (= :query (keyword (:type query-dict))) + (mbql-params/expand (dissoc query-dict :parameters) parameters) + (sql-params/expand query-dict))) + (defn- substitute-parameters* "If any parameters were supplied then substitute them into the query." [query] - (u/prog1 (params/expand-parameters query) + (u/prog1 (expand-parameters query) (when (and (not i/*disable-qp-logging*) (not= <> query)) (when-let [diff (second (data/diff query <>))] (log/debug (u/format-color 'cyan "\n\nPARAMS/SUBSTITUTED: %s\n%s" (u/emoji "😻") (u/pprint-to-str diff))))))) (defn substitute-parameters - "Substitute parameters in a " + "Substitute Dashboard or Card-supplied parameters in a query, replacing the param placeholers + with appropriate values and/or modifiying the query as appropriate. + + (e.g. a SQL query with a param like `{{param}}` will have that part of the query replaced with an appropriate + snippet as well as any prepared statement args needed.)" [qp] (comp qp substitute-parameters*)) diff --git a/src/metabase/query_processor/parameters.clj b/src/metabase/query_processor/middleware/parameters/dates.clj similarity index 71% rename from src/metabase/query_processor/parameters.clj rename to src/metabase/query_processor/middleware/parameters/dates.clj index ae3a3cbbdaf..812e3961b8b 100644 --- a/src/metabase/query_processor/parameters.clj +++ b/src/metabase/query_processor/middleware/parameters/dates.clj @@ -1,18 +1,11 @@ -(ns metabase.query-processor.parameters - "Code for handling parameter substitution in MBQL queries." +(ns metabase.query-processor.middleware.parameters.dates + "Shared code for handling datetime parameters, used by both MBQL and native params implementations." (:require [clj-time [core :as t] [format :as tf]] - [clojure.string :as s] - [medley.core :as m] - [metabase.driver :as driver] - [metabase.query-processor.sql-parameters :as native-params]) + [medley.core :as m]) (:import [org.joda.time DateTime DateTimeConstants])) -;;; +-------------------------------------------------------------------------------------------------------+ -;;; | DATE RANGES & PERIODS | -;;; +-------------------------------------------------------------------------------------------------------+ - ;; Both in MBQL and SQL parameter substitution a field value is compared to a date range, either relative or absolute. ;; Currently the field value is casted to a day (ignoring the time of day), so the ranges should have the same ;; granularity level. @@ -69,6 +62,7 @@ [date] (tf/parse (tf/formatters :date-opt-time) date)) + ;;; +-------------------------------------------------------------------------------------------------------+ ;;; | DATE STRING DECODERS | ;;; +-------------------------------------------------------------------------------------------------------+ @@ -84,6 +78,7 @@ (:date :date-1 :date-2) [[group-label (parse-absolute-date group-value)]] [[group-label group-value]])) + (defn- regex->parser "Takes a regex and labels matching the regex capturing groups. Returns a parser which takes a parameter value, validates the value against regex and gives a map of labels @@ -217,74 +212,8 @@ (->> (execute-decoders absolute-date-string-decoders :range nil date-string) (m/map-vals (partial tf/unparse formatter-no-tz)))))) -(defn- date-string->filter +(defn date-string->filter "Takes a string description of a date range such as 'lastmonth' or '2016-07-15~2016-08-6' and returns a corresponding MBQL filter clause for a given field reference." [date-string field-reference] (execute-decoders all-date-string-decoders :filter field-reference date-string)) - -;;; +-------------------------------------------------------------------------------------------------------+ -;;; | MBQL QUERIES | -;;; +-------------------------------------------------------------------------------------------------------+ - -(defn- parse-param-value-for-type - "Convert PARAM-VALUE to a type appropriate for PARAM-TYPE. - The frontend always passes parameters in as strings, which is what we want in most cases; for numbers, instead convert the parameters to integers or floating-point numbers." - [param-type param-value] - (cond - ;; no conversion needed if PARAM-TYPE isn't :number or PARAM-VALUE isn't a string - (or (not= (keyword param-type) :number) - (not (string? param-value))) param-value - ;; if PARAM-VALUE contains a period then convert to a Double - (re-find #"\." param-value) (Double/parseDouble param-value) - ;; otherwise convert to a Long - :else (Long/parseLong param-value))) - -(defn- build-filter-clause [{param-type :type, param-value :value, [_ field] :target}] - (let [param-value (parse-param-value-for-type param-type param-value)] - (cond - ;; default behavior (non-date filtering) is to use a simple equals filter - (not (s/starts-with? param-type "date")) ["=" field param-value] - ;; date range - :else (date-string->filter param-value field)))) - -(defn- merge-filter-clauses [base addtl] - (cond - (and (seq base) - (seq addtl)) ["AND" base addtl] - (seq base) base - (seq addtl) addtl - :else [])) - -(defn- expand-params:mbql [query-dict [{:keys [target value], :as param} & rest]] - (cond - (not param) query-dict - (or (not target) - (not value)) (recur query-dict rest) - :else (let [filter-subclause (build-filter-clause param) - query (assoc-in query-dict [:query :filter] (merge-filter-clauses (get-in query-dict [:query :filter]) filter-subclause))] - (recur query rest)))) - - -;;; +-------------------------------------------------------------------------------------------------------+ -;;; | SQL QUERIES | -;;; +-------------------------------------------------------------------------------------------------------+ - -(defn- expand-params:native [{:keys [driver] :as query}] - (if-not (driver/driver-supports? driver :native-parameters) - query - (native-params/expand-params query))) - - -;;; +-------------------------------------------------------------------------------------------------------+ -;;; | PUBLIC API | -;;; +-------------------------------------------------------------------------------------------------------+ - - -(defn expand-parameters - "Expand any :parameters set on the QUERY-DICT and apply them to the query definition. - This function removes the :parameters attribute from the QUERY-DICT as part of its execution." - [{:keys [parameters], :as query-dict}] - (if (= :query (keyword (:type query-dict))) - (expand-params:mbql (dissoc query-dict :parameters) parameters) - (expand-params:native query-dict))) diff --git a/src/metabase/query_processor/middleware/parameters/mbql.clj b/src/metabase/query_processor/middleware/parameters/mbql.clj new file mode 100644 index 00000000000..e13209b69c0 --- /dev/null +++ b/src/metabase/query_processor/middleware/parameters/mbql.clj @@ -0,0 +1,44 @@ +(ns metabase.query-processor.middleware.parameters.mbql + "Code for handling parameter substitution in MBQL queries." + (:require [clojure.string :as str] + [metabase.query-processor.middleware.parameters.dates :as date-params])) + +(defn- parse-param-value-for-type + "Convert PARAM-VALUE to a type appropriate for PARAM-TYPE. + The frontend always passes parameters in as strings, which is what we want in most cases; for numbers, instead convert the parameters to integers or floating-point numbers." + [param-type param-value] + (cond + ;; no conversion needed if PARAM-TYPE isn't :number or PARAM-VALUE isn't a string + (or (not= (keyword param-type) :number) + (not (string? param-value))) param-value + ;; if PARAM-VALUE contains a period then convert to a Double + (re-find #"\." param-value) (Double/parseDouble param-value) + ;; otherwise convert to a Long + :else (Long/parseLong param-value))) + +(defn- build-filter-clause [{param-type :type, param-value :value, [_ field] :target}] + (let [param-value (parse-param-value-for-type param-type param-value)] + (cond + ;; default behavior (non-date filtering) is to use a simple equals filter + (not (str/starts-with? param-type "date")) ["=" field param-value] + ;; date range + :else (date-params/date-string->filter param-value field)))) + +(defn- merge-filter-clauses [base addtl] + (cond + (and (seq base) + (seq addtl)) ["AND" base addtl] + (seq base) base + (seq addtl) addtl + :else [])) + +(defn expand + "Expand parameters for MBQL queries in QUERY-DICT (replacing Dashboard or Card-supplied params with the appropriate values in the queries themselves)." + [query-dict [{:keys [target value], :as param} & rest]] + (cond + (not param) query-dict + (or (not target) + (not value)) (recur query-dict rest) + :else (let [filter-subclause (build-filter-clause param) + query (assoc-in query-dict [:query :filter] (merge-filter-clauses (get-in query-dict [:query :filter]) filter-subclause))] + (recur query rest)))) diff --git a/src/metabase/query_processor/sql_parameters.clj b/src/metabase/query_processor/middleware/parameters/sql.clj similarity index 98% rename from src/metabase/query_processor/sql_parameters.clj rename to src/metabase/query_processor/middleware/parameters/sql.clj index 6c762c40833..a086f09265a 100644 --- a/src/metabase/query_processor/sql_parameters.clj +++ b/src/metabase/query_processor/middleware/parameters/sql.clj @@ -1,4 +1,4 @@ -(ns metabase.query-processor.sql-parameters +(ns metabase.query-processor.middleware.parameters.sql "Param substitution for *SQL* queries. This is a new implementation, fondly referred to as 'SQL parameters 2.0', written for v0.23.0. The new implementation uses prepared statement args instead of substituting them directly into the query, @@ -8,6 +8,7 @@ [honeysql.core :as hsql] [metabase.models.field :as field :refer [Field]] [metabase.query-processor.expand :as ql] + [metabase.query-processor.middleware.parameters.dates :as date-params] [metabase.util :as u] [metabase.util.schema :as su] [schema.core :as s] @@ -205,7 +206,7 @@ ;; for relative dates convert the param to a `DateRange` record type and call `->replacement-snippet-info` on it (s/defn ^:private ^:always-validate relative-date-dimension-value->replacement-snippet-info :- ParamSnippetInfo [value] - (->replacement-snippet-info (map->DateRange ((resolve 'metabase.query-processor.parameters/date-string->range) value *timezone*)))) ; TODO - get timezone from query dict + (->replacement-snippet-info (map->DateRange (date-params/date-string->range value *timezone*)))) ; TODO - get timezone from query dict (s/defn ^:private ^:always-validate dimension-value->equals-clause-sql :- ParamSnippetInfo [value] @@ -402,8 +403,8 @@ (merge native (when-let [param-snippets-info (seq (add-replacement-snippet-info (sql->params-snippets-info sql) param-key->value))] (substitute sql param-snippets-info)))) -(defn expand-params - "Expand parameters inside a *native* QUERY." +(defn expand + "Expand parameters inside a *SQL* QUERY." [query] (binding [*driver* (:driver query) *timezone* (get-in query [:settings :report-timezone])] diff --git a/test/metabase/api/database_test.clj b/test/metabase/api/database_test.clj index ab97f890d3d..c22b310d80a 100644 --- a/test/metabase/api/database_test.clj +++ b/test/metabase/api/database_test.clj @@ -150,11 +150,11 @@ "Delete all the randomly created Databases we've made so far. Optionally specify one or more IDs to SKIP." [& {:keys [skip]}] (db/delete! Database :id [:not-in (into (set skip) - (for [engine datasets/all-valid-engines - :let [id (datasets/when-testing-engine engine - (:id (get-or-create-test-data-db! (driver/engine->driver engine))))] - :when id] - id))])) + (for [engine datasets/all-valid-engines + :let [id (datasets/when-testing-engine engine + (:id (get-or-create-test-data-db! (driver/engine->driver engine))))] + :when id] + id))])) ;; ## GET /api/database diff --git a/test/metabase/query_processor/middleware/parameters/date_test.clj b/test/metabase/query_processor/middleware/parameters/date_test.clj new file mode 100644 index 00000000000..4012819d7b9 --- /dev/null +++ b/test/metabase/query_processor/middleware/parameters/date_test.clj @@ -0,0 +1,44 @@ +(ns metabase.query-processor.middleware.parameters.date-test + (:require [expectations :refer :all] + [clj-time.core :as t] + [metabase.query-processor.middleware.parameters.dates :refer :all])) + +;; we hard code "now" to a specific point in time so that we can control the test output +(defn- test-date->range [value] + (with-redefs-fn {#'clj-time.core/now (fn [] (t/date-time 2016 06 07 12 0 0))} + #(date-string->range value nil))) + +(expect {:end "2016-03-31", :start "2016-01-01"} (test-date->range "Q1-2016")) +(expect {:end "2016-02-29", :start "2016-02-01"} (test-date->range "2016-02")) +(expect {:end "2016-04-18", :start "2016-04-18"} (test-date->range "2016-04-18")) +(expect {:end "2016-04-23", :start "2016-04-18"} (test-date->range "2016-04-18~2016-04-23")) +(expect {:end "2016-04-23", :start "2016-04-18"} (test-date->range "2016-04-18~2016-04-23")) +(expect {:start "2016-04-18"} (test-date->range "2016-04-18~")) +(expect {:end "2016-04-18"} (test-date->range "~2016-04-18")) + +(expect {:end "2016-06-06", :start "2016-06-04"} (test-date->range "past3days")) +(expect {:end "2016-06-06", :start "2016-05-31"} (test-date->range "past7days")) +(expect {:end "2016-06-06", :start "2016-05-08"} (test-date->range "past30days")) +(expect {:end "2016-05-31", :start "2016-04-01"} (test-date->range "past2months")) +(expect {:end "2016-05-31", :start "2015-05-01"} (test-date->range "past13months")) +(expect {:end "2015-12-31", :start "2015-01-01"} (test-date->range "past1years")) +(expect {:end "2015-12-31", :start "2000-01-01"} (test-date->range "past16years")) + +(expect {:end "2016-06-10", :start "2016-06-08"} (test-date->range "next3days")) +(expect {:end "2016-06-14", :start "2016-06-08"} (test-date->range "next7days")) +(expect {:end "2016-07-07", :start "2016-06-08"} (test-date->range "next30days")) +(expect {:end "2016-08-31", :start "2016-07-01"} (test-date->range "next2months")) +(expect {:end "2017-07-31", :start "2016-07-01"} (test-date->range "next13months")) +(expect {:end "2017-12-31", :start "2017-01-01"} (test-date->range "next1years")) +(expect {:end "2032-12-31", :start "2017-01-01"} (test-date->range "next16years")) + +(expect {:end "2016-06-07", :start "2016-06-07"} (test-date->range "thisday")) +(expect {:end "2016-06-11", :start "2016-06-05"} (test-date->range "thisweek")) +(expect {:end "2016-06-30", :start "2016-06-01"} (test-date->range "thismonth")) +(expect {:end "2016-12-31", :start "2016-01-01"} (test-date->range "thisyear")) + +(expect {:end "2016-06-04", :start "2016-05-29"} (test-date->range "lastweek")) +(expect {:end "2016-05-31", :start "2016-05-01"} (test-date->range "lastmonth")) +(expect {:end "2015-12-31", :start "2015-01-01"} (test-date->range "lastyear")) +(expect {:end "2016-06-06", :start "2016-06-06"} (test-date->range "yesterday")) +(expect {:end "2016-06-07", :start "2016-06-07"} (test-date->range "today")) diff --git a/test/metabase/query_processor/parameters_test.clj b/test/metabase/query_processor/middleware/parameters/mbql_test.clj similarity index 65% rename from test/metabase/query_processor/parameters_test.clj rename to test/metabase/query_processor/middleware/parameters/mbql_test.clj index 225cd56fc4f..ef6cb90b51e 100644 --- a/test/metabase/query_processor/parameters_test.clj +++ b/test/metabase/query_processor/middleware/parameters/mbql_test.clj @@ -1,59 +1,16 @@ -(ns metabase.query-processor.parameters-test +(ns metabase.query-processor.middleware.parameters.mbql-test "Tests for *MBQL* parameter substitution." - (:require [clj-time.core :as t] - [expectations :refer :all] + (:require [expectations :refer :all] [metabase [query-processor :as qp] [query-processor-test :refer [first-row format-rows-by non-timeseries-engines]]] - [metabase.query-processor - [expand :as ql] - [parameters :refer :all]] + [metabase.query-processor.expand :as ql] + [metabase.query-processor.middleware.parameters.mbql :refer :all] [metabase.test.data :as data] [metabase.test.data.datasets :as datasets])) -;; we hard code "now" to a specific point in time so that we can control the test output -(defn- test-date->range [value] - (with-redefs-fn {#'clj-time.core/now (fn [] (t/date-time 2016 06 07 12 0 0))} - #(date-string->range value nil))) - -(expect {:end "2016-03-31", :start "2016-01-01"} (test-date->range "Q1-2016")) -(expect {:end "2016-02-29", :start "2016-02-01"} (test-date->range "2016-02")) -(expect {:end "2016-04-18", :start "2016-04-18"} (test-date->range "2016-04-18")) -(expect {:end "2016-04-23", :start "2016-04-18"} (test-date->range "2016-04-18~2016-04-23")) -(expect {:end "2016-04-23", :start "2016-04-18"} (test-date->range "2016-04-18~2016-04-23")) -(expect {:start "2016-04-18"} (test-date->range "2016-04-18~")) -(expect {:end "2016-04-18"} (test-date->range "~2016-04-18")) - -(expect {:end "2016-06-06", :start "2016-06-04"} (test-date->range "past3days")) -(expect {:end "2016-06-06", :start "2016-05-31"} (test-date->range "past7days")) -(expect {:end "2016-06-06", :start "2016-05-08"} (test-date->range "past30days")) -(expect {:end "2016-05-31", :start "2016-04-01"} (test-date->range "past2months")) -(expect {:end "2016-05-31", :start "2015-05-01"} (test-date->range "past13months")) -(expect {:end "2015-12-31", :start "2015-01-01"} (test-date->range "past1years")) -(expect {:end "2015-12-31", :start "2000-01-01"} (test-date->range "past16years")) - -(expect {:end "2016-06-10", :start "2016-06-08"} (test-date->range "next3days")) -(expect {:end "2016-06-14", :start "2016-06-08"} (test-date->range "next7days")) -(expect {:end "2016-07-07", :start "2016-06-08"} (test-date->range "next30days")) -(expect {:end "2016-08-31", :start "2016-07-01"} (test-date->range "next2months")) -(expect {:end "2017-07-31", :start "2016-07-01"} (test-date->range "next13months")) -(expect {:end "2017-12-31", :start "2017-01-01"} (test-date->range "next1years")) -(expect {:end "2032-12-31", :start "2017-01-01"} (test-date->range "next16years")) - -(expect {:end "2016-06-07", :start "2016-06-07"} (test-date->range "thisday")) -(expect {:end "2016-06-11", :start "2016-06-05"} (test-date->range "thisweek")) -(expect {:end "2016-06-30", :start "2016-06-01"} (test-date->range "thismonth")) -(expect {:end "2016-12-31", :start "2016-01-01"} (test-date->range "thisyear")) - -(expect {:end "2016-06-04", :start "2016-05-29"} (test-date->range "lastweek")) -(expect {:end "2016-05-31", :start "2016-05-01"} (test-date->range "lastmonth")) -(expect {:end "2015-12-31", :start "2015-01-01"} (test-date->range "lastyear")) -(expect {:end "2016-06-06", :start "2016-06-06"} (test-date->range "yesterday")) -(expect {:end "2016-06-07", :start "2016-06-07"} (test-date->range "today")) - -;;; +-------------------------------------------------------------------------------------------------------+ -;;; | MBQL QUERIES | -;;; +-------------------------------------------------------------------------------------------------------+ +(defn- expand-parameters [query] + (expand (dissoc query :parameters) (:parameters query))) ;; adding a simple parameter diff --git a/test/metabase/query_processor/sql_parameters_test.clj b/test/metabase/query_processor/middleware/parameters/sql_test.clj similarity index 79% rename from test/metabase/query_processor/sql_parameters_test.clj rename to test/metabase/query_processor/middleware/parameters/sql_test.clj index 2bd5f7aca3a..48271d23708 100644 --- a/test/metabase/query_processor/sql_parameters_test.clj +++ b/test/metabase/query_processor/middleware/parameters/sql_test.clj @@ -1,11 +1,11 @@ -(ns metabase.query-processor.sql-parameters-test +(ns metabase.query-processor.middleware.parameters.sql-test (:require [clj-time.core :as t] [expectations :refer :all] [metabase [driver :as driver] [query-processor :as qp] [query-processor-test :refer [engines-that-support first-row format-rows-by]]] - [metabase.query-processor.sql-parameters :refer :all] + [metabase.query-processor.middleware.parameters.sql :refer :all] [metabase.test [data :as data] [util :as tu]] @@ -17,8 +17,8 @@ ;;; ------------------------------------------------------------ simple substitution -- {{x}} ------------------------------------------------------------ (defn- substitute {:style/indent 1} [sql params] - (binding [metabase.query-processor.sql-parameters/*driver* (driver/engine->driver :h2)] ; apparently you can still bind private dynamic vars - ((resolve 'metabase.query-processor.sql-parameters/expand-query-params) + (binding [metabase.query-processor.middleware.parameters.sql/*driver* (driver/engine->driver :h2)] ; apparently you can still bind private dynamic vars + ((resolve 'metabase.query-processor.middleware.parameters.sql/expand-query-params) {:query sql} (into {} (for [[k v] params] {k v}))))) @@ -182,7 +182,7 @@ ;;; ------------------------------------------------------------ tests for value-for-tag ------------------------------------------------------------ -(tu/resolve-private-vars metabase.query-processor.sql-parameters value-for-tag) +(tu/resolve-private-vars metabase.query-processor.middleware.parameters.sql value-for-tag) ;; variable -- specified (expect @@ -192,7 +192,7 @@ ;; variable -- unspecified (expect - #metabase.query_processor.sql_parameters.NoValue{} + #metabase.query_processor.middleware.parameters.sql.NoValue{} (value-for-tag {:name "id", :display_name "ID", :type "text"} nil)) ;; variable -- default @@ -238,8 +238,8 @@ ;;; ------------------------------------------------------------ expansion tests: variables ------------------------------------------------------------ -(defn- expand-params* [query] - (-> (expand-params (assoc query :driver (driver/engine->driver :h2))) +(defn- expand* [query] + (-> (expand (assoc query :driver (driver/engine->driver :h2))) :native (select-keys [:query :params]))) @@ -247,59 +247,59 @@ (expect {:query "SELECT * FROM orders ;" :params []} - (expand-params* {:native {:query "SELECT * FROM orders [[WHERE id = {{id}}]];" - :template_tags {:id {:name "id", :display_name "ID", :type "number"}}} - :parameters []})) + (expand* {:native {:query "SELECT * FROM orders [[WHERE id = {{id}}]];" + :template_tags {:id {:name "id", :display_name "ID", :type "number"}}} + :parameters []})) ;; unspecified *required* param (expect Exception - (expand-params {:native {:query "SELECT * FROM orders [[WHERE id = {{id}}]];" - :template_tags {:id {:name "id", :display_name "ID", :type "number", :required true}}} - :parameters []})) + (expand {:native {:query "SELECT * FROM orders [[WHERE id = {{id}}]];" + :template_tags {:id {:name "id", :display_name "ID", :type "number", :required true}}} + :parameters []})) ;; default value (expect {:query "SELECT * FROM orders WHERE id = 100;" :params []} - (expand-params* {:native {:query "SELECT * FROM orders WHERE id = {{id}};" - :template_tags {:id {:name "id", :display_name "ID", :type "number", :required true, :default "100"}}} - :parameters []})) + (expand* {:native {:query "SELECT * FROM orders WHERE id = {{id}};" + :template_tags {:id {:name "id", :display_name "ID", :type "number", :required true, :default "100"}}} + :parameters []})) ;; specified param (numbers) (expect {:query "SELECT * FROM orders WHERE id = 2;" :params []} - (expand-params* {:native {:query "SELECT * FROM orders WHERE id = {{id}};" - :template_tags {:id {:name "id", :display_name "ID", :type "number", :required true, :default "100"}}} - :parameters [{:type "category", :target ["variable" ["template-tag" "id"]], :value "2"}]})) + (expand* {:native {:query "SELECT * FROM orders WHERE id = {{id}};" + :template_tags {:id {:name "id", :display_name "ID", :type "number", :required true, :default "100"}}} + :parameters [{:type "category", :target ["variable" ["template-tag" "id"]], :value "2"}]})) ;; specified param (date/single) (expect {:query "SELECT * FROM orders WHERE created_at > ?;" :params [#inst "2016-07-19T00:00:00.000000000-00:00"]} - (expand-params* {:native {:query "SELECT * FROM orders WHERE created_at > {{created_at}};" - :template_tags {:created_at {:name "created_at", :display_name "Created At", :type "date"}}} - :parameters [{:type "date/single", :target ["variable" ["template-tag" "created_at"]], :value "2016-07-19"}]})) + (expand* {:native {:query "SELECT * FROM orders WHERE created_at > {{created_at}};" + :template_tags {:created_at {:name "created_at", :display_name "Created At", :type "date"}}} + :parameters [{:type "date/single", :target ["variable" ["template-tag" "created_at"]], :value "2016-07-19"}]})) ;; specified param (text) (expect {:query "SELECT * FROM products WHERE category = ?;" :params ["Gizmo"]} - (expand-params* {:native {:query "SELECT * FROM products WHERE category = {{category}};" - :template_tags {:category {:name "category", :display_name "Category", :type "text"}}} - :parameters [{:type "category", :target ["variable" ["template-tag" "category"]], :value "Gizmo"}]})) + (expand* {:native {:query "SELECT * FROM products WHERE category = {{category}};" + :template_tags {:category {:name "category", :display_name "Category", :type "text"}}} + :parameters [{:type "category", :target ["variable" ["template-tag" "category"]], :value "Gizmo"}]})) ;;; ------------------------------------------------------------ expansion tests: dimensions ------------------------------------------------------------ (defn- expand-with-dimension-param [dimension-param] (with-redefs [t/now (fn [] (t/date-time 2016 06 07 12 0 0))] - (expand-params* {:native {:query "SELECT * FROM checkins WHERE {{date}};" - :template_tags {:date {:name "date", :display_name "Checkin Date", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}}} - :parameters (when dimension-param - [(merge {:target ["dimension" ["template-tag" "date"]]} - dimension-param)])}))) + (expand* {:native {:query "SELECT * FROM checkins WHERE {{date}};" + :template_tags {:date {:name "date", :display_name "Checkin Date", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}}} + :parameters (when dimension-param + [(merge {:target ["dimension" ["template-tag" "date"]]} + dimension-param)])}))) ;; dimension (date/single) (expect @@ -496,53 +496,53 @@ :template_tags {:created_at {:name "created_at", :display_name "Created At", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}}, :params [#inst "2017-03-01T00:00:00.000000000-00:00" #inst "2017-03-31T00:00:00.000000000-00:00"]} - (:native (expand-params {:driver (driver/engine->driver :h2) - :native {:query "SELECT count(*) FROM CHECKINS WHERE {{created_at}}" - :template_tags {:created_at {:name "created_at", :display_name "Created At", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}}} - :parameters [{:type "date/month-year", :target ["dimension" ["template-tag" "created_at"]], :value "2017-03"}]}))) + (:native (expand {:driver (driver/engine->driver :h2) + :native {:query "SELECT count(*) FROM CHECKINS WHERE {{created_at}}" + :template_tags {:created_at {:name "created_at", :display_name "Created At", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}}} + :parameters [{:type "date/month-year", :target ["dimension" ["template-tag" "created_at"]], :value "2017-03"}]}))) (expect {:query "SELECT count(*) FROM ORDERS" :template_tags {:price {:name "price", :display_name "Price", :type "number", :required false}} :params []} - (:native (expand-params {:driver (driver/engine->driver :h2) - :native {:query "SELECT count(*) FROM ORDERS [[WHERE price > {{price}}]]" - :template_tags {:price {:name "price", :display_name "Price", :type "number", :required false}}} - :parameters []}))) + (:native (expand {:driver (driver/engine->driver :h2) + :native {:query "SELECT count(*) FROM ORDERS [[WHERE price > {{price}}]]" + :template_tags {:price {:name "price", :display_name "Price", :type "number", :required false}}} + :parameters []}))) (expect {:query "SELECT count(*) FROM ORDERS WHERE price > 100" :template_tags {:price {:name "price", :display_name "Price", :type "number", :required false}} :params []} - (:native (expand-params {:driver (driver/engine->driver :h2) - :native {:query "SELECT count(*) FROM ORDERS [[WHERE price > {{price}}]]" - :template_tags {:price {:name "price", :display_name "Price", :type "number", :required false}}} - :parameters [{:type "category", :target ["variable" ["template-tag" "price"]], :value "100"}]}))) + (:native (expand {:driver (driver/engine->driver :h2) + :native {:query "SELECT count(*) FROM ORDERS [[WHERE price > {{price}}]]" + :template_tags {:price {:name "price", :display_name "Price", :type "number", :required false}}} + :parameters [{:type "category", :target ["variable" ["template-tag" "price"]], :value "100"}]}))) (expect {:query "SELECT count(*) FROM PRODUCTS WHERE TITLE LIKE ?" :template_tags {:x {:name "x", :display_name "X", :type "text", :required true, :default "%Toucan%"}} :params ["%Toucan%"]} - (:native (expand-params {:driver (driver/engine->driver :h2) - :native {:query "SELECT count(*) FROM PRODUCTS WHERE TITLE LIKE {{x}}", - :template_tags {:x {:name "x", :display_name "X", :type "text", :required true, :default "%Toucan%"}}}, - :parameters [{:type "category", :target ["variable" ["template-tag" "x"]]}]}))) + (:native (expand {:driver (driver/engine->driver :h2) + :native {:query "SELECT count(*) FROM PRODUCTS WHERE TITLE LIKE {{x}}", + :template_tags {:x {:name "x", :display_name "X", :type "text", :required true, :default "%Toucan%"}}}, + :parameters [{:type "category", :target ["variable" ["template-tag" "x"]]}]}))) ;; make sure that you can use the same parameter multiple times (#4659) (expect {:query "SELECT count(*) FROM products WHERE title LIKE ? AND subtitle LIKE ?" :template_tags {:x {:name "x", :display_name "X", :type "text", :required true, :default "%Toucan%"}} :params ["%Toucan%" "%Toucan%"]} - (:native (expand-params {:driver (driver/engine->driver :h2) - :native {:query "SELECT count(*) FROM products WHERE title LIKE {{x}} AND subtitle LIKE {{x}}", - :template_tags {:x {:name "x", :display_name "X", :type "text", :required true, :default "%Toucan%"}}}, - :parameters [{:type "category", :target ["variable" ["template-tag" "x"]]}]}))) + (:native (expand {:driver (driver/engine->driver :h2) + :native {:query "SELECT count(*) FROM products WHERE title LIKE {{x}} AND subtitle LIKE {{x}}", + :template_tags {:x {:name "x", :display_name "X", :type "text", :required true, :default "%Toucan%"}}}, + :parameters [{:type "category", :target ["variable" ["template-tag" "x"]]}]}))) (expect {:query "SELECT * FROM ORDERS WHERE true AND ID = ? OR USER_ID = ?" :template_tags {:id {:name "id", :display_name "ID", :type "text"}} :params ["2" "2"]} - (:native (expand-params {:driver (driver/engine->driver :h2) - :native {:query "SELECT * FROM ORDERS WHERE true [[ AND ID = {{id}} OR USER_ID = {{id}} ]]" - :template_tags {:id {:name "id", :display_name "ID", :type "text"}}} - :parameters [{:type "category", :target ["variable" ["template-tag" "id"]], :value "2"}]}))) + (:native (expand {:driver (driver/engine->driver :h2) + :native {:query "SELECT * FROM ORDERS WHERE true [[ AND ID = {{id}} OR USER_ID = {{id}} ]]" + :template_tags {:id {:name "id", :display_name "ID", :type "text"}}} + :parameters [{:type "category", :target ["variable" ["template-tag" "id"]], :value "2"}]}))) -- GitLab