diff --git a/modules/drivers/mongo/src/metabase/driver/mongo.clj b/modules/drivers/mongo/src/metabase/driver/mongo.clj index 1edbb9609388e922421bb5557e5d056f682683d6..4971081aec3ad0869d41dc0d8f6ad7c13526b711 100644 --- a/modules/drivers/mongo/src/metabase/driver/mongo.clj +++ b/modules/drivers/mongo/src/metabase/driver/mongo.clj @@ -264,8 +264,9 @@ :inner-join true :left-join true :nested-fields true - :nested-queries true + :native-parameter-card-reference false :native-parameters true + :nested-queries true :set-timezone true :standard-deviation-aggregations true :test/jvm-timezone-setting false diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj index b6fc21b214f7abc7c5d4a37ffb5c77e7d383a81e..b463d203edb95c77c3e8757299d31e3169469668 100644 --- a/src/metabase/driver.clj +++ b/src/metabase/driver.clj @@ -563,6 +563,16 @@ ;; subselects in SQL queries. :nested-queries + ;; Does this driver support native template tag parameters of type `:card`, e.g. in a native query like + ;; + ;; SELECT * FROM {{card}} + ;; + ;; do we support substituting `{{card}}` with another compiled (nested) query? + ;; + ;; By default, this is true for drivers that support `:native-parameters` and `:nested-queries`, but drivers can opt + ;; out if they do not support Card ID template tag parameters. + :native-parameter-card-reference + ;; Does the driver support persisting models :persist-models ;; Is persisting enabled? @@ -712,6 +722,13 @@ :upload-with-auto-pk true}] (defmethod database-supports? [::driver feature] [_driver _feature _db] supported?)) +;;; By default a driver supports `:native-parameter-card-reference` if it supports `:native-parameters` AND +;;; `:nested-queries`. +(defmethod database-supports? [::driver :native-parameter-card-reference] + [driver _feature database] + (and (database-supports? driver :native-parameters database) + (database-supports? driver :nested-queries database))) + (defmulti ^String escape-alias "Escape a `column-or-table-alias` string in a way that makes it valid for your database. This method is used for existing columns; aggregate functions and other expressions; joined tables; and joined subqueries; be sure to return diff --git a/src/metabase/driver/common/parameters/values.clj b/src/metabase/driver/common/parameters/values.clj index f48338a3edfc593ab3bf2af0b83a77f3c83015f5..5db7278f3b503906a0d68c90af2068b6b5091526 100644 --- a/src/metabase/driver/common/parameters/values.clj +++ b/src/metabase/driver/common/parameters/values.clj @@ -404,3 +404,13 @@ :tags tags :params params} e))))) + +(mu/defn referenced-card-ids :- [:set ::lib.schema.id/card] + "Return a set of all Card IDs referenced in the parameters in `params-map`. This should be added to the (inner) query + under the `:metabase.models.query.permissions/referenced-card-ids` key when doing parameter expansion." + [params-map :- [:map-of ::lib.schema.common/non-blank-string ParsedParamValue]] + (into #{} + (keep (fn [param] + (when (params/ReferencedCardQuery? param) + (:card-id param)))) + (vals params-map))) diff --git a/src/metabase/driver/sql.clj b/src/metabase/driver/sql.clj index 2172e10323ecad7e303cb0881a2edf2c259d69b8..09e669ddd7dcc54539ae5d12295d4c452c869c7f 100644 --- a/src/metabase/driver/sql.clj +++ b/src/metabase/driver/sql.clj @@ -1,6 +1,7 @@ (ns metabase.driver.sql "Shared code for all drivers that use SQL under the hood." (:require + [clojure.set :as set] [metabase.driver :as driver] [metabase.driver.common.parameters.parse :as params.parse] [metabase.driver.common.parameters.values :as params.values] @@ -55,12 +56,16 @@ (mu/defmethod driver/substitute-native-parameters :sql [_driver {:keys [query] :as inner-query} :- [:and [:map-of :keyword :any] [:map {:query ::lib.schema.common/non-blank-string}]]] - (let [[query params] (-> query - params.parse/parse - (sql.params.substitute/substitute (params.values/query->params-map inner-query)))] - (assoc inner-query - :query query - :params params))) + (let [params-map (params.values/query->params-map inner-query) + referenced-card-ids (params.values/referenced-card-ids params-map) + [query params] (-> query + params.parse/parse + (sql.params.substitute/substitute params-map))] + (cond-> (assoc inner-query + :query query + :params params) + (seq referenced-card-ids) + (update :metabase.models.query.permissions/referenced-card-ids set/union referenced-card-ids)))) ;;; +----------------------------------------------------------------------------------------------------------------+ diff --git a/src/metabase/lib/schema.cljc b/src/metabase/lib/schema.cljc index 5730ff8332f237e467a7bb23c02cabfafbc9b51e..f07bc9e65b7cb3237467a3ae36ae409967895685 100644 --- a/src/metabase/lib/schema.cljc +++ b/src/metabase/lib/schema.cljc @@ -60,6 +60,10 @@ ;; optional template tag declarations. Template tags are things like `{{x}}` in the query (the value of the ;; `:native` key), but their definition lives under this key. [:template-tags {:optional true} [:ref ::template-tag/template-tag-map]] + ;; optional, set of Card IDs referenced by this query in `:card` template tags like `{{card}}`. This is added + ;; automatically during parameter expansion. To run a native query you must have native query permissions as well + ;; as permissions for any Cards' parent Collections used in `:card` template tag parameters. + [:metabase.models.query.permissions/referenced-card-ids {:optional true} [:maybe [:set ::id/card]]] ;; ;; TODO -- parameters?? ] diff --git a/src/metabase/models/query/permissions.clj b/src/metabase/models/query/permissions.clj index 25a199232944aebc563036e76f298c6e17636b1e..f9ac93904dd9b15d6599988b4cec206103b93949 100644 --- a/src/metabase/models/query/permissions.clj +++ b/src/metabase/models/query/permissions.clj @@ -4,6 +4,7 @@ as a Card. Saved Cards are subject to the permissions of the Collection to which they belong." (:require [clojure.set :as set] + [clojure.walk :as walk] [metabase.api.common :as api] [metabase.legacy-mbql.normalize :as mbql.normalize] [metabase.lib.core :as lib] @@ -45,17 +46,17 @@ ;; Is calculating permissions for queries complicated? Some would say so. Refer to this handy flow chart to see how ;; things get calculated. ;; -;; perms-set -;; | -;; | -;; | -;; native query? <--------+---------> mbql query? -;; ↓ ↓ -;; {:perms/create-queries :query-builder-and-native} legacy-mbql-required-perms -;; | -;; no source card <--------+------> has source card -;; ↓ ↓ -;; {:perms/view-data {table-id :unrestricted}} source-card-read-perms +;; perms-set +;; | +;; | +;; | +;; native query? <--------+---------> mbql query? +;; ↓ ↓ +;; native-query-perms legacy-mbql-required-perms +;; | +;; no source card <--------+------> has source card +;; ↓ ↓ +;; {:perms/view-data {table-id :unrestricted}} source-card-read-perms ;; (mu/defn query->source-table-ids :- [:set [:or [:= ::native] ::lib.schema.id/table]] @@ -103,6 +104,30 @@ (binding [api/*current-user-id* nil] ((requiring-resolve 'metabase.query-processor.preprocess/preprocess) query))) +(defn- referenced-card-ids + "Return the union of all the `::referenced-card-ids` sets anywhere in the query." + [query] + (let [all-ids (atom #{})] + (walk/postwalk + (fn [form] + (when (map? form) + (when-let [ids (not-empty (::referenced-card-ids form))] + (swap! all-ids set/union ids))) + form) + query) + (not-empty @all-ids))) + +(defn- native-query-perms + [query] + (merge + {:perms/create-queries :query-builder-and-native + :perms/view-data :unrestricted} + (when-let [card-ids (referenced-card-ids query)] + {:paths (into #{} + (mapcat (fn [card-id] + (mi/perms-objects-set (card-instance card-id) :read))) + card-ids)}))) + (defn- legacy-mbql-required-perms [query {:keys [throw-exceptions? already-preprocessed?]}] (try @@ -117,7 +142,7 @@ table-ids (filter integer? table-ids-or-native) native? (.contains ^clojure.lang.PersistentVector table-ids-or-native ::native)] (merge - {:perms/view-data :unrestricted} + (native-query-perms query) (when (seq table-ids) {:perms/create-queries (zipmap table-ids (repeat :query-builder))}) (when native? @@ -151,8 +176,7 @@ {} (let [query-type (lib/normalized-query-type query)] (case query-type - :native {:perms/create-queries :query-builder-and-native - :perms/view-data :unrestricted} + :native (native-query-perms query) :query (legacy-mbql-required-perms query perms-opts) :mbql/query (pmbql-required-perms query perms-opts) (throw (ex-info (tru "Invalid query type: {0}" query-type) diff --git a/test/metabase/driver/common/parameters/values_test.clj b/test/metabase/driver/common/parameters/values_test.clj index 5ea90b9c850dff7c99d753389b58f755d40b772a..76f6300e6256fc444e26e2ff09d802eb70917148 100644 --- a/test/metabase/driver/common/parameters/values_test.clj +++ b/test/metabase/driver/common/parameters/values_test.clj @@ -837,3 +837,25 @@ {:type :date/month-year :value "2023-01" :target [:dimension [:template-tag "createdAt"]]}]}))))))))) + +(deftest ^:parallel referenced-card-ids-test + (mt/with-temp [:model/Card {card-1-id :id} {:collection_id nil + :dataset_query (mt/mbql-query venues {:limit 2})} + :model/Card {card-2-id :id} {:collection_id nil + :dataset_query (mt/native-query + {:query "SELECT * FROM {{card}}" + :template-tags {"card" {:name "card" + :display-name "card" + :type :card + :card-id card-1-id}}})}] + ;; even tho Card 2 references Card 1, we don't want to include it in the set of referenced Card IDs, since you + ;; should only need permissions for Card 2 to be able to run the query (see #15131) + (testing (format "Card 1 ID = %d, Card 2 ID = %d" card-1-id card-2-id) + (mt/with-metadata-provider (mt/id) + (is (=? #{card-2-id} + (params.values/referenced-card-ids (params.values/query->params-map + {:query "SELECT * FROM {{card}}" + :template-tags {"card" {:name "card" + :display-name "card" + :type :card + :card-id card-2-id}}})))))))) diff --git a/test/metabase/models/query/permissions_test.clj b/test/metabase/models/query/permissions_test.clj index 2695e891c48402a9646597242d571f9626762fa2..bd5b3aee4b5bcce0d3c2058fb3e39dc711c4417c 100644 --- a/test/metabase/models/query/permissions_test.clj +++ b/test/metabase/models/query/permissions_test.clj @@ -263,3 +263,42 @@ (is (= {:perms/view-data :unrestricted :perms/create-queries :query-builder-and-native} (query-perms/required-perms query)))))) + +(deftest ^:parallel native-query-referenced-card-permissions-test + (testing "Check permissions for native query card reference parameters (the `:metabase.models.query.permissions/referenced-card-ids` key(s))" + (mt/with-temp [:model/Collection {collection-1-id :id} {} + :model/Collection {collection-2-id :id} {} + :model/Card {card-1-id :id} {:collection_id collection-1-id} + :model/Card {card-2-id :id} {:collection_id collection-2-id}] + (testing "native query" + (is (= {:perms/create-queries :query-builder-and-native + :perms/view-data :unrestricted + :paths #{(format "/collection/%d/read/" collection-1-id) + (format "/collection/%d/read/" collection-2-id)}} + (query-perms/required-perms + {:database (mt/id) + :type :native + :native "SELECT * FROM (SELECT * FROM whatever);" + ::query-perms/referenced-card-ids #{card-1-id card-2-id}})))) + (testing "MBQL query with native source queries" + (let [native-query {:database (mt/id) + :type :query + :query {:source-query {:native "SELECT * FROM (SELECT * FROM whatever);" + ::query-perms/referenced-card-ids #{card-1-id}} + :joins [{:alias "J" + :source-query {:native "SELECT * FROM (SELECT * FROM whatever);" + ::query-perms/referenced-card-ids #{card-2-id}} + :condition [:= true false]}]}}] + (is (= {:perms/create-queries :query-builder-and-native + :perms/view-data :unrestricted + :paths #{(format "/collection/%d/read/" collection-1-id) + (format "/collection/%d/read/" collection-2-id)}} + (query-perms/required-perms native-query))) + (testing "pMBQL query" + (is (= {:perms/create-queries :query-builder-and-native + :perms/view-data :unrestricted + :paths #{(format "/collection/%d/read/" collection-1-id) + (format "/collection/%d/read/" collection-2-id)}} + (query-perms/required-perms + (lib/query (lib.metadata.jvm/application-database-metadata-provider (mt/id)) + (lib/->pMBQL native-query))))))))))) diff --git a/test/metabase/query_processor/middleware/parameters/native_test.clj b/test/metabase/query_processor/middleware/parameters/native_test.clj index 455896520cf7c2d59a8f5a324d402f596e190aef..11f243ae16335d9254a068ef57e724a6e86f11d1 100644 --- a/test/metabase/query_processor/middleware/parameters/native_test.clj +++ b/test/metabase/query_processor/middleware/parameters/native_test.clj @@ -1,6 +1,7 @@ (ns metabase.query-processor.middleware.parameters.native-test (:require [clojure.test :refer :all] + [metabase.driver :as driver] [metabase.models.card :refer [Card]] [metabase.query-processor.middleware.parameters.native :as qp.native] [metabase.test :as mt] @@ -31,3 +32,27 @@ [:native ms/NonBlankString] [:params [:= ["G%"]]]] (qp.native/expand-inner query)))))))))) + +(deftest ^:parallel native-query-with-card-template-tag-include-referenced-card-ids-test + (mt/test-drivers (mt/normal-drivers-with-feature :native-parameters :nested-queries :native-parameter-card-reference) + (testing "Expanding a Card template tag should add the card ID(s) to `:metabase.models.query.permissions/referenced-card-ids`" + (mt/with-temp [:model/Card {card-1-id :id} {:collection_id nil + :dataset_query (mt/mbql-query venues {:limit 2})} + :model/Card {card-2-id :id} {:collection_id nil + :dataset_query (mt/native-query + {:query (mt/native-query-with-card-template-tag driver/*driver* "card") + :template-tags {"card" {:name "card" + :display-name "card" + :type :card + :card-id card-1-id}}})}] + (testing (format "Card 1 ID = %d, Card 2 ID = %d" card-1-id card-2-id) + ;; this SHOULD NOT include `card-1-id`, because Card 1 is only referenced indirectly; if you have permissions + ;; to run Card 2 that should be sufficient to run it even if it references Card 1 (see #15131) + (mt/with-metadata-provider (mt/id) + (is (=? {:metabase.models.query.permissions/referenced-card-ids #{Integer/MAX_VALUE card-2-id}} + (qp.native/expand-inner {:query (mt/native-query-with-card-template-tag driver/*driver* "card") + :template-tags {"card" {:name "card" + :display-name "card" + :type :card + :card-id card-2-id}} + :metabase.models.query.permissions/referenced-card-ids #{Integer/MAX_VALUE}}))))))))) diff --git a/test/metabase/query_processor/middleware/parameters_test.clj b/test/metabase/query_processor/middleware/parameters_test.clj index 5045a1f0775d296f33e17d55a6de6335590a339d..7dff651202edd6ff2d0fbabbd205b4be37f9e140 100644 --- a/test/metabase/query_processor/middleware/parameters_test.clj +++ b/test/metabase/query_processor/middleware/parameters_test.clj @@ -226,32 +226,32 @@ (deftest ^:parallel expand-multiple-referenced-cards-in-template-tags (testing "multiple sub-queries, referenced in template tags, are correctly substituted" (qp.store/with-metadata-provider mock-native-query-cards-metadata-provider - (is (= (native-query - {:query "SELECT COUNT(*) FROM (SELECT 1) AS c1, (SELECT 2) AS c2", :params []}) - (substitute-params - (native-query - {:query (str "SELECT COUNT(*) FROM {{#" 1 "}} AS c1, {{#" 2 "}} AS c2") - :template-tags (card-template-tags [1 2])}))))))) + (is (=? (native-query + {:query "SELECT COUNT(*) FROM (SELECT 1) AS c1, (SELECT 2) AS c2", :params []}) + (substitute-params + (native-query + {:query (str "SELECT COUNT(*) FROM {{#" 1 "}} AS c1, {{#" 2 "}} AS c2") + :template-tags (card-template-tags [1 2])}))))))) (deftest ^:parallel expand-multiple-referenced-cards-in-template-tags-2 (testing "multiple CTE queries, referenced in template tags, are correctly substituted" (qp.store/with-metadata-provider mock-native-query-cards-metadata-provider - (is (= (native-query - {:query "WITH c1 AS (SELECT 1), c2 AS (SELECT 2) SELECT COUNT(*) FROM c1, c2", :params []}) - (substitute-params - (native-query - {:query "WITH c1 AS {{#1}}, c2 AS {{#2}} SELECT COUNT(*) FROM c1, c2" - :template-tags (card-template-tags [1 2])}))))))) + (is (=? (native-query + {:query "WITH c1 AS (SELECT 1), c2 AS (SELECT 2) SELECT COUNT(*) FROM c1, c2", :params []}) + (substitute-params + (native-query + {:query "WITH c1 AS {{#1}}, c2 AS {{#2}} SELECT COUNT(*) FROM c1, c2" + :template-tags (card-template-tags [1 2])}))))))) (deftest ^:parallel expand-multiple-referenced-cards-in-template-tags-3 (testing "recursive native queries, referenced in template tags, are correctly substituted" (qp.store/with-metadata-provider mock-native-query-cards-metadata-provider - (is (= (native-query - {:query "SELECT COUNT(*) FROM (SELECT * FROM (SELECT 1) AS c1) AS c2", :params []}) - (substitute-params - (native-query - {:query "SELECT COUNT(*) FROM {{#3}} AS c2" - :template-tags (card-template-tags [3])}))))))) + (is (=? (native-query + {:query "SELECT COUNT(*) FROM (SELECT * FROM (SELECT 1) AS c1) AS c2", :params []}) + (substitute-params + (native-query + {:query "SELECT COUNT(*) FROM {{#3}} AS c2" + :template-tags (card-template-tags [3])}))))))) (deftest ^:parallel expand-multiple-referenced-cards-in-template-tags-4 (testing "recursive native/MBQL queries, referenced in template tags, are correctly substituted" @@ -269,12 +269,12 @@ "\"PUBLIC\".\"VENUES\".\"LONGITUDE\" AS \"LONGITUDE\", " "\"PUBLIC\".\"VENUES\".\"PRICE\" AS \"PRICE\" " "FROM \"PUBLIC\".\"VENUES\"")] - (is (= (native-query - {:query (str "SELECT COUNT(*) FROM (SELECT * FROM (" card-1-subquery ") AS c1) AS c2") :params []}) - (substitute-params - (native-query - {:query "SELECT COUNT(*) FROM {{#2}} AS c2" - :template-tags (card-template-tags [2])})))))))) + (is (=? (native-query + {:query (str "SELECT COUNT(*) FROM (SELECT * FROM (" card-1-subquery ") AS c1) AS c2") :params []}) + (substitute-params + (native-query + {:query "SELECT COUNT(*) FROM {{#2}} AS c2" + :template-tags (card-template-tags [2])})))))))) (deftest ^:parallel referencing-cards-with-parameters-test (testing "referencing card with parameter and default value substitutes correctly" @@ -289,12 +289,12 @@ :type :number :default "1" :required true}}})]) - (is (= (native-query - {:query "SELECT * FROM (SELECT 1) AS x", :params []}) - (substitute-params - (native-query - {:query "SELECT * FROM {{#1}} AS x" - :template-tags (card-template-tags [1])}))))))) + (is (=? (native-query + {:query "SELECT * FROM (SELECT 1) AS x", :params []}) + (substitute-params + (native-query + {:query "SELECT * FROM {{#1}} AS x" + :template-tags (card-template-tags [1])}))))))) (deftest ^:parallel referencing-cards-with-parameters-test-2 (testing "referencing card with parameter and NO default value, fails substitution" @@ -348,12 +348,12 @@ {:query "SELECT name, price FROM venues WHERE price > 2", :params nil}) (substitute-params (:dataset_query card))))) (testing "multiple snippets are expanded from saved sub-query" - (is (= (mt/native-query - {:query "SELECT * FROM (SELECT name, price FROM venues WHERE price > 2) AS x", :params []}) - (substitute-params - (mt/native-query - {:query (str "SELECT * FROM {{#" (:id card) "}} AS x") - :template-tags (card-template-tags [(:id card)])})))))))) + (is (=? (mt/native-query + {:query "SELECT * FROM (SELECT name, price FROM venues WHERE price > 2) AS x", :params []}) + (substitute-params + (mt/native-query + {:query (str "SELECT * FROM {{#" (:id card) "}} AS x") + :template-tags (card-template-tags [(:id card)])})))))))) (deftest ^:parallel include-card-parameters-test (testing "Expanding a Card reference should include its parameters (#12236)" diff --git a/test/metabase/query_processor_test/parameters_test.clj b/test/metabase/query_processor_test/parameters_test.clj index 7e902b735aa603b38a88c4dc72f11c91287ce953..7bff48e1d079b4170090114d95e25983ff079e47 100644 --- a/test/metabase/query_processor_test/parameters_test.clj +++ b/test/metabase/query_processor_test/parameters_test.clj @@ -4,11 +4,14 @@ (:require [clojure.string :as str] [clojure.test :refer :all] + [clojure.walk :as walk] [java-time.api :as t] [medley.core :as m] [metabase.driver :as driver] [metabase.lib.native :as lib-native] [metabase.models :refer [Card]] + [metabase.models.permissions :as perms] + [metabase.models.permissions-group :as perms-group] [metabase.query-processor :as qp] [metabase.query-processor.compile :as qp.compile] [metabase.test :as mt] @@ -512,3 +515,80 @@ :target [:variable [:template-tag "n"]] :slug "n" :value "30"}]})))))) + +(deftest sql-permissions-but-no-card-permissions-template-tag-test + (testing "If we have full SQL perms for a DW but no Card perms we shouldn't be able to include it with a ref or template tag" + (mt/test-drivers (mt/normal-drivers-with-feature :native-parameters :nested-queries :native-parameter-card-reference) + (mt/with-non-admin-groups-no-root-collection-perms + (mt/with-temp [:model/Collection {collection-1-id :id} {} + :model/Collection {collection-2-id :id} {} + + :model/Card + {card-1-id :id} + {:collection_id collection-1-id + :dataset_query (mt/mbql-query venues {:fields [$id $name] + :order-by [[:asc $id]] + :limit 2})} + + :model/Card + {card-2-id :id, :as card-2} + {:collection_id collection-2-id + :dataset_query (mt/native-query + {:query (mt/native-query-with-card-template-tag driver/*driver* "card") + :template-tags {"card" {:name "card" + :display-name "card" + :type :card + :card-id card-1-id}}})}] + (testing (format "\nCollection 1 ID = %d, Card 1 ID = %d; Collection 2 ID = %d, Card 2 ID = %d" + collection-1-id card-1-id collection-2-id card-2-id) + (mt/with-test-user :rasta + (testing "Sanity check: shouldn't be able to run Card as MBQL query" + (is (thrown-with-msg? + clojure.lang.ExceptionInfo + #"You do not have permissions to view Card \d+" + (qp/process-query {:database (mt/id), :type :query, :query {:source-table (format "card__%d" card-2-id)}})))) + (testing "Sanity check: SHOULD be able to run a native query" + (testing (str "COMPILED = \n" (u/pprint-to-str (qp.compile/compile (:dataset_query card-2)))) + (is (= [[1 "Red Medicine"] + [2 "Stout Burgers & Beers"]] + (mt/formatted-rows + [int str] + (qp/process-query {:database (mt/id) + :type :native + :native (dissoc (qp.compile/compile (:dataset_query card-2)) + :metabase.models.query.permissions/referenced-card-ids)})))))) + (let [query (mt/native-query + {:query (mt/native-query-with-card-template-tag driver/*driver* "card") + :template-tags {"card" {:name "card" + :display-name "card" + :type :card + :card-id card-2-id}}})] + (testing "SHOULD NOT be able to run native query with Card ID template tag" + (is (thrown-with-msg? + clojure.lang.ExceptionInfo + #"\QYou do not have permissions to run this query.\E" + (qp/process-query query)))) + (testing "Exception should NOT include the compiled native query" + (try + (qp/process-query query) + (is (not ::here?) + "Should never get here, query should throw an Exception") + (catch Throwable e + (doseq [data (keep ex-data (u/full-exception-chain e))] + (walk/postwalk + (fn [form] + (when (string? form) + (is (not (re-find #"SELECT" form)))) + form) + data))))) + (testing (str "If we have permissions for Card 2's Collection (but not Card 1's) we should be able to" + " run a native query referencing Card 2, even tho it references Card 1 (#15131)") + (perms/grant-collection-read-permissions! (perms-group/all-users) collection-2-id) + ;; need to call [[mt/with-test-user]] again so [[metabase.api.common/*current-user-permissions-set*]] + ;; gets rebound with the updated permissions. This will be fixed in #45001 + (mt/with-test-user :rasta + (is (= [[1 "Red Medicine"] + [2 "Stout Burgers & Beers"]] + (mt/formatted-rows + [int str] + (qp/process-query query)))))))))))))) diff --git a/test/metabase/test.clj b/test/metabase/test.clj index e5b04bfa473ab418f6737a4a77a95c8f76cabc40..3fee3e59ead56896b595ead4c0e9ddec34e5c4a3 100644 --- a/test/metabase/test.clj +++ b/test/metabase/test.clj @@ -311,6 +311,7 @@ get-dataset-definition has-test-extensions? metabase-instance + native-query-with-card-template-tag sorts-nil-first? supports-time-type? supports-timestamptz-type?] diff --git a/test/metabase/test/data/interface.clj b/test/metabase/test/data/interface.clj index 9c7537a68ae691bb208678d4df940f46fbeec2e6..54d60b84b424c489e1a6288f778fc6fd869cf4f0 100644 --- a/test/metabase/test/data/interface.clj +++ b/test/metabase/test/data/interface.clj @@ -793,3 +793,13 @@ ;; Following cyclic dependency by that requiring resolve. ((requiring-resolve 'metabase.test.data.impl/resolve-dataset-definition) 'metabase.test.data.dataset-definitions 'test-data)) + +(defmulti native-query-with-card-template-tag + "For drivers that support `:native-parameter-card-reference`: + + Return a native `:query` (just the SQL string or equivalent with a `:card` template tag e.g. + + \"SELECT * FROM {{%s}}\"" + {:arglists '([driver card-template-tag-name])} + dispatch-on-driver-with-test-extensions + :hierarchy #'driver/hierarchy) diff --git a/test/metabase/test/data/sql.clj b/test/metabase/test/data/sql.clj index 0b050d78528b00e017dc0eafd995de76c543b392..569ec3baa783713ca0f633a57ac9f97f6751eb32 100644 --- a/test/metabase/test/data/sql.clj +++ b/test/metabase/test/data/sql.clj @@ -9,7 +9,9 @@ [metabase.query-processor.compile :as qp.compile] [metabase.test.data :as data] [metabase.test.data.interface :as tx] - [metabase.util.log :as log])) + [metabase.util :as u] + [metabase.util.log :as log] + [metabase.util.random :as u.random])) (comment metabase.driver.sql/keep-me) @@ -344,3 +346,8 @@ :hierarchy #'driver/hierarchy) (defmethod session-schema :sql/test-extensions [_] nil) + +(defmethod tx/native-query-with-card-template-tag :sql + [_driver card-template-tag-name] + (let [source-table-name (u/lower-case-en (u.random/random-name))] + (format "SELECT * FROM {{%s}} %s" card-template-tag-name source-table-name)))