diff --git a/src/metabase/lib/convert.cljc b/src/metabase/lib/convert.cljc index 8db742f516a746ba7f9716e408f20e4e0c74cdf2..93e31780ee6a4aa27aa8dc82731ce84c7e0ec52e 100644 --- a/src/metabase/lib/convert.cljc +++ b/src/metabase/lib/convert.cljc @@ -102,7 +102,14 @@ (defmethod ->pMBQL :mbql.stage/mbql [stage] - (let [aggregations (->pMBQL (:aggregation stage))] + (let [aggregations (->pMBQL (:aggregation stage)) + expressions (->> stage + :expressions + (mapv (fn [[k v]] + (-> v + ->pMBQL + (lib.util/named-expression-clause k)))) + not-empty)] (binding [*legacy-index->pMBQL-uuid* (into {} (map-indexed (fn [idx [_tag {ag-uuid :lib/uuid}]] [idx ag-uuid])) @@ -112,8 +119,8 @@ (if-not (get stage k) stage (update stage k ->pMBQL))) - (m/assoc-some stage :aggregation aggregations) - (disj stage-keys :aggregation))))) + (m/assoc-some stage :aggregation aggregations :expressions expressions) + (disj stage-keys :aggregation :expressions))))) (defmethod ->pMBQL :mbql/join [join] @@ -370,8 +377,18 @@ (-> stage disqualify (m/update-existing :aggregation #(mapv aggregation->legacy-MBQL %)) + (m/update-existing :expressions (fn [expressions] + (into {} + (for [expression expressions + :let [legacy-clause (->legacy-MBQL expression)]] + [(lib.util/expression-name expression) + ;; We wrap literals in :value ->pMBQL + ;; so unwrap this direction + (if (= :value (first legacy-clause)) + (second legacy-clause) + legacy-clause)])))) (update-list->legacy-boolean-expression :filters :filter)) - (disj stage-keys :aggregation :filters)))) + (disj stage-keys :aggregation :filters :expressions)))) (defmethod ->legacy-MBQL :mbql.stage/native [stage] (-> stage diff --git a/src/metabase/lib/dev.cljc b/src/metabase/lib/dev.cljc index e4c0f726f5c66a7c0af4cb4afa42323df8f2f0b5..ccad959fef4eacb8b230f13224aba4b9487a2b4b 100644 --- a/src/metabase/lib/dev.cljc +++ b/src/metabase/lib/dev.cljc @@ -69,7 +69,8 @@ (fn [query stage-number] (case expression-or-aggregation :expression - (if (contains? (:expressions (lib.util/query-stage query stage-number)) index-or-name) + (if (some (comp #{index-or-name} lib.util/expression-name) + (:expressions (lib.util/query-stage query stage-number))) (lib.options/ensure-uuid [:expression {} index-or-name]) (throw (ex-info (str "Undefined expression " index-or-name) {:expression-name index-or-name diff --git a/src/metabase/lib/expression.cljc b/src/metabase/lib/expression.cljc index 7db77dde0400c9c73f5844fe56281ba8485ed1eb..13e369dc947ff205fa5774ac6889cbba150588db 100644 --- a/src/metabase/lib/expression.cljc +++ b/src/metabase/lib/expression.cljc @@ -4,6 +4,7 @@ [+ - * / case coalesce abs time concat replace]) (:require [clojure.string :as str] + [medley.core :as m] [metabase.lib.common :as lib.common] [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.metadata :as lib.metadata] @@ -36,7 +37,8 @@ stage-number :- :int expression-name :- ::lib.schema.common/non-blank-string] (let [stage (lib.util/query-stage query stage-number)] - (or (get-in stage [:expressions expression-name]) + (or (m/find-first (comp #{expression-name} lib.util/expression-name) + (:expressions stage)) (throw (ex-info (i18n/tru "No expression named {0}" (pr-str expression-name)) {:expression-name expression-name :query query @@ -204,7 +206,9 @@ (lib.util/update-query-stage query stage-number update :expressions - assoc expression-name (lib.common/->op-arg query stage-number an-expression-clause))))) + (fnil conj []) + (-> (lib.common/->op-arg query stage-number an-expression-clause) + (lib.util/named-expression-clause expression-name)))))) (lib.common/defop + [x y & more]) (lib.common/defop - [x y & more]) @@ -257,11 +261,12 @@ ([query :- ::lib.schema/query stage-number :- :int] (some->> (not-empty (:expressions (lib.util/query-stage query stage-number))) - (mapv (fn [[expression-name expression-definition]] - (-> (lib.metadata.calculation/metadata query stage-number expression-definition) - (assoc :lib/source :source/expressions - :name expression-name - :display-name expression-name))))))) + (mapv (fn [expression-definition] + (let [expression-name (lib.util/expression-name expression-definition)] + (-> (lib.metadata.calculation/metadata query stage-number expression-definition) + (assoc :lib/source :source/expressions + :name expression-name + :display-name expression-name)))))))) (mu/defn expressions :- [:maybe ::lib.schema.expression/expressions] "Get the expressions map from a given stage of a `query`." diff --git a/src/metabase/lib/remove_replace.cljc b/src/metabase/lib/remove_replace.cljc index c4023a800c6cd272c4c102262b94e7fc3215ffcb..cd93619272fd01eaeeb971acb4bd232b3da27931 100644 --- a/src/metabase/lib/remove_replace.cljc +++ b/src/metabase/lib/remove_replace.cljc @@ -2,7 +2,6 @@ (:require [medley.core :as m] [metabase.lib.common :as lib.common] - [metabase.lib.expression :as lib.expression] [metabase.lib.join :as lib.join] [metabase.lib.metadata.calculation :as lib.metadata.calculation] [metabase.lib.util :as lib.util] @@ -15,11 +14,8 @@ join-condition-paths (for [idx join-indices] [:joins idx :conditions]) join-field-paths (for [idx join-indices] - [:joins idx :fields]) - expr-paths (for [[expr-name] (lib.expression/expressions query stage-number)] - [:expressions expr-name])] - (concat [[:order-by] [:breakout] [:filters] [:fields] [:aggregation]] - expr-paths + [:joins idx :fields])] + (concat [[:order-by] [:breakout] [:filters] [:fields] [:aggregation] [:expressions]] join-field-paths join-condition-paths))) @@ -62,17 +58,17 @@ (defn- remove-replace-location [query stage-number unmodified-query-for-stage location target-clause remove-replace-fn] (let [result (lib.util/update-query-stage query stage-number - remove-replace-fn location target-clause) + remove-replace-fn location target-clause) target-uuid (lib.util/clause-uuid target-clause)] (if (not= query result) (mbql.match/match-one location - [:expressions expr-name] + [:expressions] (-> result (remove-local-references stage-number unmodified-query-for-stage :expression - expr-name) + (lib.util/expression-name target-clause)) (remove-stage-references stage-number unmodified-query-for-stage target-uuid)) [:aggregation] @@ -99,13 +95,10 @@ (let [stage (lib.util/query-stage query stage-number) to-remove (mapcat (fn [location] - (when-let [sub-loc (get-in stage location)] - (let [clauses (if (lib.util/clause-uuid sub-loc) - [sub-loc] - sub-loc)] - (->> clauses - (keep #(mbql.match/match-one sub-loc - [target-op _ target-ref-id] [location %])))))) + (when-let [clauses (get-in stage location)] + (->> clauses + (keep #(mbql.match/match-one % + [target-op _ target-ref-id] [location %]))))) (stage-paths query stage-number))] (reduce (fn [query [location target-clause]] @@ -133,11 +126,8 @@ stage (lib.util/query-stage query stage-number) location (m/find-first (fn [possible-location] - (when-let [sub-loc (get-in stage possible-location)] - (let [clauses (if (lib.util/clause? sub-loc) - [sub-loc] - sub-loc) - target-uuid (lib.util/clause-uuid target-clause)] + (when-let [clauses (get-in stage possible-location)] + (let [target-uuid (lib.util/clause-uuid target-clause)] (when (some (comp #{target-uuid} :lib/uuid second) clauses) possible-location)))) (stage-paths query stage-number)) diff --git a/src/metabase/lib/schema.cljc b/src/metabase/lib/schema.cljc index 2eaeb5816cbe6a32003c93dc9d3738d1f7c7a50a..5facbc83dab06a67f2bf6d3b6ebbf135c34074fd 100644 --- a/src/metabase/lib/schema.cljc +++ b/src/metabase/lib/schema.cljc @@ -52,7 +52,7 @@ [:ref ::id/table-card-id-string]]) (defn- expression-ref-error-for-stage [stage] - (let [expression-names (set (keys (:expressions stage)))] + (let [expression-names (into #{} (map (comp :lib/expression-name second)) (:expressions stage))] (mbql.match/match-one (dissoc stage :joins :lib/stage-metadata) [:expression _opts (expression-name :guard (complement expression-names))] (str "Invalid :expression reference: no expression named " (pr-str expression-name))))) diff --git a/src/metabase/lib/schema/expression.cljc b/src/metabase/lib/schema/expression.cljc index ef150881173f630627ac38d7cd45fd3db5231a96..ae1075f48620d877230e04ace302bb08f1eb90d7 100644 --- a/src/metabase/lib/schema/expression.cljc +++ b/src/metabase/lib/schema/expression.cljc @@ -153,7 +153,5 @@ ;;; the `:expressions` definition map as found as a top-level key in an MBQL stage (mr/def ::expressions - [:map-of - {:min 1, :error/message ":expressions definition map of expression name -> expression"} - ::common/non-blank-string - ::expression]) + [:sequential {:min 1} [:and [:ref ::expression] + [:cat :any [:map [:lib/expression-name :string]] [:* :any]]]]) diff --git a/src/metabase/lib/util.cljc b/src/metabase/lib/util.cljc index 88d1c7d3b6539d7696259a561290bef36f0930de..1a3ae79721d31b8106f1fd6878a7c8fa2bb3710f 100644 --- a/src/metabase/lib/util.cljc +++ b/src/metabase/lib/util.cljc @@ -14,6 +14,7 @@ [metabase.lib.options :as lib.options] [metabase.lib.schema :as lib.schema] [metabase.lib.schema.common :as lib.schema.common] + [metabase.lib.schema.expression :as lib.schema.expression] [metabase.lib.schema.id :as lib.schema.id] [metabase.mbql.util :as mbql.u] [metabase.shared.util.i18n :as i18n] @@ -49,18 +50,36 @@ (when (clause? clause) (get-in clause [1 :lib/uuid]))) +(defn expression-name + "Returns the :lib/expression-name of `clause`. Returns nil if `clause` is not a clause." + [clause] + (when (clause? clause) + (get-in clause [1 :lib/expression-name]))) + +(defn named-expression-clause + "Top level expressions must be clauses with :lib/expression-name, so if we get a literal, wrap it in :value." + [clause a-name] + (assoc-in + (if (clause? clause) + clause + [:value {:lib/uuid (str (random-uuid)) + :effective-type (lib.schema.expression/type-of clause)} + clause]) + [1 :lib/expression-name] a-name)) + (defn replace-clause "Replace the `target-clause` in `stage` `location` with `new-clause`. If a clause has :lib/uuid equal to the `target-clause` it is swapped with `new-clause`. If `location` contains no clause with `target-clause` no replacement happens." [stage location target-clause new-clause] {:pre [(clause? target-clause)]} - (m/update-existing-in - stage - location - (fn [clause-or-clauses] - (if (= :expressions (first location)) - new-clause + (let [new-clause (if (= :expressions (first location)) + (named-expression-clause new-clause (expression-name target-clause)) + new-clause)] + (m/update-existing-in + stage + location + (fn [clause-or-clauses] (->> (for [clause clause-or-clauses] (if (= (clause-uuid clause) (clause-uuid target-clause)) new-clause @@ -77,9 +96,7 @@ (if-let [target (get-in stage location)] (let [target-uuid (clause-uuid target-clause) [first-loc last-loc] [(first location) (last location)] - result (if (= :expressions first-loc) - nil - (into [] (remove (comp #{target-uuid} clause-uuid)) target))] + result (into [] (remove (comp #{target-uuid} clause-uuid)) target)] (cond (seq result) (assoc-in stage location result) diff --git a/test/metabase/lib/aggregation_test.cljc b/test/metabase/lib/aggregation_test.cljc index 68a1ff4426df33b89ad97e775da544d43df4bc4a..37b443f6a01023ebe3192e0d3ae3fa949e4e0130 100644 --- a/test/metabase/lib/aggregation_test.cljc +++ b/test/metabase/lib/aggregation_test.cljc @@ -168,10 +168,11 @@ :display-name "Sum of double-price"} (col-info-for-aggregation-clause (lib.tu/venues-query-with-last-stage - {:expressions {"double-price" [:* - {:lib/uuid (str (random-uuid))} - (lib.tu/field-clause :venues :price {:base-type :type/Integer}) - 2]}}) + {:expressions [[:* + {:lib/uuid (str (random-uuid)) + :lib/expression-name "double-price"} + (lib.tu/field-clause :venues :price {:base-type :type/Integer}) + 2]]}) [:sum {:lib/uuid (str (random-uuid))} [:expression {:base-type :type/Integer, :lib/uuid (str (random-uuid))} "double-price"]]))))) @@ -400,10 +401,8 @@ [{:lib/type :mbql.stage/mbql :source-table int? :expressions - {"double-price" - [:* {} [:field {:base-type :type/Integer, :effective-type :type/Integer} int?] 2] - "budget?" - [:< {} [:field {:base-type :type/Integer, :effective-type :type/Integer} int?] 2]} + [[:* {:lib/expression-name "double-price"} [:field {:base-type :type/Integer, :effective-type :type/Integer} int?] 2] + [:< {:lib/expression-name "budget?"} [:field {:base-type :type/Integer, :effective-type :type/Integer} int?] 2]] :aggregation [[:sum {} [:expression {} "double-price"]] [:count {}] diff --git a/test/metabase/lib/convert_test.cljc b/test/metabase/lib/convert_test.cljc index b5869dc37e3100c0139e5f78d5781bfd278ed7ff..e5cabac8ef99b4d8c9f1ced2caa0a3712706613f 100644 --- a/test/metabase/lib/convert_test.cljc +++ b/test/metabase/lib/convert_test.cljc @@ -268,7 +268,12 @@ :parameters [{:target [:dimension [:field 16 {:source-field 5}]], :type :category, :value [:param-value]}], - :type :native})) + :type :native} + + {:database 1 + :type :query + :query {:source-table 224 + :expressions {"a" 1}}})) (deftest ^:parallel round-trip-options-test (testing "Round-tripping (p)MBQL caluses with options (#30280)" diff --git a/test/metabase/lib/expression_test.cljc b/test/metabase/lib/expression_test.cljc index 5001464cf2dc0539965a19a2928ac16a44809307..850bf851703b47431a7ea27d9134f84a1b664536 100644 --- a/test/metabase/lib/expression_test.cljc +++ b/test/metabase/lib/expression_test.cljc @@ -1,5 +1,6 @@ (ns metabase.lib.expression-test (:require + #?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal])) [clojure.test :refer [deftest is testing]] [malli.core :as mc] [metabase.lib.core :as lib] @@ -8,8 +9,7 @@ [metabase.lib.schema :as lib.schema] [metabase.lib.schema.expression :as lib.schema.expression] [metabase.lib.test-metadata :as meta] - [metabase.lib.test-util :as lib.tu] - #?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal])))) + [metabase.lib.test-util :as lib.tu])) (comment lib/keep-me) @@ -20,9 +20,9 @@ :database (meta/id) :stages [{:lib/type :mbql.stage/mbql :source-table (meta/id :venues) - :expressions {"myadd" [:+ {:lib/uuid string?} - 1 - [:field {:base-type :type/Integer, :lib/uuid string?} (meta/id :venues :category-id)]]}}]} + :expressions [[:+ {:lib/uuid string? :lib/expression-name "myadd"} + 1 + [:field {:base-type :type/Integer, :lib/uuid string?} (meta/id :venues :category-id)]]]}]} (-> (lib/query-for-table-name meta/metadata-provider "VENUES") (lib/expression "myadd" (lib/+ 1 (lib/field "VENUES" "CATEGORY_ID"))) (dissoc :lib/metadata))))) @@ -89,20 +89,19 @@ :display-name "double-price" :lib/source :source/expressions} (lib.metadata.calculation/metadata - (lib.tu/venues-query-with-last-stage - {:expressions {"double-price" [:* - {:lib/uuid (str (random-uuid))} - (lib.tu/field-clause :venues :price {:base-type :type/Integer}) - 2]}}) - -1 - [:expression {:lib/uuid (str (random-uuid))} "double-price"])))) + (-> lib.tu/venues-query + (lib/expression "double-price" + (lib/* (lib.tu/field-clause :venues :price {:base-type :type/Integer}) 2))) + -1 + [:expression {:lib/uuid (str (random-uuid))} "double-price"])))) (deftest ^:parallel expression-references-in-fields-clause-test (let [query (lib.tu/venues-query-with-last-stage - {:expressions {"prev_month" [:+ - {:lib/uuid (str (random-uuid))} - (lib.tu/field-clause :users :last-login) - [:interval {:lib/uuid (str (random-uuid))} -1 :month]]} + {:expressions [[:+ + {:lib/uuid (str (random-uuid)) + :lib/expression-name "prev_month"} + (lib.tu/field-clause :users :last-login) + [:interval {:lib/uuid (str (random-uuid))} -1 :month]]] :fields [[:expression {:base-type :type/DateTime, :lib/uuid (str (random-uuid))} "prev_month"]]})] (is (=? [{:name "prev_month" :display-name "prev_month" @@ -122,12 +121,11 @@ (lib.metadata.calculation/display-name lib.tu/venues-query -1 clause))))) (deftest ^:parallel expression-reference-names-test - (let [query (assoc-in lib.tu/venues-query - [:stages 0 :expressions "double-price"] - [:* - {:lib/uuid (str (random-uuid))} - (lib.tu/field-clause :venues :price {:base-type :type/Integer}) - 2]) + (let [query (-> lib.tu/venues-query + (lib/expression "double-price" + (lib/* + (lib.tu/field-clause :venues :price {:base-type :type/Integer}) + 2))) expr [:sum {:lib/uuid (str (random-uuid))} [:expression {:lib/uuid (str (random-uuid))} "double-price"]]] @@ -144,17 +142,12 @@ (lib.metadata.calculation/display-name lib.tu/venues-query -1 clause))))) (defn- infer-first - ([expr] - (infer-first expr nil)) - - ([expr last-stage] - (lib.metadata.calculation/metadata - (lib.tu/venues-query-with-last-stage - (merge - {:expressions {"expr" expr}} - last-stage)) + [expr] + (lib.metadata.calculation/metadata + (-> lib.tu/venues-query + (lib/expression "expr" expr)) -1 - [:expression {:lib/uuid (str (random-uuid))} "expr"]))) + [:expression {:lib/uuid (str (random-uuid))} "expr"])) (deftest ^:parallel infer-coalesce-test (testing "Coalesce" @@ -198,25 +191,26 @@ :display-name "last-login-plus-2" :lib/source :source/expressions} (lib.metadata.calculation/metadata - (lib.tu/venues-query-with-last-stage - {:expressions {"last-login-plus-2" [:datetime-add - {:lib/uuid (str (random-uuid))} - (lib.tu/field-clause :users :last-login {:base-type :type/DateTime}) - 2 - :hour]}}) + (-> lib.tu/venues-query + (lib/expression "last-login-plus-2" + [:datetime-add + {:lib/uuid (str (random-uuid))} + (lib.tu/field-clause :users :last-login {:base-type :type/DateTime}) + 2 + :hour])) -1 [:expression {:lib/uuid (str (random-uuid))} "last-login-plus-2"])))) (deftest ^:parallel col-info-for-expression-error-message-test (testing "if there is no matching expression it should give a meaningful error message" (is (thrown-with-msg? - #?(:clj Throwable :cljs js/Error) - #"No expression named \"double-price\"" - (lib.metadata.calculation/metadata - (lib.tu/venues-query-with-last-stage - {:expressions {"one-hundred" 100}}) - -1 - [:expression {:lib/uuid (str (random-uuid))} "double-price"]))))) + #?(:clj Throwable :cljs js/Error) + #"No expression named \"double-price\"" + (lib.metadata.calculation/metadata + (-> lib.tu/venues-query + (lib/expression "one-hundred" (lib/+ 100 0))) + -1 + [:expression {:lib/uuid (str (random-uuid))} "double-price"]))))) (deftest ^:parallel arithmetic-expression-type-of-test (testing "Make sure we can calculate correct type information for arithmetic expression" @@ -262,3 +256,21 @@ (ex-message ex))) (is (= {:expression-name "ID"} (ex-data ex)))))) + +(deftest ^:parallel literal-expression-test + (is (=? [{:lib/type :metadata/field, + :base-type :type/Integer, + :name "expr", + :display-name "expr", + :lib/source :source/expressions}] + (-> (lib/query-for-table-name meta/metadata-provider "VENUES") + (lib/expression "expr" 100) + (lib/expressions-metadata)))) + (is (=? [[:value {:lib/expression-name "expr" :effective-type :type/Integer} 100]] + (-> (lib/query-for-table-name meta/metadata-provider "VENUES") + (lib/expression "expr" 100) + (lib/expressions)))) + (is (=? [[:value {:lib/expression-name "expr" :effective-type :type/Text} "value"]] + (-> (lib/query-for-table-name meta/metadata-provider "VENUES") + (lib/expression "expr" "value") + (lib/expressions))))) diff --git a/test/metabase/lib/remove_replace_test.cljc b/test/metabase/lib/remove_replace_test.cljc index e0163fb36fef66f077a95638c56b41c26a394f41..ae426057aeb7b658336a25be8e4837676ede7b63 100644 --- a/test/metabase/lib/remove_replace_test.cljc +++ b/test/metabase/lib/remove_replace_test.cljc @@ -197,7 +197,7 @@ (let [query (-> (lib/query-for-table-name meta/metadata-provider "VENUES") (lib/expression "a" (lib/field "VENUES" "ID")) (lib/expression "b" (lib/field "VENUES" "PRICE"))) - {expr-a "a" expr-b "b" :as expressions} (lib/expressions query)] + [expr-a expr-b :as expressions] (lib/expressions query)] (is (= 2 (count expressions))) (is (= 1 (-> query (lib/remove-clause expr-a) @@ -208,7 +208,7 @@ (lib/remove-clause expr-b) (lib/expressions)))) (testing "removing with dependent should cascade" - (is (=? {:stages [{:expressions {"b" expr-b} :order-by (symbol "nil #_\"key is not present.\"")} + (is (=? {:stages [{:expressions [expr-b] :order-by (symbol "nil #_\"key is not present.\"")} (complement :filters)]} (-> query (lib/order-by (lib.dev/ref-lookup :expression "a")) @@ -367,20 +367,21 @@ (let [query (-> (lib/query-for-table-name meta/metadata-provider "VENUES") (lib/expression "a" (lib/field (meta/id :venues :id))) (lib/expression "b" (lib/field (meta/id :venues :name)))) - {expr-a "a" expr-b "b" :as expressions} (lib/expressions query) + [expr-a expr-b :as expressions] (lib/expressions query) replaced (-> query (lib/replace-clause expr-a (lib/field (meta/id :venues :price)))) - {_repl-expr-a "a" repl-expr-b "b" :as replaced-expressions} (lib/expressions replaced)] + [_repl-expr-a repl-expr-b :as replaced-expressions] (lib/expressions replaced)] (is (= 2 (count expressions))) - (is (=? {"a" [:field {} (meta/id :venues :price)]} + (is (=? [[:field {:lib/expression-name "a"} (meta/id :venues :price)] + expr-b] replaced-expressions)) (is (not= expressions replaced-expressions)) (is (= 2 (count replaced-expressions))) (is (= expr-b repl-expr-b)) (testing "replacing with dependent should cascade" (is (=? {:stages [{:aggregation (symbol "nil #_\"key is not present.\"") - :expressions {"a" [:field {} (meta/id :venues :price)] - "b" expr-b}} + :expressions [[:field {:lib/expression-name "a"} (meta/id :venues :price)] + expr-b]} (complement :filters)]} (-> query (lib/aggregate (lib/sum (lib.dev/ref-lookup :expression "a"))) @@ -388,7 +389,12 @@ (lib/append-stage) ;; TODO Should be able to create a ref with lib/field [#29763] (lib/filter (lib/= [:field {:lib/uuid (str (random-uuid)) :base-type :type/Integer} "a"] 1)) - (lib/replace-clause 0 expr-a (lib/field "VENUES" "PRICE")))))))) + (lib/replace-clause 0 expr-a (lib/field "VENUES" "PRICE")))))) + (testing "replace with literal expression" + (is (=? {:stages [{:expressions [[:value {:lib/expression-name "a" :effective-type :type/Integer} 999] + expr-b]}]} + (-> query + (lib/replace-clause 0 expr-a 999))))))) (deftest ^:parallel replace-order-by-breakout-col-test (testing "issue #30980" diff --git a/test/metabase/lib/schema/expression/conditional_test.cljc b/test/metabase/lib/schema/expression/conditional_test.cljc index d660386b37625b0379e2633e445f81881d1205b4..5f83adf155948ce41d3aa4ddb42d9fb9a07a4182 100644 --- a/test/metabase/lib/schema/expression/conditional_test.cljc +++ b/test/metabase/lib/schema/expression/conditional_test.cljc @@ -63,13 +63,13 @@ :database (meta/id) :stages [{:lib/type :mbql.stage/mbql, :source-table (meta/id :venues) - :expressions {"expr" - [:coalesce - {:lib/uuid "455a9f5e-4996-4df9-82aa-01bc083b2efe"} + :expressions [[:coalesce + {:lib/uuid "455a9f5e-4996-4df9-82aa-01bc083b2efe" + :lib/expression-name "expr"} [:field {:base-type :type/Text, :lib/uuid "68443c43-f9de-45e3-9f30-8dfd5fef5af6"} (meta/id :venues :name)] - "bar"]}}]}))) + "bar"]]}]}))) (deftest ^:parallel case-type-of-with-fields-only-test ;; Ideally expression/type-of should have enough information to determine the types of fields. diff --git a/test/metabase/lib/schema_test.cljc b/test/metabase/lib/schema_test.cljc index 7a3a189fe53100a7b4a7a5bb28661e20dfb07353..2cc345119678329ce4d76bd28517f6e215fad673 100644 --- a/test/metabase/lib/schema_test.cljc +++ b/test/metabase/lib/schema_test.cljc @@ -68,7 +68,8 @@ (def ^:private valid-expression [:+ - {:lib/uuid (str (random-uuid))} + {:lib/uuid (str (random-uuid)) + :lib/expression-name "price + 2"} [:field {:lib/uuid (str (random-uuid))} 2] @@ -79,15 +80,15 @@ (me/humanize (mc/explain ::lib.schema/stage stage))) {:lib/type :mbql.stage/mbql :source-table 1 - :expressions {"price + 2" valid-expression} + :expressions [valid-expression] :fields [[:expression {:lib/uuid (str (random-uuid))} "price + 2"]]} nil {:lib/type :mbql.stage/mbql :source-table 1 - :expressions {"price + 1" valid-expression} - :fields [[:expression {:lib/uuid (str (random-uuid))} "price + 2"]]} - ["Invalid :expression reference: no expression named \"price + 2\""] + :expressions [valid-expression] + :fields [[:expression {:lib/uuid (str (random-uuid))} "price + 1"]]} + ["Invalid :expression reference: no expression named \"price + 1\""] {:lib/type :mbql.stage/mbql :source-table 1 @@ -107,7 +108,7 @@ [:field {:join-alias "Q1", :lib/uuid (str (random-uuid))} 2]]] :stages [{:lib/type :mbql.stage/mbql :source-table 3 - :expressions {"price + 2" valid-expression} + :expressions [valid-expression] :order-by [[:asc {:lib/uuid (str (random-uuid))} [:expression {:lib/uuid (str (random-uuid))} "price + 2"]]]} diff --git a/test/metabase/lib/stage_test.cljc b/test/metabase/lib/stage_test.cljc index e2cd8cf18286cb9b59df21f9b0500cabbecdf532..bb19c5846bea0e77dd31aa825536d2b81e67d63b 100644 --- a/test/metabase/lib/stage_test.cljc +++ b/test/metabase/lib/stage_test.cljc @@ -75,8 +75,8 @@ (let [query (-> (lib/query-for-table-name meta/metadata-provider "VENUES") (lib/expression "ID + 1" (lib/+ (lib/field "VENUES" "ID") 1)) (lib/expression "ID + 2" (lib/+ (lib/field "VENUES" "ID") 2)))] - (is (=? {:stages [{:expressions {"ID + 1" [:+ {} [:field {} (meta/id :venues :id)] 1] - "ID + 2" [:+ {} [:field {} (meta/id :venues :id)] 2]}}]} + (is (=? {:stages [{:expressions [[:+ {:lib/expression-name "ID + 1"} [:field {} (meta/id :venues :id)] 1] + [:+ {:lib/expression-name "ID + 2"} [:field {} (meta/id :venues :id)] 2]]}]} query)) query)) @@ -108,8 +108,8 @@ [:field {} (meta/id :venues :category-id)] [:field {:join-alias "Cat"} (meta/id :categories :id)]]] :fields :all}] - :expressions {"ID + 1" [:+ {} [:field {} (meta/id :venues :id)] 1] - "ID + 2" [:+ {} [:field {} (meta/id :venues :id)] 2]}}]} + :expressions [[:+ {:lib/expression-name "ID + 1"} [:field {} (meta/id :venues :id)] 1] + [:+ {:lib/expression-name "ID + 2"} [:field {} (meta/id :venues :id)] 2]]}]} query)) (let [metadata (lib.metadata.calculation/metadata query)] (is (=? [{:id (meta/id :venues :id), :name "ID", :lib/source :source/table-defaults} @@ -160,8 +160,8 @@ id-plus-1)) (let [query' (-> query (lib/with-fields [id-plus-1]))] - (is (=? {:stages [{:expressions {"ID + 1" [:+ {} [:field {} (meta/id :venues :id)] 1] - "ID + 2" [:+ {} [:field {} (meta/id :venues :id)] 2]} + (is (=? {:stages [{:expressions [[:+ {:lib/expression-name "ID + 1"} [:field {} (meta/id :venues :id)] 1] + [:+ {:lib/expression-name "ID + 2"} [:field {} (meta/id :venues :id)] 2]] :fields [[:expression {} "ID + 1"]]}]} query')) (testing "If `:fields` is specified, expressions should only come back if they are in `:fields`"