From d6bc5e93876c6421e97300937fcaf27735d4f80a Mon Sep 17 00:00:00 2001
From: metamben <103100869+metamben@users.noreply.github.com>
Date: Thu, 4 Aug 2022 21:39:47 +0300
Subject: [PATCH] Recursively nest expressions (#24404)

---
 .../23862-cc-group-by-nested.cy.spec.js       |  2 +-
 .../query_processor/util/nest_query.clj       | 21 ++--
 .../driver/sql/query_processor_test.clj       | 51 ++++++----
 .../query_processor/util/nest_query_test.clj  | 97 ++++++++++++-------
 .../nested_queries_test.clj                   | 14 +++
 5 files changed, 120 insertions(+), 65 deletions(-)

diff --git a/frontend/test/metabase/scenarios/custom-column/reproductions/23862-cc-group-by-nested.cy.spec.js b/frontend/test/metabase/scenarios/custom-column/reproductions/23862-cc-group-by-nested.cy.spec.js
index a1d4ba5a04a..35d59a628af 100644
--- a/frontend/test/metabase/scenarios/custom-column/reproductions/23862-cc-group-by-nested.cy.spec.js
+++ b/frontend/test/metabase/scenarios/custom-column/reproductions/23862-cc-group-by-nested.cy.spec.js
@@ -22,7 +22,7 @@ const questionDetails = {
   },
 };
 
