From 71a58264b76987dd4428efe4c6446109e7091fee Mon Sep 17 00:00:00 2001
From: Cam Saul <1455846+camsaul@users.noreply.github.com>
Date: Tue, 17 Dec 2019 22:53:27 +0000
Subject: [PATCH] Make sure query templates containing newlines are parsed
 properly (#11532)

Make sure query templates containing newlines are parsed properly [ci drivers]
---
 .../driver/common/parameters/parse.clj        |  2 +-
 .../driver/common/parameters/parse_test.clj   |  4 +
 .../driver/sql/parameters/substitute_test.clj | 33 ++++++--
 .../query_processor_test/parameters_test.clj  | 82 +++++++++++--------
 4 files changed, 76 insertions(+), 45 deletions(-)

diff --git a/src/metabase/driver/common/parameters/parse.clj b/src/metabase/driver/common/parameters/parse.clj
index df3f8e24f06..efc16d4f72b 100644
--- a/src/metabase/driver/common/parameters/parse.clj
+++ b/src/metabase/driver/common/parameters/parse.clj
@@ -61,7 +61,7 @@
     ["]]" :optional-end]
     ;; param-begin should only match the last two opening brackets in a sequence of > 2, e.g.
     ;; [{$match: {{{x}}, field: 1}}] should parse to ["[$match: {" (param "x") ", field: 1}}]"]
-    [#"(.*?)(\{\{(?!\{))(.*)" :param-begin]
+    [#"(?s)(.*?)(\{\{(?!\{))(.*)" :param-begin]
     ["}}" :param-end]]))
 
 (defn- param [& [k & more]]
diff --git a/test/metabase/driver/common/parameters/parse_test.clj b/test/metabase/driver/common/parameters/parse_test.clj
index ab3bfb5f4b8..27af2e33566 100644
--- a/test/metabase/driver/common/parameters/parse_test.clj
+++ b/test/metabase/driver/common/parameters/parse_test.clj
@@ -70,6 +70,10 @@
            (let [query "SELECT array_dims(1 || '[0:1]={2,3}'::int[])"]
              {query [query]})
 
+           "Queries with newlines (#11526)"
+           {"SELECT count(*)\nFROM products\nWHERE category = {{category}}"
+            ["SELECT count(*)\nFROM products\nWHERE category = " (param "category")]}
+
            "JSON queries that contain non-param fragments like '}}'"
            {"{x: {y: \"{{param}}\"}}"         ["{x: {y: \"" (param "param") "\"}}"]
             "{$match: {{{date}}, field: 1}}}" ["{$match: {" (param "date") ", field: 1}}}"]}}]
diff --git a/test/metabase/driver/sql/parameters/substitute_test.clj b/test/metabase/driver/sql/parameters/substitute_test.clj
index 45a0e0e4c04..c0b4df0615f 100644
--- a/test/metabase/driver/sql/parameters/substitute_test.clj
+++ b/test/metabase/driver/sql/parameters/substitute_test.clj
@@ -698,6 +698,21 @@
                                                      :default      "2017-11-14"
                                                      :widget-type  :date/all-options}}}}))
 
+(deftest newlines-test
+  (testing "Make sure queries with newlines are parsed correctly (#11526)"
+    (is (= [[1]]
+           (mt/rows
+             (qp/process-query
+               {:database   (mt/id)
+                :type       "native"
+                :native     {:query         "SELECT count(*)\nFROM venues\n WHERE name = {{name}}"
+                             :template-tags {:name {:name         "name"
+                                                    :display_name "Name"
+                                                    :type         "text"
+                                                    :required     true
+                                                    :default      "Fred 62"}}}
+                :parameters []}))))))
+
 
 ;;; ------------------------------- Multiple Value Support (comma-separated or array) --------------------------------
 
