From 6752c30dfe834e10c1d0cdb99740235ffa64b1e7 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Thu, 30 Jul 2026 11:54:23 +1000 Subject: [PATCH v3] Fix escapes for psql describe schema patterns psql \dn was building the query with a raw '%s' substitution of the user-supplied pattern, so a pattern containing a single quote (\dn "it's a bug") broke out of the string literal and produced a syntax error on the server. Fixed by using a pattern escape function instead of raw-substitution. Author: Peter Smith Reviewed-by: Steven Niu Reviewed by: Jim Jones Reviewed-by: Nishant Sharma Discussion: https://www.postgresql.org/message-id/flat/SA5PPF230C70D4BFBD6006437E357AE7CF9A7CB2%40SA5PPF230C70D4B.namprd14.prod.outlook.com#c4b1b145852d2076db6590e0aae5c7da --- src/bin/psql/describe.c | 12 ++-- src/test/regress/expected/publication.out | 73 +++++++++++++++++++++++ src/test/regress/sql/publication.sql | 16 +++++ 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index ad9c8affb4f..635c77f993d 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -5327,10 +5327,14 @@ listSchemas(const char *pattern, bool verbose, bool showSystem) "SELECT pubname \n" "FROM pg_catalog.pg_publication p\n" " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n" - " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n" - "WHERE n.nspname = '%s'\n" - "ORDER BY 1", - pattern); + " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + NULL, "n.nspname", NULL, + NULL, NULL, NULL); + + appendPQExpBufferStr(&buf, "ORDER BY 1;"); + result = PSQLexec(buf.data); if (!result) goto error_return; diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index 9c6386d518a..9b2f8d04c88 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -171,6 +171,79 @@ CREATE PUBLICATION testpub_parsertst FOR TABLES IN SCHEMA foo, test.foo; ERROR: invalid schema name LINE 1: ...CATION testpub_parsertst FOR TABLES IN SCHEMA foo, test.foo; ^ +-- should be able to publish and describe schemas and tables with embedded quotes +CREATE SCHEMA "it's my schema"; +CREATE TABLE "it's my schema"."it's my schema table"("it's my col1" int, "it's my col2" int); +CREATE TABLE "it's my public table"("it's my col1" int, "it's my col2" int); +CREATE PUBLICATION regress_pub_embedded_quotes1 FOR TABLES IN SCHEMA "it's my schema"; +CREATE PUBLICATION regress_pub_embedded_quotes2 FOR TABLE "it's my schema"."it's my schema table"; +CREATE PUBLICATION regress_pub_embedded_quotes3 FOR TABLE "it's my public table"("it's my col1"); +CREATE PUBLICATION regress_pub_embedded_quotes4 FOR ALL TABLES EXCEPT (TABLE "it's my public table"); +\dn "it's my schema" + List of schemas + Name | Owner +----------------+-------------------------- + it's my schema | regress_publication_user +Included in publications: + "regress_pub_embedded_quotes1" + +\d "it's my schema"."it's my schema table" + Table "it's my schema.it's my schema table" + Column | Type | Collation | Nullable | Default +--------------+---------+-----------+----------+--------- + it's my col1 | integer | | | + it's my col2 | integer | | | +Included in publications: + "regress_pub_embedded_quotes1" + "regress_pub_embedded_quotes2" + "regress_pub_embedded_quotes4" + "testpub_foralltables" + +\d "it's my public table"; + Table "public.it's my public table" + Column | Type | Collation | Nullable | Default +--------------+---------+-----------+----------+--------- + it's my col1 | integer | | | + it's my col2 | integer | | | +Included in publications: + "regress_pub_embedded_quotes3" (it's my col1) + "testpub_foralltables" +Excluded from publications: + "regress_pub_embedded_quotes4" + +\dRp+ regress_pub_embedded_quotes* + Publication regress_pub_embedded_quotes1 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | f | f | t | t | t | t | none | f | +Tables from schemas: + "it's my schema" + + Publication regress_pub_embedded_quotes2 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | f | f | t | t | t | t | none | f | +Tables: + "it's my schema.it's my schema table" + + Publication regress_pub_embedded_quotes3 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | f | f | t | t | t | t | none | f | +Tables: + "public.it's my public table" (it's my col1) + + Publication regress_pub_embedded_quotes4 + Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description +--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+------------- + regress_publication_user | t | f | t | t | t | t | none | f | +Except tables: + "public.it's my public table" + +DROP PUBLICATION regress_pub_embedded_quotes1, regress_pub_embedded_quotes2, regress_pub_embedded_quotes3, regress_pub_embedded_quotes4; +DROP SCHEMA "it's my schema" CASCADE; +NOTICE: drop cascades to table "it's my schema"."it's my schema table" +DROP TABLE "it's my public table"; -- should be able to add a table of the same schema to the schema publication ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk; \dRp+ testpub_forschema diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql index fac54b02e27..0834b11ebad 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -90,6 +90,22 @@ RESET client_min_messages; CREATE PUBLICATION testpub_parsertst FOR TABLE pub_test.testpub_nopk, CURRENT_SCHEMA; CREATE PUBLICATION testpub_parsertst FOR TABLES IN SCHEMA foo, test.foo; +-- should be able to publish and describe schemas and tables with embedded quotes +CREATE SCHEMA "it's my schema"; +CREATE TABLE "it's my schema"."it's my schema table"("it's my col1" int, "it's my col2" int); +CREATE TABLE "it's my public table"("it's my col1" int, "it's my col2" int); +CREATE PUBLICATION regress_pub_embedded_quotes1 FOR TABLES IN SCHEMA "it's my schema"; +CREATE PUBLICATION regress_pub_embedded_quotes2 FOR TABLE "it's my schema"."it's my schema table"; +CREATE PUBLICATION regress_pub_embedded_quotes3 FOR TABLE "it's my public table"("it's my col1"); +CREATE PUBLICATION regress_pub_embedded_quotes4 FOR ALL TABLES EXCEPT (TABLE "it's my public table"); +\dn "it's my schema" +\d "it's my schema"."it's my schema table" +\d "it's my public table"; +\dRp+ regress_pub_embedded_quotes* +DROP PUBLICATION regress_pub_embedded_quotes1, regress_pub_embedded_quotes2, regress_pub_embedded_quotes3, regress_pub_embedded_quotes4; +DROP SCHEMA "it's my schema" CASCADE; +DROP TABLE "it's my public table"; + -- should be able to add a table of the same schema to the schema publication ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk; \dRp+ testpub_forschema -- 2.47.3