-describe.skip("issue 23862", () => {
+describe("issue 23862", () => {
   beforeEach(() => {
     restore();
     cy.signInAsAdmin();
diff --git a/src/metabase/query_processor/util/nest_query.clj b/src/metabase/query_processor/util/nest_query.clj
index 36dfe1fd8da..2f91124162c 100644
--- a/src/metabase/query_processor/util/nest_query.clj
+++ b/src/metabase/query_processor/util/nest_query.clj
@@ -100,13 +100,14 @@
   "Pushes the `:source-table`/`:source-query`, `:expressions`, and `:joins` in the top-level of the query into a
   `:source-query` and updates `:expression` references and `:field` clauses with `:join-alias`es accordingly. See
   tests for examples. This is used by the SQL QP to make sure expressions happen in a subselect."
-  [{:keys [expressions], :as query}]
-  (if (empty? expressions)
-    query
-    (let [{:keys [source-query], :as query} (nest-source query)
-          query                             (rewrite-fields-and-expressions query)
-          source-query                      (assoc source-query :expressions expressions)]
-      (-> query
-          (dissoc :source-query :expressions)
-          (assoc :source-query source-query)
-          add/add-alias-info))))
+  [query]
+  (let [{:keys [expressions], :as query} (m/update-existing query :source-query nest-expressions)]
+    (if (empty? expressions)
+      query
+      (let [{:keys [source-query], :as query} (nest-source query)
+            query                             (rewrite-fields-and-expressions query)
+            source-query                      (assoc source-query :expressions expressions)]
+        (-> query
+            (dissoc :source-query :expressions)
+            (assoc :source-query source-query)
+            add/add-alias-info)))))
diff --git a/test/metabase/driver/sql/query_processor_test.clj b/test/metabase/driver/sql/query_processor_test.clj
index 17cf559a156..a4bc882044b 100644
--- a/test/metabase/driver/sql/query_processor_test.clj
+++ b/test/metabase/driver/sql/query_processor_test.clj
@@ -347,14 +347,22 @@
                     source.LONGITUDE   AS LONGITUDE
                     source.PRICE       AS PRICE
                     source.double_id   AS double_id]
-           :from   [{:select [VENUES.ID          AS ID
-                              VENUES.NAME        AS NAME
-                              VENUES.CATEGORY_ID AS CATEGORY_ID
-                              VENUES.LATITUDE    AS LATITUDE
-                              VENUES.LONGITUDE   AS LONGITUDE
-                              VENUES.PRICE       AS PRICE
-                              (VENUES.ID * 2)    AS double_id]
-                     :from   [VENUES]}
+           :from   [{:select [source.ID          AS ID
+                              source.NAME        AS NAME
+                              source.CATEGORY_ID AS CATEGORY_ID
+                              source.LATITUDE    AS LATITUDE
+                              source.LONGITUDE   AS LONGITUDE
+                              source.PRICE       AS PRICE
+                              source.double_id   AS double_id]
+                     :from   [{:select [VENUES.ID AS ID
+                                        VENUES.NAME AS NAME
+                                        VENUES.CATEGORY_ID AS CATEGORY_ID
+                                        VENUES.LATITUDE    AS LATITUDE
+                                        VENUES.LONGITUDE   AS LONGITUDE
+                                        VENUES.PRICE       AS PRICE
+                                        (VENUES.ID * 2)    AS double_id]
+                               :from   [VENUES]}
+                              source]}
                     source]
            :limit  [1]}
          (-> (mt/mbql-query venues
@@ -843,19 +851,24 @@
                            Q1.CC           AS Q1__CC]
                :from      [{:select [source.CATEGORY AS CATEGORY
                                      source.count    AS count
-                                     (1 + 1)         AS CC]
-                            :from   [{:select   [PRODUCTS.CATEGORY AS CATEGORY
-                                                 count (*)         AS count]
-                                      :from     [PRODUCTS]
-                                      :group-by [PRODUCTS.CATEGORY]
-                                      :order-by [PRODUCTS.CATEGORY ASC]}
-                                     source]} source]
+                                     source.CC       AS CC]
+                            :from   [{:select [source.CATEGORY AS CATEGORY
+                                               source.count    AS count
+                                               (1 + 1)         AS CC]
+                                      :from   [{:select   [PRODUCTS.CATEGORY AS CATEGORY
+                                                           count (*)         AS count]
+                                                :from     [PRODUCTS]
+                                                :group-by [PRODUCTS.CATEGORY]
+                                                :order-by [PRODUCTS.CATEGORY ASC]}
+                                               source]}
+                                     source]}
+                           source]
                :left-join [{:select [source.CATEGORY AS CATEGORY
-                                     source.count AS count
-                                     source.CC AS CC]
+                                     source.count    AS count
+                                     source.CC       AS CC]
                             :from   [{:select [source.CATEGORY AS CATEGORY
-                                               source.count AS count
-                                               (1 + 1) AS CC]
+                                               source.count    AS count
+                                               (1 + 1)         AS CC]
                                       :from   [{:select   [PRODUCTS.CATEGORY AS CATEGORY
                                                            count (*)         AS count]
                                                 :from     [PRODUCTS]
diff --git a/test/metabase/query_processor/util/nest_query_test.clj b/test/metabase/query_processor/util/nest_query_test.clj
index c4e411f84eb..abbc5010b18 100644
--- a/test/metabase/query_processor/util/nest_query_test.clj
+++ b/test/metabase/query_processor/util/nest_query_test.clj
@@ -214,41 +214,68 @@
                    :limit        1})]
       (mt/with-native-query-testing-context query
         (is (partial= (mt/$ids venues
-                        {:fields       [[:field %id {::add/source-table  ::add/source
-                                                     ::add/source-alias  "ID"
-                                                     ::add/desired-alias "ID"
-                                                     ::add/position      0}]
-                                        [:field "x_2" {:base-type          :type/Float
-                                                       ::add/source-table  ::add/source
-                                                       ::add/source-alias  "x_2"
-                                                       ::add/desired-alias "x_2"
-                                                       ::add/position      1}]]
-                         :source-query {:expressions  {"x" [:*
-                                                            [:field %price {::add/source-table ::add/source
-                                                                            ::add/source-alias "PRICE"}]
-                                                            4]}
-                                        :fields       [[:field %id {::add/source-table  ::add/source
-                                                                    ::add/source-alias  "ID"
-                                                                    ::add/desired-alias "ID"
-                                                                    ::add/position      0}]
-                                                       [:field "x" {:base-type          :type/Float
-                                                                    ::add/source-table  ::add/source
-                                                                    ::add/source-alias  "x"
-                                                                    ::add/desired-alias "x"
-                                                                    ::add/position      1}]
-                                                       [:expression "x" {::add/desired-alias "x_2"
-                                                                         ::add/position      2}]]
-                                        :source-query {:source-table $$venues
-                                                       :expressions  {"x" [:*
-                                                                           [:field %price {::add/source-table $$venues
-                                                                                           ::add/source-alias "PRICE"}]
-                                                                           2]}
-                                                       :fields       [[:field %id {::add/source-table  $$venues
-                                                                                   ::add/source-alias  "ID"
-                                                                                   ::add/desired-alias "ID"
-                                                                                   ::add/position      0}]
-                                                                      [:expression "x" {::add/desired-alias "x"
-                                                                                        ::add/position      1}]]}}
+                        {:fields
+                         [[:field %id #::add{:source-table  ::add/source
+                                             :source-alias  "ID"
+                                             :desired-alias "ID"
+                                             :position      0}]
+                          [:field "x_2" {:base-type          :type/Float
+                                         ::add/source-table  ::add/source
+                                         ::add/source-alias  "x_2"
+                                         ::add/desired-alias "x_2"
+                                         ::add/position      1}]]
+                         :source-query
+                         {:expressions
+                          {"x" [:*
+                                [:field %price #::add{:source-table ::add/source
+                                                      :source-alias "PRICE"}]
+                                4]}
+                          :fields
+                          [[:field %id #::add{:source-table  ::add/source
+                                              :source-alias  "ID"
+                                              :desired-alias "ID"}]
+                           [:field "x" {:base-type          :type/Float
+                                        ::add/source-table  ::add/source
+                                        ::add/source-alias  "x"
+                                        ::add/desired-alias "x"}]
+                           [:expression "x" #::add{:desired-alias "x_2"}]]
+                          :source-query
+                          {:fields
+                           [[:field %id #::add{:source-table  ::add/source
+                                               :source-alias  "ID"
+                                               :desired-alias "ID"}]
+                            [:field "x" {:base-type          :type/Float
+                                         ::add/source-table  ::add/source
+                                         ::add/source-alias  "x"
+                                         ::add/desired-alias "x"}]]
+                           :source-query
+                           {:source-table $$venues
+                            :expressions
+                            {"x" [:*
+                                  [:field %price #::add{:source-table  $$venues
+                                                        :source-alias  "PRICE"
+                                                        :desired-alias "PRICE"}]
+                                  2]}
+                            :fields
+                            [[:field %id #::add{:source-table  $$venues
+                                                :source-alias  "ID"
+                                                :desired-alias "ID"}]
+                             [:field %name #::add{:source-table  $$venues
+                                                  :source-alias  "NAME"
+                                                  :desired-alias "NAME"}]
+                             [:field %category_id #::add{:source-table  $$venues
+                                                         :source-alias  "CATEGORY_ID"
+                                                         :desired-alias "CATEGORY_ID"}]
+                             [:field %latitude #::add{:source-table  $$venues
+                                                      :source-alias  "LATITUDE"
+                                                      :desired-alias "LATITUDE"}]
+                             [:field %longitude #::add{:source-table  $$venues
+                                                       :source-alias  "LONGITUDE"
+                                                       :desired-alias "LONGITUDE"}]
+                             [:field %price #::add{:source-table  $$venues
+                                                   :source-alias  "PRICE"
+                                                   :desired-alias "PRICE"}]
+                             [:expression "x" #::add{:desired-alias "x"}]]}}}
                          :limit        1})
                       (nest-expressions query))))))
   (testing "Ignores source-query from joins (#20809)"
diff --git a/test/metabase/query_processor_test/nested_queries_test.clj b/test/metabase/query_processor_test/nested_queries_test.clj
index 73d8731f271..b195b7427ed 100644
--- a/test/metabase/query_processor_test/nested_queries_test.clj
+++ b/test/metabase/query_processor_test/nested_queries_test.clj
@@ -267,6 +267,20 @@
                       {:aggregation [:count]
                        :breakout    [$price]}))))))))))
 
+(deftest grouped-expression-in-card-test
+  (testing "Nested grouped expressions work (#23862)."
+    (mt/with-temp Card [card {:dataset_query
+                              (mt/mbql-query venues
+                                {:aggregation [[:count]]
+                                 :breakout [[:expression "Price level"]]
+                                 :expressions {"Price level" [:case [[[:> $price 2] "expensive"]] {:default "budget"}]}
+                                 :limit 2})}]
+      (is (= [["budget"    81]
+              ["expensive" 19]]
+             (mt/rows
+              (qp/process-query
+               (query-with-source-card card))))))))
+
 (deftest card-id-native-source-queries-test
   (let [run-native-query
         (fn [sql]
-- 
GitLab