diff --git a/e2e/test/scenarios/native/reproductions/32121-sql-source-query-can-explore-results.cy.spec.js b/e2e/test/scenarios/native/reproductions/32121-sql-source-query-can-explore-results.cy.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..e748fed15ffe22287a6036607c6a2b313a6b19e2 --- /dev/null +++ b/e2e/test/scenarios/native/reproductions/32121-sql-source-query-can-explore-results.cy.spec.js @@ -0,0 +1,76 @@ +import { restore, saveQuestion, startNewQuestion } from "e2e/support/helpers"; + +const MONGO_DB_NAME = "QA Mongo4"; + +describe("issue 32121", () => { + describe("on SQL questions", () => { + beforeEach(() => { + restore(); + cy.signInAsAdmin(); + }); + + it("clicking 'Explore results' works (metabase#32121)", () => { + // Stepping all the way through the QB because I couldn't repro with canned `createNativeQuestion` JSON. + startNewQuestion(); + + // Query the entire Orders table, then convert to SQL. + cy.get("#DataPopover").findByText("Sample Database").click(); + cy.get("#DataPopover").findByText("Orders").click(); + cy.findByTestId("qb-header").find(".Icon-sql").click(); + cy.get(".Modal").findByText("Convert this question to SQL").click(); + + // Run the query. + cy.intercept("POST", "/api/dataset").as("dataset"); + cy.get(".NativeQueryEditor .Icon-play").click(); + cy.wait("@dataset"); + cy.findByTestId("question-row-count").contains( + "Showing first 2,000 rows", + ); + + // Save it. + saveQuestion("all Orders"); + + cy.findByTestId("qb-header").findByText("Explore results").click(); + cy.findByTestId("question-row-count").contains( + "Showing first 2,000 rows", + ); + }); + }); + + describe("on native Mongo questions", { tags: "@external" }, () => { + before(() => { + restore("mongo-4"); + cy.signInAsAdmin(); + + startNewQuestion(); + // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage + cy.findByText(MONGO_DB_NAME).click(); + // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage + cy.findByText("Orders").click(); + }); + + it("convert GUI question to native query, and 'Explore results' works (metabase#32121)", () => { + cy.get(".QueryBuilder .Icon-sql").click(); + + cy.get(".Modal") + .findByText("Convert this question to a native query") + .click(); + cy.get(".Modal").should("not.exist"); + + cy.intercept("POST", "/api/dataset").as("dataset"); + cy.get(".NativeQueryEditor .Icon-play").click(); + cy.wait("@dataset"); + + saveQuestion("all Orders"); + + cy.findByTestId("question-row-count").contains( + "Showing first 2,000 rows", + ); + + cy.findByTestId("qb-header").findByText("Explore results").click(); + cy.findByTestId("question-row-count").contains( + "Showing first 2,000 rows", + ); + }); + }); +}); diff --git a/src/metabase/query_processor/middleware/fetch_source_query.clj b/src/metabase/query_processor/middleware/fetch_source_query.clj index b8cf42322c57844b235ec497cef61cb5fba51896..8bfe825ecf1b724aeaa5df8fccf54b5196b84cbe 100644 --- a/src/metabase/query_processor/middleware/fetch_source_query.clj +++ b/src/metabase/query_processor/middleware/fetch_source_query.clj @@ -26,6 +26,7 @@ [clojure.string :as str] [medley.core :as m] [metabase.driver.ddl.interface :as ddl.i] + [metabase.driver.util :as driver.u] [metabase.mbql.normalize :as mbql.normalize] [metabase.mbql.schema :as mbql.s] [metabase.mbql.util :as mbql.u] @@ -86,7 +87,6 @@ (s/constrained query-has-resolved-database-id? "Query where source-query virtual `:database` has been replaced with actual Database ID"))) - ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | Resolving card__id -> source query | ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -106,21 +106,22 @@ (defn- source-query "Get the query to be run from the card" [{dataset-query :dataset_query card-id :id :as card}] - (let [{mbql-query :query + (let [{db-id :database + mbql-query :query {template-tags :template-tags :as native-query} :native} dataset-query] (or mbql-query ;; rename `:query` to `:native` because source queries have a slightly different shape (when-some [native-query (set/rename-keys native-query {:query :native})] - (let [collection (:collection native-query)] + (let [mongo? (= (driver.u/database->driver db-id) :mongo)] (cond-> native-query ;; MongoDB native queries consist of a collection and a pipelne (query) - collection - (update :native (fn [pipeline] {:collection collection + mongo? + (update :native (fn [pipeline] {:collection (:collection native-query) :query pipeline})) ;; trim trailing comments from SQL, but not other types of native queries - (and (nil? collection) + (and (not mongo?) (string? (:native native-query))) (update :native (partial trim-sql-query card-id)) @@ -171,7 +172,6 @@ (when-let [[_ card-id-str] (re-find #"^card__(\d+)$" source-table-str)] (Integer/parseInt card-id-str))) - ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | Logic for traversing the query | ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -290,7 +290,7 @@ extract-resolved-card-id)) (s/defn resolve-card-id-source-tables* :- {:card-id (s/maybe su/IntGreaterThanZero) - :query FullyResolvedQuery} + :query FullyResolvedQuery} "Resolve `card__n`-style `:source-tables` in `query`." [{inner-query :query, :as outer-query} :- mbql.s/Query] (if-not inner-query diff --git a/test/metabase/query_processor/middleware/fetch_source_query_test.clj b/test/metabase/query_processor/middleware/fetch_source_query_test.clj index 04002fc369c1330ddba16f381d90ca176957fd0b..590fc10e96738d95bd82a93d468a94bcb5ababbc 100644 --- a/test/metabase/query_processor/middleware/fetch_source_query_test.clj +++ b/test/metabase/query_processor/middleware/fetch_source_query_test.clj @@ -2,6 +2,7 @@ (:require [cheshire.core :as json] [clojure.test :refer :all] + [metabase.driver.util :as driver.u] [metabase.mbql.schema :as mbql.s] [metabase.models :refer [Card]] [metabase.query-processor :as qp] @@ -344,16 +345,17 @@ :collection "checkins" :mbql? true} :database (mt/id)}] - (t2.with-temp/with-temp [Card {card-id :id} {:dataset_query query}] - (is (= {:source-metadata nil - :source-query {:projections ["_id" "user_id" "venue_id"], - :native {:collection "checkins" - :query [{:$project {:_id "$_id"}} - {:$limit 1048575}]} - :collection "checkins" - :mbql? true} - :database (mt/id)} - (#'fetch-source-query/card-id->source-query-and-metadata card-id)))))) + (with-redefs [driver.u/database->driver (constantly :mongo)] + (t2.with-temp/with-temp [Card {card-id :id} {:dataset_query query}] + (is (= {:source-metadata nil + :source-query {:projections ["_id" "user_id" "venue_id"], + :native {:collection "checkins" + :query [{:$project {:_id "$_id"}} + {:$limit 1048575}]} + :collection "checkins" + :mbql? true} + :database (mt/id)} + (#'fetch-source-query/card-id->source-query-and-metadata card-id))))))) (testing "card-id->source-query-and-metadata-test should preserve mongodb native queries in string format (#30112)" (let [query-str (str "[{\"$project\":\n" " {\"_id\":\"$_id\",\n" @@ -364,10 +366,11 @@ :native {:query query-str :collection "checkins"} :database (mt/id)}] - (t2.with-temp/with-temp [Card {card-id :id} {:dataset_query query}] - (is (= {:source-metadata nil - :source-query {:native {:collection "checkins" - :query query-str} - :collection "checkins"} - :database (mt/id)} - (#'fetch-source-query/card-id->source-query-and-metadata card-id))))))) + (with-redefs [driver.u/database->driver (constantly :mongo)] + (t2.with-temp/with-temp [Card {card-id :id} {:dataset_query query}] + (is (= {:source-metadata nil + :source-query {:native {:collection "checkins" + :query query-str} + :collection "checkins"} + :database (mt/id)} + (#'fetch-source-query/card-id->source-query-and-metadata card-id))))))))