From 5d3335957841abda6f8050a115dc5c775667c4fa Mon Sep 17 00:00:00 2001 From: Ngoc Khuat <qn.khuat@gmail.com> Date: Wed, 13 Mar 2024 06:04:30 +0700 Subject: [PATCH] Postgres: Fix sync does not recognize schema with backslashes (#38946) --- src/metabase/driver.clj | 2 +- src/metabase/driver/postgres.clj | 10 ++++++++-- test/metabase/driver/postgres_test.clj | 15 ++++++++++++++- .../sql_jdbc/sync/describe_database_test.clj | 19 +++++++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj index 747cf969c48..b868f3439da 100644 --- a/src/metabase/driver.clj +++ b/src/metabase/driver.clj @@ -332,7 +332,7 @@ driver has difference escaping rules for table or schema names when used from metadata. For example, oracle treats slashes differently when querying versus when used with `.getTables` or `.getColumns`" - {:arglists '([driver table-name]), :added "0.37.0"} + {:arglists '([driver entity-name]), :added "0.37.0"} dispatch-on-initialized-driver :hierarchy #'hierarchy) diff --git a/src/metabase/driver/postgres.clj b/src/metabase/driver/postgres.clj index 9073ef746d2..046dd79132d 100644 --- a/src/metabase/driver/postgres.clj +++ b/src/metabase/driver/postgres.clj @@ -79,6 +79,12 @@ [driver _feat _db] (= driver :postgres))) +(defmethod driver/escape-entity-name-for-metadata :postgres [_driver entity-name] + (when entity-name + ;; these entities names are used as a pattern for LIKE queries in jdbc + ;; so we need to double escape it, first for java, then for sql + (str/replace entity-name "\\" "\\\\"))) + ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | metabase.driver impls | ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -862,8 +868,8 @@ ;; KNOWN LIMITATION: this won't return privileges for foreign tables, calling has_table_privilege on a foreign table ;; result in a operation not supported error (->> (jdbc/query - conn-spec - (str/join + conn-spec + (str/join "\n" ["with table_privileges as (" " select" diff --git a/test/metabase/driver/postgres_test.clj b/test/metabase/driver/postgres_test.clj index 9ce2f78d651..c50c460cb10 100644 --- a/test/metabase/driver/postgres_test.clj +++ b/test/metabase/driver/postgres_test.clj @@ -213,7 +213,20 @@ (mt/rows (qp/process-query {:database (u/the-id database) :type :query - :query {:source-table (t2/select-one-pk Table :name "presents-and-gifts")}})))))))))) + :query {:source-table (t2/select-one-pk Table :name "presents-and-gifts")}})))))))) + (testing "Make sure that Schemas / Tables / Fields with backslashes in their names get escaped properly" + (mt/with-empty-db + (let [conn-spec (sql-jdbc.conn/db->pooled-connection-spec (mt/db))] + (doseq [stmt ["CREATE SCHEMA \"my\\schema\";" + "CREATE TABLE \"my\\schema\".\"my\\table\" (\"my\\field\" INTEGER);" + "INSERT INTO \"my\\schema\".\"my\\table\" (\"my\\field\") VALUES (42);"]] + (jdbc/execute! conn-spec stmt)) + (sync/sync-database! (mt/db) {:scan :schema}) + (is (= [[42]] + (mt/rows (qp/process-query + {:database (u/the-id (mt/db)) + :type :query + :query {:source-table (t2/select-one-pk :model/Table :db_id (:id (mt/db)))}}))))))))) (mt/defdataset duplicate-names [["birds" diff --git a/test/metabase/driver/sql_jdbc/sync/describe_database_test.clj b/test/metabase/driver/sql_jdbc/sync/describe_database_test.clj index 0025b6b1d7a..df19f268b3e 100644 --- a/test/metabase/driver/sql_jdbc/sync/describe_database_test.clj +++ b/test/metabase/driver/sql_jdbc/sync/describe_database_test.clj @@ -208,3 +208,22 @@ driver/*driver* conn schema (str table-name "_should_not_exist")))) (is (true? (sql-jdbc.sync.interface/have-select-privilege? driver/*driver* conn schema table-name)))))))))))))) + +(deftest sync-table-with-backslash-test + (mt/test-drivers #{:postgres} ;; TODO: fix and change this to test on (mt/sql-jdbc-drivers) + (testing "table with backslash in name, PKs, FKS are correctly synced" + (mt/with-temp-test-data [["human\\race" + [{:field-name "humanraceid" :base-type :type/Integer :pk? true} + {:field-name "race" :base-type :type/Text}] + [[1 "homo sapiens"]]] + ["citizen" + [{:field-name "citizen\\id" :base-type :type/Integer :pk? true} + {:field-name "race\\id" :base-type :type/Integer :fk "human\\race"}] + [[1 1]]]] + (let [tables (t2/select :model/Table :db_id (:id (mt/db))) + field-name->field (t2/select-fn->fn :name identity :model/Field :table_id [:in (map :id tables)])] + (is (= #{"human\\race" "citizen"} (set (map :name tables)))) + (is (= #{"humanraceid" "citizen\\id" "race" "race\\id"} + (set (keys field-name->field)))) + (is (= (get-in field-name->field ["humanraceid" :id]) + (get-in field-name->field ["race\\id" :fk_target_field_id])))))))) -- GitLab