diff --git a/modules/drivers/redshift/test/metabase/driver/redshift_test.clj b/modules/drivers/redshift/test/metabase/driver/redshift_test.clj index ccd9a35906f5c6113d273868040c5b8057393be1..7e30896db12e243e24dd852d8e586bdd86b2121c 100644 --- a/modules/drivers/redshift/test/metabase/driver/redshift_test.clj +++ b/modules/drivers/redshift/test/metabase/driver/redshift_test.clj @@ -49,7 +49,7 @@ (deftest ^:parallel default-select-test (is (= ["SELECT \"source\".* FROM (SELECT *) AS \"source\""] - (->> {:from [[[::sql.qp/sql-source-query "SELECT *"] + (->> {:from [[{::sql.qp/sql-source-query ["SELECT *"]} [(h2x/identifier :table-alias "source")]]]} (#'sql.qp/add-default-select :redshift) (sql.qp/format-honeysql :redshift))))) diff --git a/src/metabase/driver/sql/query_processor.clj b/src/metabase/driver/sql/query_processor.clj index f16277f4ddf66b6dd7848b44c703c61d29f47d93..6c601150dbe30fe098a67fb18bb453449dbeff13 100644 --- a/src/metabase/driver/sql/query_processor.clj +++ b/src/metabase/driver/sql/query_processor.clj @@ -38,6 +38,18 @@ "The INNER query currently being processed, for situations where we need to refer back to it." nil) +(defn make-nestable-sql* + "See [[make-nestable-sql]] but does not wrap in result in parens." + [sql] + (-> sql + (str/replace #";([\s;]*(--.*\n?)*)*$" "") + str/trimr + (as-> trimmed + ;; Query could potentially end with a comment. + (if (re-find #"--.*$" trimmed) + (str trimmed "\n") + trimmed)))) + (defn make-nestable-sql "Do best effort edit to the `sql`, to make it nestable in subselect. @@ -53,23 +65,13 @@ probably require _parsing_ sql string and not just a regular expression replacement. Link to the discussion: https://github.com/metabase/metabase/pull/30677 - For the limitations see the [[metabase.driver.sql.query-processor-test/make-nestable-sql-test]]" - [sql] - (str "(" - (-> sql - (str/replace #";([\s;]*(--.*\n?)*)*$" "") - str/trimr - (as-> trimmed - ;; Query could potentially end with a comment. - (if (re-find #"--.*$" trimmed) - (str trimmed "\n") - trimmed))) - ")")) - -(defn- format-sql-source-query [_fn [sql params]] - (into [(make-nestable-sql sql)] params)) - -(sql/register-fn! ::sql-source-query #'format-sql-source-query) + For the limitations see the [[metabase.driver.sql.query-processor-test/make-nestable-sql-test]]" [sql] + (str "(" (make-nestable-sql* sql) ")")) + +(defn- format-sql-source-query [_clause [sql params]] + (into [(make-nestable-sql* sql)] params)) + +(sql/register-clause! ::sql-source-query #'format-sql-source-query :select) (defn sql-source-query "Wrap clause in `::sql-source-query`. Does additional validation." @@ -84,7 +86,7 @@ (.getCanonicalName (class params))) {:type qp.error-type/invalid-query :query params}))) - [::sql-source-query sql params]) + {::sql-source-query [sql params]}) ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | Interface (Multimethods) | @@ -1409,23 +1411,43 @@ (declare apply-clauses) (defn- apply-source-query - "Handle a `:source-query` clause by adding a recursive `SELECT` or native query. At the time of this writing, all - source queries are aliased as `source`." - [driver honeysql-form {{:keys [native params], - persisted :persisted-info/native - :as source-query} :source-query}] - (assoc honeysql-form - :from [[(cond - persisted - (sql-source-query persisted nil) - - native - (sql-source-query native params) - - :else - (apply-clauses driver {} source-query)) - (let [table-alias (->honeysql driver (h2x/identifier :table-alias source-query-alias))] - [table-alias])]])) + "Handle a `:source-query` clause by adding a recursive `SELECT` or native query. + If the source query has ambiguous column names, use a `WITH` statement to rename the source columns. + At the time of this writing, all source queries are aliased as `source`." + [driver honeysql-form {{:keys [native params] persisted :persisted-info/native :as source-query} :source-query + source-metadata :source-metadata}] + (let [table-alias (->honeysql driver (h2x/identifier :table-alias source-query-alias)) + source-clause (cond + persisted + (sql-source-query persisted nil) + + native + (sql-source-query native params) + + :else + (apply-clauses driver {} source-query)) + ;; TODO: Use MLv2 here to get source and desired-aliases + alias-info (mapv (fn [{[_ desired-ref-name] :field_ref source-name :name}] + [source-name desired-ref-name]) + source-metadata) + source-aliases (mapv first alias-info) + desired-aliases (mapv second alias-info) + duplicate-source-aliases? (and (> (count source-aliases) 1) + (not (apply distinct? source-aliases))) + needs-columns? (and (seq desired-aliases) + (> (count desired-aliases) 1) + duplicate-source-aliases? + (apply distinct? desired-aliases) + (every? string? desired-aliases))] + (merge + honeysql-form + (if needs-columns? + ;; HoneySQL cannot expand [::h2x/identifier :table "source"] in the with alias. + ;; This is ok since we control the alias. + {:with [[[source-query-alias {:columns (mapv #(h2x/identifier :field %) desired-aliases)}] + source-clause]] + :from [[table-alias]]} + {:from [[source-clause [table-alias]]]})))) (defn- apply-clauses "Like [[apply-top-level-clauses]], but handles `source-query` as well, which needs to be handled in a special way diff --git a/test/metabase/driver/sql/query_processor_test.clj b/test/metabase/driver/sql/query_processor_test.clj index 7314e101e04453319a98271948e9216d350d15c2..085c70a7ba3059de4246f6efa3f9524b169d6cf3 100644 --- a/test/metabase/driver/sql/query_processor_test.clj +++ b/test/metabase/driver/sql/query_processor_test.clj @@ -38,7 +38,7 @@ (testing "[[sql.qp/sql-source-query]] should throw Exceptions if you pass in invalid nonsense" (doseq [params [nil [1000]]] (testing (format "Params = %s" (pr-str params)) - (is (= [::sql.qp/sql-source-query "SELECT *" params] + (is (= {::sql.qp/sql-source-query ["SELECT *" params]} (sql.qp/sql-source-query "SELECT *" params))))) (is (thrown-with-msg? clojure.lang.ExceptionInfo diff --git a/test/metabase/driver/sql/query_processor_test_util.clj b/test/metabase/driver/sql/query_processor_test_util.clj index cca791ec6c5143ac3bcf57fbf2416b49015a76e6..c3ca041680595ed074444a9ddff93386e50e595c 100644 --- a/test/metabase/driver/sql/query_processor_test_util.clj +++ b/test/metabase/driver/sql/query_processor_test_util.clj @@ -5,6 +5,8 @@ [metabase.driver :as driver] [metabase.driver.util :as driver.u] [metabase.query-processor.compile :as qp.compile] + [metabase.test.data.env :as tx.env] + [metabase.test.data.interface :as tx] [metabase.util :as u])) (set! *warn-on-reflection* true) @@ -47,6 +49,7 @@ [FULL JOIN] [GROUP BY] [ORDER BY] + WITH SELECT FROM LIMIT @@ -151,3 +154,11 @@ `body`." [query & body] `(do-with-native-query-testing-context ~query (fn [] ~@body))) + +(defn sql-drivers + "All the drivers in the :sql hierarchy." + [] + (set + (for [driver (tx.env/test-drivers) + :when (isa? driver/hierarchy (driver/the-driver driver) (driver/the-driver :sql))] + (tx/the-driver-with-test-extensions driver)))) diff --git a/test/metabase/query_processor_test/native_test.clj b/test/metabase/query_processor_test/native_test.clj index 252a1f69e1bdd87b8bb4b5d79b6bd179d67101d9..0051478a2d3357090ef65b7eac231512d09b0c1a 100644 --- a/test/metabase/query_processor_test/native_test.clj +++ b/test/metabase/query_processor_test/native_test.clj @@ -1,6 +1,7 @@ (ns metabase.query-processor-test.native-test (:require [clojure.test :refer :all] + [metabase.driver.sql.query-processor-test-util :as sql.qp-test-util] [metabase.models.card :refer [Card]] [metabase.query-processor :as qp] [metabase.query-processor.test-util :as qp.test-util] @@ -37,6 +38,34 @@ (mt/native-query {:query "select name from users;"})))))) +(deftest ^:parallel native-with-duplicate-column-names + (testing "Should be able to run native query referring a question referring a question (#25988)" + (mt/with-test-drivers (sql.qp-test-util/sql-drivers) + (t2.with-temp/with-temp [:model/Card card {:dataset_query {:native {:query "select id, id from orders"} + :database (mt/id) + :type :native} + :result_metadata [{:base_type :type/BigInteger, + :display_name "ID", + :effective_type :type/BigInteger, + :field_ref [:field "ID" {:base-type :type/BigInteger}], + :fingerprint nil, + :name "ID", + :semantic_type :type/PK} + {:base_type :type/BigInteger, + :display_name "ID", + :effective_type :type/BigInteger, + :field_ref [:field "ID_2" {:base-type :type/BigInteger}], + :fingerprint nil, + :name "ID", + :semantic_type :type/PK}]}] + (is (=? {:columns ["ID" "ID_2"]} + (mt/rows+column-names + (qp/process-query + {:query {:source-table (str "card__" (:id card)) + :fields [[:field "ID" {:base-type :type/Integer}] [:field "ID_2" {:base-type :type/Integer}]]} + :database (mt/id) + :type :query})))))))) + (deftest ^:parallel native-referring-question-referring-question-test (testing "Should be able to run native query referring a question referring a question (#25988)" (mt/with-driver :h2