From 778051588028d235360a9af76d1cdbd79f1893eb Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Fri, 16 Oct 2026 14:35:04 +1100 Subject: [PATCH v9] 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 "HAS ' QUOTE") broke out of the string literal and produced a syntax error on the server. The failure happened when querying to find publications involving this schema. Fixed by using a string escape function instead of raw-substitution to the SQL. Author: Peter Smith Reviewed-by: Steven Niu Reviewed by: Jim Jones Reviewed-by: Nishant Sharma Reviewed-by: surya poondla Reviewed-by: Ajin Cherian Discussion: https://www.postgresql.org/message-id/flat/SA5PPF230C70D4BFBD6006437E357AE7CF9A7CB2%40SA5PPF230C70D4B.namprd14.prod.outlook.com#c4b1b145852d2076db6590e0aae5c7da --- src/bin/psql/describe.c | 25 +++-- src/test/regress/expected/publication.out | 114 ++++++++++++++++++++++ src/test/regress/sql/publication.sql | 37 +++++++ 3 files changed, 167 insertions(+), 9 deletions(-) diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index e4a4ba47280..2e112f635c8 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -5201,20 +5201,27 @@ listSchemas(const char *pattern, bool verbose, bool showSystem) myopt.title = _("List of schemas"); myopt.translate_header = true; - if (pattern && pset.sversion >= 150000) + /* + * \dn returns a LIST of schemas, so adding a publications footer to the + * describe output makes sense only if the list has a single row. + */ + if (pattern && pset.sversion >= 150000 && PQntuples(res) == 1) { PGresult *result; int i; + const char *schema_name = PQgetvalue(res, 0, 0); addQueryComment(&buf, "Get publications that publish this schema"); - appendPQExpBuffer(&buf, - "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); + + appendPQExpBufferStr(&buf, + "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 = "); + appendStringLiteralConn(&buf, schema_name, pset.db); + appendPQExpBufferStr(&buf, "\nORDER 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 b55da39fde5..56faa945606 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -171,6 +171,120 @@ 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; ^ +-- create schema names with embedded quotes or backslashes +-- should be able to describe them in psql +CREATE SCHEMA "test-schema-embedded ' quote"; +CREATE SCHEMA "test-schema-embedded \ backslash"; +\dn "test-schema-embedded ' quote" + List of schemas + Name | Owner +------------------------------+-------------------------- + test-schema-embedded ' quote | regress_publication_user +(1 row) + +\dn "test-schema-embedded \ backslash" + List of schemas + Name | Owner +----------------------------------+-------------------------- + test-schema-embedded \ backslash | regress_publication_user +(1 row) + +\dn+ test-schema-embedded* + List of schemas + Name | Owner | Access privileges | Description +----------------------------------+--------------------------+-------------------+------------- + test-schema-embedded ' quote | regress_publication_user | | + test-schema-embedded \ backslash | regress_publication_user | | +(2 rows) + +-- should be able to publish schemas with embedded quotes +-- or backslashes, and see that they are published +CREATE TABLE "test-schema-embedded ' quote"."test-table-embedded ' quote"("test-col1-embedded ' quote" int, "test-col2-embedded ' quote" int); +CREATE TABLE "test-schema-embedded \ backslash".tab1(a int); +CREATE PUBLICATION regress_pub_embedded1 + FOR TABLES IN SCHEMA "test-schema-embedded ' quote", "test-schema-embedded \ backslash"; +CREATE PUBLICATION regress_pub_embedded2 FOR ALL TABLES EXCEPT (TABLE "test-schema-embedded ' quote"."test-table-embedded ' quote"); +\dn "test-schema-embedded ' quote" + List of schemas + Name | Owner +------------------------------+-------------------------- + test-schema-embedded ' quote | regress_publication_user +Included in publications: + "regress_pub_embedded1" + +\dn "test-schema-embedded \ backslash" + List of schemas + Name | Owner +----------------------------------+-------------------------- + test-schema-embedded \ backslash | regress_publication_user +Included in publications: + "regress_pub_embedded1" + +\d "test-schema-embedded ' quote"."test-table-embedded ' quote" + Table "test-schema-embedded ' quote.test-table-embedded ' quote" + Column | Type | Collation | Nullable | Default +----------------------------+---------+-----------+----------+--------- + test-col1-embedded ' quote | integer | | | + test-col2-embedded ' quote | integer | | | +Included in publications: + "regress_pub_embedded1" + "testpub_foralltables" +Excluded from publications: + "regress_pub_embedded2" + +\dRp+ regress_pub_embedded* + Publication regress_pub_embedded1 + 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: + "test-schema-embedded ' quote" + "test-schema-embedded \ backslash" + + Publication regress_pub_embedded2 + 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: + "test-schema-embedded ' quote.test-table-embedded ' quote" + +DROP PUBLICATION regress_pub_embedded1, regress_pub_embedded2; +DROP SCHEMA "test-schema-embedded ' quote" CASCADE; +NOTICE: drop cascades to table "test-schema-embedded ' quote"."test-table-embedded ' quote" +DROP SCHEMA "test-schema-embedded \ backslash" CASCADE; +NOTICE: drop cascades to table "test-schema-embedded \ backslash".tab1 +-- test describe and publication of some other tricky schema names +CREATE SCHEMA "MixedCase"; +CREATE SCHEMA "2200"; +CREATE PUBLICATION regress_pub_tricky_schemas FOR TABLES IN SCHEMA "MixedCase", "2200"; +\dn "MixedCase" + List of schemas + Name | Owner +-----------+-------------------------- + MixedCase | regress_publication_user +Included in publications: + "regress_pub_tricky_schemas" + +\dn 2200 + List of schemas + Name | Owner +------+-------------------------- + 2200 | regress_publication_user +Included in publications: + "regress_pub_tricky_schemas" + +\dRp+ regress_pub_tricky_schemas + Publication regress_pub_tricky_schemas + 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: + "2200" + "MixedCase" + +DROP PUBLICATION regress_pub_tricky_schemas; +DROP SCHEMA "MixedCase" CASCADE; +DROP SCHEMA "2200" CASCADE; -- 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 e3dbb2bc57c..6c9ee367a1e 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -90,6 +90,43 @@ 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; +-- create schema names with embedded quotes or backslashes +-- should be able to describe them in psql +CREATE SCHEMA "test-schema-embedded ' quote"; +CREATE SCHEMA "test-schema-embedded \ backslash"; +\dn "test-schema-embedded ' quote" +\dn "test-schema-embedded \ backslash" +\dn+ test-schema-embedded* + +-- should be able to publish schemas with embedded quotes +-- or backslashes, and see that they are published +CREATE TABLE "test-schema-embedded ' quote"."test-table-embedded ' quote"("test-col1-embedded ' quote" int, "test-col2-embedded ' quote" int); +CREATE TABLE "test-schema-embedded \ backslash".tab1(a int); + +CREATE PUBLICATION regress_pub_embedded1 + FOR TABLES IN SCHEMA "test-schema-embedded ' quote", "test-schema-embedded \ backslash"; +CREATE PUBLICATION regress_pub_embedded2 FOR ALL TABLES EXCEPT (TABLE "test-schema-embedded ' quote"."test-table-embedded ' quote"); + +\dn "test-schema-embedded ' quote" +\dn "test-schema-embedded \ backslash" +\d "test-schema-embedded ' quote"."test-table-embedded ' quote" +\dRp+ regress_pub_embedded* + +DROP PUBLICATION regress_pub_embedded1, regress_pub_embedded2; +DROP SCHEMA "test-schema-embedded ' quote" CASCADE; +DROP SCHEMA "test-schema-embedded \ backslash" CASCADE; + +-- test describe and publication of some other tricky schema names +CREATE SCHEMA "MixedCase"; +CREATE SCHEMA "2200"; +CREATE PUBLICATION regress_pub_tricky_schemas FOR TABLES IN SCHEMA "MixedCase", "2200"; +\dn "MixedCase" +\dn 2200 +\dRp+ regress_pub_tricky_schemas +DROP PUBLICATION regress_pub_tricky_schemas; +DROP SCHEMA "MixedCase" CASCADE; +DROP SCHEMA "2200" CASCADE; + -- 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