From bb3856c6c12e7cb0727b6c9c312edb0eb12bc0ac Mon Sep 17 00:00:00 2001
From: Cam Saul <1455846+camsaul@users.noreply.github.com>
Date: Thu, 28 May 2020 13:55:15 -0700
Subject: [PATCH] Fix normalization of native query params (#12608)

---
 backend/mbql/src/metabase/mbql/normalize.clj  |   2 +-
 .../test/metabase/mbql/normalize_test.clj     | 132 +++++++++---------
 2 files changed, 69 insertions(+), 65 deletions(-)

diff --git a/backend/mbql/src/metabase/mbql/normalize.clj b/backend/mbql/src/metabase/mbql/normalize.clj
index 19630c088e9..0a8ed03ba3e 100644
--- a/backend/mbql/src/metabase/mbql/normalize.clj
+++ b/backend/mbql/src/metabase/mbql/normalize.clj
@@ -622,7 +622,7 @@
     (remove-empty-clauses source-query [:query])))
 
 (def ^:private path->special-remove-empty-clauses-fn
-  {:native {:query identity}
+  {:native identity
    :query  {:source-query remove-empty-clauses-in-source-query
             :joins        {::sequence remove-empty-clauses-in-join}}})
 
diff --git a/backend/mbql/test/metabase/mbql/normalize_test.clj b/backend/mbql/test/metabase/mbql/normalize_test.clj
index 1da4454005c..6d67dd3dc2f 100644
--- a/backend/mbql/test/metabase/mbql/normalize_test.clj
+++ b/backend/mbql/test/metabase/mbql/normalize_test.clj
@@ -9,63 +9,61 @@
 ;;; |                                                NORMALIZE TOKENS                                                |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-;; Query type should get normalized
-(expect
-  {:type :native}
-  (#'normalize/normalize-tokens {:type "NATIVE"}))
+(deftest normalize-tokens-test
+  (doseq [[group query->expected]
+          {"Query type should get normalized"
+           {{:type "NATIVE"}
+            {:type :native}}
 
-;; native queries should NOT get normalized
-(expect
-  {:type :native, :native {:query "SELECT COUNT(*) FROM CANS;"}}
-  (#'normalize/normalize-tokens {:type "NATIVE", :native {"QUERY" "SELECT COUNT(*) FROM CANS;"}}))
+           "native queries should NOT get normalized"
+           {{:type "NATIVE", :native {"QUERY" "SELECT COUNT(*) FROM CANS;"}}
+            {:type :native, :native {:query "SELECT COUNT(*) FROM CANS;"}}
 
-(expect
-  {:native {:query {:NAME        "FAKE_QUERY"
-                    :description "Theoretical fake query in a JSON-based query lang"}}}
-  (#'normalize/normalize-tokens {:native {:query {:NAME        "FAKE_QUERY"
-                                                  :description "Theoretical fake query in a JSON-based query lang"}}}))
+            {:native {:query {:NAME        "FAKE_QUERY"
+                              :description "Theoretical fake query in a JSON-based query lang"}}}
+            {:native {:query {:NAME        "FAKE_QUERY"
+                              :description "Theoretical fake query in a JSON-based query lang"}}}}
 
-;; METRICS shouldn't get normalized in some kind of wacky way
-(expect
-  {:aggregation [:+ [:metric 10] 1]}
-  (#'normalize/normalize-tokens {:aggregation ["+" ["METRIC" 10] 1]}))
+           "METRICS shouldn't get normalized in some kind of wacky way"
+           {{:aggregation ["+" ["METRIC" 10] 1]}
+            {:aggregation [:+ [:metric 10] 1]}}
 
-;; Nor should SEGMENTS
-(expect
-  {:filter [:= [:+ [:segment 10] 1] 10]}
-  (#'normalize/normalize-tokens {:filter ["=" ["+" ["SEGMENT" 10] 1] 10]}))
+           "Nor should SEGMENTS"
+           {{:filter ["=" ["+" ["SEGMENT" 10] 1] 10]}
+            {:filter [:= [:+ [:segment 10] 1] 10]}}
 
-;; are expression names exempt from lisp-casing/lower-casing?
-(expect
-  {:query {:expressions {:sales_tax [:- [:field-id 10] [:field-id 20]]}}}
-  (#'normalize/normalize-tokens {"query" {"expressions" {:sales_tax ["-" ["field-id" 10] ["field-id" 20]]}}}))
+           "are expression names exempt from lisp-casing/lower-casing?"
+           {{"query" {"expressions" {:sales_tax ["-" ["field-id" 10] ["field-id" 20]]}}}
+            {:query {:expressions {:sales_tax [:- [:field-id 10] [:field-id 20]]}}}}
 
-;; expression references should be exempt too
-(expect
-  {:order-by [[:desc [:expression "SALES_TAX"]]]}
-  (#'normalize/normalize-tokens {:order-by [[:desc [:expression "SALES_TAX"]]]}) )
+           "expression references should be exempt too"
+           {{:order-by [[:desc [:expression "SALES_TAX"]]]}
+            {:order-by [[:desc [:expression "SALES_TAX"]]]}}
 
-;; ... but they should be converted to strings if passed in as a KW for some reason. Make sure we preserve namespace!
-(expect
-  {:order-by [[:desc [:expression "SALES/TAX"]]]}
-  (#'normalize/normalize-tokens {:order-by [[:desc ["expression" :SALES/TAX]]]}))
+           "... but they should be converted to strings if passed in as a KW for some reason. Make sure we preserve namespace!"
+           {{:order-by [[:desc ["expression" :SALES/TAX]]]}
+            {:order-by [[:desc [:expression "SALES/TAX"]]]}}
 
-;; field literals should be exempt too
-(expect
-  {:order-by [[:desc [:field-literal "SALES_TAX" :type/Number]]]}
-  (#'normalize/normalize-tokens {:order-by [[:desc [:field-literal "SALES_TAX" :type/Number]]]}) )
 
-;; ... but they should be converted to strings if passed in as a KW for some reason
-(expect
-  {:order-by [[:desc [:field-literal "SALES/TAX" :type/Number]]]}
-  (#'normalize/normalize-tokens {:order-by [[:desc ["field_literal" :SALES/TAX "type/Number"]]]}))
+           "field literals should be exempt too"
+           {{:order-by [[:desc [:field-literal "SALES_TAX" :type/Number]]]}
+            {:order-by [[:desc [:field-literal "SALES_TAX" :type/Number]]]}}
+
+
+           "... but they should be converted to strings if passed in as a KW for some reason"
+           {{:order-by [[:desc ["field_literal" :SALES/TAX "type/Number"]]]}
+            {:order-by [[:desc [:field-literal "SALES/TAX" :type/Number]]]}}}]
+    (testing group
+      (doseq [[query expected] query->expected]
+        (is (= expected
+               (#'normalize/normalize-tokens query)))))))
 
 
 ;;; -------------------------------------------------- aggregation ---------------------------------------------------
 
 (expect
-  {:query {:aggregation :rows}}
-  (#'normalize/normalize-tokens {:query {"AGGREGATION" "ROWS"}}))
+ {:query {:aggregation :rows}}
+ (#'normalize/normalize-tokens {:query {"AGGREGATION" "ROWS"}}))
 
 (expect
   {:query {:aggregation [:rows]}}
@@ -1084,24 +1082,30 @@
                                                              "average-length" 15.63}}}}]}))
 
 (deftest normalize-nil-values-in-native-maps-test
-  (testing "nil values in native query maps (e.g. MongoDB queries) should not get removed during normalization"
-    (testing "keys in native query maps should not get normalized"
-      (let [native-query        {:projections [:count]
-                                 :query       [{"$project" {"price" "$price"}}
-                                               {"$match" {"price" {"$eq" 1}}}
-                                               {"$group" {"_id" nil, "count" {"$sum" 1}}}
-                                               {"$sort" {"_id" 1}}
-                                               {"$project" {"_id" false, "count" true}}]
-                                 :collection  "venues"}
-            native-source-query (set/rename-keys native-query {:query :native})]
-        (doseq [[message query] {"top-level native query"
-                                 {:native native-query}
-
-                                 "native source query"
-                                 {:query {:source-query native-source-query}}
-
-                                 "native source query in join"
-                                 {:query {:joins [{:source-query native-source-query}]}}}]
-          (testing message
-            (is (= query
-                   (normalize/normalize query)))))))))
+  (testing "nil values in native query maps (e.g. MongoDB queries) should not get removed during normalization.\n"
+    (letfn [(test-normalization [native-query]
+              (let [native-source-query (set/rename-keys native-query {:query :native})]
+                (doseq [[message query] {"top-level native query"
+                                         {:native native-query}
+
+                                         "native source query"
+                                         {:query {:source-query native-source-query}}
+
+                                         "native source query in join"
+                                         {:query {:joins [{:source-query native-source-query}]}}}]
+                  (testing (str "\n" message)
+                    (is (= query
+                           (normalize/normalize query)))))))]
+
+      (testing "Keys in native query maps should not get normalized"
+        (test-normalization
+         {:projections [:count]
+          :query       [{"$project" {"price" "$price"}}
+                        {"$match" {"price" {"$eq" 1}}}
+                        {"$group" {"_id" nil, "count" {"$sum" 1}}}
+                        {"$sort" {"_id" 1}}
+                        {"$project" {"_id" false, "count" true}}]
+          :collection  "venues"}))
+
+      (testing "`nil` values inside native :params shouldn't get removed"
+        (test-normalization {:query "SELECT ?" :params [nil]})))))
-- 
GitLab