@@ -705,15 +720,15 @@
   (testing "Make sure using commas in numeric params treats them as separate IDs (#5457)"
     (is (= "SELECT * FROM USERS where id IN (1, 2, 3)"
            (-> (qp/process-query
-                 {:database   (mt/id)
-                  :type       "native"
-                  :native     {:query         "SELECT * FROM USERS [[where id IN ({{ids_list}})]]"
-                               :template-tags {"ids_list" {:name         "ids_list"
-                                                           :display-name "Ids list"
-                                                           :type         :number}}}
-                  :parameters [{:type   "category"
-                                :target [:variable [:template-tag "ids_list"]]
-                                :value  "1,2,3"}]})
+                {:database   (mt/id)
+                 :type       "native"
+                 :native     {:query         "SELECT * FROM USERS [[where id IN ({{ids_list}})]]"
+                              :template-tags {"ids_list" {:name         "ids_list"
+                                                          :display-name "Ids list"
+                                                          :type         :number}}}
+                 :parameters [{:type   "category"
+                               :target [:variable [:template-tag "ids_list"]]
+                               :value  "1,2,3"}]})
                :data :native_form :query))))
   (testing "make sure you can now also pass multiple values in by passing an array of values"
     (is (= {:query  "SELECT * FROM CATEGORIES where name IN (?, ?, ?)"
diff --git a/test/metabase/query_processor_test/parameters_test.clj b/test/metabase/query_processor_test/parameters_test.clj
index c8e6c8c842e..6c3343f92fc 100644
--- a/test/metabase/query_processor_test/parameters_test.clj
+++ b/test/metabase/query_processor_test/parameters_test.clj
@@ -78,44 +78,56 @@
                     {:$project {"_id" false, "count" true}}])
      :collection  (name table)}))
 
-(defn- count-query [table field->type+value]
+(defn- count-query [table field->type+value {:keys [defaults?]}]
   {:database   (mt/id)
    :type       :native
    :native     (assoc (native-count-query driver/*driver* table field->type+value)
-                      :template-tags (into {} (for [[field [param-type]] field->type+value]
-                                                [field {:name         (name field)
-                                                        :display-name (name field)
-                                                        :type         (or (namespace param-type)
-                                                                          (name param-type))}])))
-   :parameters (for [[field [param-type v]] field->type+value]
-                 {:type   param-type
-                  :target [:variable [:template-tag (name field)]]
-                  :value  v})})
-
-(defn- count= [expected table field->type+value]
-  (let [query (count-query table field->type+value)]
-    (testing (str "\nquery =\n" (u/pprint-to-str query))
-      (is (= expected
-             (ffirst
-              (mt/formatted-rows [int]
-                (qp/process-query query))))
-          (format "count with of %s with %s should be %d"
-                  (name table)
-                  (str/join " and " (for [[field [_ v]] field->type+value]
-                                      (format "%s = %s" (name field) v)))
-                  expected)))))
+                      :template-tags (into {} (for [[field [param-type v]] field->type+value]
+                                                [field (cond-> {:name         (name field)
+                                                                :display-name (name field)
+                                                                :type         (or (namespace param-type)
+                                                                                  (name param-type))}
+                                                         defaults? (assoc :default v))])))
+   :parameters (when-not defaults?
+                 (for [[field [param-type v]] field->type+value]
+                   {:type   param-type
+                    :target [:variable [:template-tag (name field)]]
+                    :value  v}))})
 
 (deftest param-test
   (mt/test-drivers (mt/normal-drivers-with-feature :native-parameters)
-    (testing "text params"
-      (count= 1
-              :venues {:name [:text "In-N-Out Burger"]}))
-    (testing "number params"
-      (count= 22
-              :venues {:price [:number "1"]}))
-    ;; FIXME — This is not currently working on SQLite, probably because SQLite's implementation of temporal types is
-    ;; wacko.
-    (when-not (= driver/*driver* :sqlite)
-      (testing "date params"
-        (count= 1
-                :users {:last_login [:date/single "2014-08-02T09:30Z"]})))))
+    (doseq [[message {:keys [expected-count table param-name param-type value exclude-drivers]}]
+            {"text params"   {:expected-count 1
+                              :table          :venues
+                              :param-name     :name
+                              :param-type     :text
+                              :value          "In-N-Out Burger"}
+             "number params" {:expected-count 22
+                              :table          :venues
+                              :param-name     :price
+                              :param-type     :number
+                              :value          "1"}
+             "date params"   {:expected-count  1
+                              ;; FIXME — This is not currently working on SQLite, probably because SQLite's
+                              ;; implementation of temporal types is wacko.
+                              :exclude-drivers #{:sqlite}
+                              :table           :users
+                              :param-name      :last_login
+                              :param-type      :date/single
+                              :value           "2014-08-02T09:30Z"}}
+            :when (not (contains? exclude-drivers driver/*driver*))]
+      (testing (str "\n" message)
+        (doseq [[message options] {"Query with all supplied parameters" nil
+                                   "Query using default values"         {:defaults? true}}]
+          (testing (str "\n" message)
+            (let [query (count-query table {param-name [param-type value]} options)]
+              (testing (str "\nquery =\n" (u/pprint-to-str query))
+                (is (= expected-count
+                       (ffirst
+                        (mt/formatted-rows [int]
+                          (qp/process-query query))))
+                    (format "count with of %s with %s = %s should be %d"
+                            (name table)
+                            (name param-name)
+                            value
+                            expected-count))))))))))
-- 
GitLab