Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wPdP1-000uid-1m for pgsql-hackers@arkaria.postgresql.org; Wed, 20 May 2026 09:39:47 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wPdOz-006wJK-1g for pgsql-hackers@arkaria.postgresql.org; Wed, 20 May 2026 09:39:46 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wPdOz-006wJB-0M for pgsql-hackers@lists.postgresql.org; Wed, 20 May 2026 09:39:46 +0000 Received: from mail-qk1-x72e.google.com ([2607:f8b0:4864:20::72e]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wPdOw-00000000Szy-39N5 for pgsql-hackers@lists.postgresql.org; Wed, 20 May 2026 09:39:44 +0000 Received: by mail-qk1-x72e.google.com with SMTP id af79cd13be357-913cc4d7c71so458736685a.2 for ; Wed, 20 May 2026 02:39:43 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1779269983; cv=none; d=google.com; s=arc-20240605; b=dYF7IJ2smLmFxfscPNoRwrsNLFrI1KvzRIDqst9xHkRuAhgRrZr/rvuZCJAzS/Snv7 nsJp5Q8K7Hn17dsFSaMuHIMOa9Ii6w27zOGPt6Y1fDLa9jKkAWlqtDfQRzDx34EaCoLb 25eoo4ntjMzTE7YFKIYMwzzu9Gn5K9HGYC7BUh915dcOsNJ5WelnXjvT2HuTLrltL2Ab e6mk5VBPT9SghOgdPT5js5iHtdUId8IEMSHVIxn+cOAYhMWpZUkFjF4IzXcISyxeQSHO ka92XLRQcG8KtzKeKubo4W28pgq9sCOneDoUbVdajatK23Zvl/4arLN5qQ73YqDlCIKU fp8g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=lFqb92PY5okOz8fMXiDfI+BUOAxXFx/kYaJ+61wXDlY=; fh=/YpDvpsKzRXAFaF1FdEs/Vh1BM5FXrXkJQJpj2Z4iG4=; b=QxLhLQ8IHAg9kHMwDNC8TODTfrneVD6UPnQMbtj3bz03PPhmwaGUUXLyflPy6+ddJF HCoE6amyBQuLemJH0jUJ+QMYS9qgRzVVG4p/nvyfo16QaPE5DL75+O66pz1ukNahruXO PW+l9ccDwgIynDmj1wCz8pm+i38bnZbdUMnapL1P3onsYtD/KEZmK2IPOgaZQreSebRI bA+oHjzMEgQ9Ld+wSDyjLmOSSHGoKumXQj9ih6/JXSUkR5igwy1ViLss/lblr21vHrCI 9hhrXAVwiDeoh2aDnMGpGg4Dms8dLmkQ3Cq3k2r0ky2fskl4vecC727L0A8nZYafiUFP LQRA==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779269983; x=1779874783; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=lFqb92PY5okOz8fMXiDfI+BUOAxXFx/kYaJ+61wXDlY=; b=AQkhdHmbMvunpyiUgnmzC+mK2NTVPwewN/cp/2/ami+L8GFKG0/cpiVrlq2QN/7XXU gY7rgdbevbaOAr/UPORfrx504v9SqEeraKibuGh3anvJf7WmZ2ZirGzctCawexhHVZxN 8fvpNK2tS7pKM+umNfZdzWA+oeVx6pao4MsvqavRmzoSZ5Ld5wc2dO5HkzA2u0tIr1H2 M74lFaJ1IPr73hkUzYN8rTTFOmnzcQNQ5HNIDDIt6AdBHq6xtOtbeaDY+/I0YZrCQxfZ XxLhpcgA10SBrosUtHf5LoDrJoN4Y9LCNRwvQBmtu6wNuEvJkrrRZzyM6Aq9qEXu6Y9T OHzA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779269983; x=1779874783; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=lFqb92PY5okOz8fMXiDfI+BUOAxXFx/kYaJ+61wXDlY=; b=KGtyY8lL+03td9SN7BdUubo+dLXYwmRCkl4jOkIm9lwfyKahKaKDUV3b2n1JYarDil 5+eyj1nTov0qb4Q7U1Iraf7Jvv30vFpCPc0zrojh/WgqxC7TH2Sv5uzZRVSR1CG4lQqi zhoyPK9DzVnAvDM+xUqFY1kuDORNZ/9I6wAuev1zSwprQvTGgzO0iNEsj3j4w/y9D7js 6DNy/qKbLbdOcVfRz44QDlC/qt5oDHb2uGaQLLZqInigDJrWKt/ROeLFqPoNJKU3lpdv zgzcU/YR00z8c90JSrZbCqC3fJY1UDaerj62fBvW5NHSTqp7MUttR1wHEHwpxuPyAuJL Q5jQ== X-Gm-Message-State: AOJu0YxgmZcCEjD+SWkqAINiesE+nG6eLQgoTaCW92PvUyYiZWC6MtNy vKSOyQyDXNbfv2/JEsN0ucF+PqxEEeWp6QtSH5kp9o/PYg/7JcUWa7gRlWlRL1przNhw8Ainwzp M5ZswB7YdLkLf+VHNgFOWWQfOzeQd1zBxzIQJ X-Gm-Gg: Acq92OHCNu0XkBvuKLwCBa2Ro/UL3eUctRqZ2euw9fK9jRtduuJncirqzVfwW4/67wC 3yg95YQIwpke2KGeEK7sn0VoHA5Nq8TBnBodXn856IqAciCnn2fPN80whFsNgMJxy7HTrvWN14o R/pFseQRP0LV7Ieowz5kL6mbpnkCqpeQ5QVUiWjV1p2/UMdXU3zBJZwtS7UVKzjzZc78H6m16+A OzgLtcpBONq7JGKEcYIiJPhkp7X6jNDeGaZ1nKy+0abmCC8VOZjG7qXurYJRoq09XfH6/hO4e1u gz4Uj84= X-Received: by 2002:a05:620a:4707:b0:8cd:9033:1724 with SMTP id af79cd13be357-911cce886fbmr3460424385a.9.1779269982532; Wed, 20 May 2026 02:39:42 -0700 (PDT) MIME-Version: 1.0 References: <70d20c14d1c14f93e4200c1a9327b97d7c18a782.camel@gmail.com> In-Reply-To: From: Peter Smith Date: Wed, 20 May 2026 19:39:14 +1000 X-Gm-Features: AVHnY4J-M2x7w9KsoxfaAw6Q3CkSbCQwjGU8ZGooDIzFUa6Jz7rhUZixbofsxyU Message-ID: Subject: Re: Add pg_get_publication_ddl function To: "Jonathan Gonzalez V." Cc: pgsql-hackers@lists.postgresql.org Content-Type: text/plain; charset="UTF-8" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi. Some review comments for the v2-0001 patch. ====== doc/src/sgml/func/func-info.sgml (9.28.13. Get Object DDL Functions) 1. + + pg_get_publication_ddl + ( publication text + , VARIADIC options + text ) + setof text + I think "pubname" might be a more meaningful name for the first parameter. ~~~ 2. + + Reconstructs the CREATE PUBLICATION statement for + the specified publication (by OID or name), followed by an + ALTER PUBLICATION ... OWNER TO statement (the + CREATE PUBLICATION grammar has no + OWNER clause). Each statement is returned as a + separate row. An error is raised if no publication with the supplied + OID or name exists. When the publication was created with + FOR ALL TABLES, ALL SEQUENCES, the emitted + statement always lists ALL TABLES before + ALL SEQUENCES regardless of the original order. + The following options are supported: + pretty (boolean) for formatted output and + owner (boolean) to include + OWNER. + 2a. That "CREATE PUBLICATION" should back to the CREATE PUBLICATION docs page. ~ 2b. It is overkill to mention about the potential reordering of ALL TABLES and ALL SEQUENCES. Apart from being unnecessary, there are many other things can also be rearranged which are not mentioned: - TABLES and ALL TABLES IN SCHEMA clauses might be different order than specified - The publication parameters might be in a different order than specified - The values of 'publish' parameter might be different order than specified - etc. ~~~ GENERAL 3. It would be better if the the rows of "Table 9.96" were in alphabetical order. ====== src/backend/utils/adt/ddlutils.c pg_get_publication_ddl_internal: 4. + if (pub->allsequences) + appendStringInfo(buf, + "%sALL SEQUENCES", + pub->alltables ? ", " : ""); Maybe better to avoid tricky format strings. SUGGESTION if (pub->allsequences) { if (pub->alltables) appendStringInfo(buf, ", "); appendStringInfo("ALL SEQUENCES"); } ~~~ 5. + if (pub_incl_relids != NIL) + { + ListCell *pub_cell; + char *schemaname = NULL; + char *tablename; + + append_ddl_option(buf, pretty, 4, "FOR TABLE "); + + /* + * Publication can have table relations + */ + foreach(pub_cell, pub_incl_relids) Maybe that comment belongs earlier (above the if). ~~~ 6. + appendStringInfo(buf, "%s%s", + foreach_current_index(pub_cell) > 0 ? ", " : "", + quote_qualified_identifier(schemaname, tablename)); Another place where avoiding a tricky format string may be tidier. SUGGESTION if (foreach_current_index(pub_cell) > 0) appendStringInfo(buf, ", "); appendStringInfo(buf, "%s", quote_qualified_identifier(schemaname, tablename)); ~~~ 7. + pubtuple = SearchSysCache2(PUBLICATIONRELMAP, ObjectIdGetDatum(relid), + ObjectIdGetDatum(pub->oid)); + + if (!HeapTupleIsValid(pubtuple)) + elog(ERROR, + "cache lookup failed for publication relation %u in publication %u", + relid, pub->oid); 7a. Maybe blank line here is not wanted. ~ 7b. Don't need to say "publication" 2x. /publication relation/relation/ ~~~ 8. + /* If non-null, we have a list of columns to publish */ + if (!cols_nulls) SUGGESTION FOR THE COMMENT Does this table specify a column-list? ~~~ 9. + appendStringInfo(buf, "%s%s", + bms_member_index(attmap, attnum) ? ", " : "", + quote_identifier(get_attname(relid, attnum, true))); Another place where avoiding a tricky format string may be tidier. (change similar to previous review comments) ~~~ 10. + /* + * If there is a condition it goes after the columns. We can have + * conditions without columns as well. + */ + if (!condition_nulls) 10a. The earlier assignment to 'conditions' should be moved to be directly above here. ~ 10b. BTW, it is called a "row filter" so maybe it is better to refer to that in the comments/vars instead of the generic sounding "condition". ~~~ 11. + /* If we have schemas, they will go right before the WITH */ The kind of comments that just say "this-goes-after-that" or "this-goes-after-that" are not very useful, because it is obvious from the code logic that some appendStringInfo comes before or after another one. ~~~ 12. + /* + * Schemas can be preceded by a list of tables. When they are, the + * "TABLES IN SCHEMA" stays inline as a continuation of the existing + * FOR clause; otherwise it starts the FOR clause on its own line in + * pretty mode. + */ IMO it would be better for the FOR TABLE IN SCHEMA to come *before* the specific tables in FOR TABLE. e.g. For the case when there are specified tables "absorbed" into the same named schemas I think it is more natural to see the schemas first. CREATE PUBLICATION mypub FOR TABLES IN SCHEMA s, TABLE s.t1; ~~~ 13. + appendStringInfo(buf, "%s %s", + foreach_current_index(schema_cell) > 0 ? "," : "", + quote_identifier(nspname)); Another place where avoiding a tricky format string may be tidier. (change similar to previous review comments) ~~~ 14. + if (pub_excl_relids != NIL) + { + ListCell *excl_cell; + char *schemaname = NULL; + + appendStringInfoString(buf, " EXCEPT (TABLE "); The EXCEPT clause is currently permitted only with FOR ALL TABLES, so it would be better moving this to earlier in this function where pub->alltables was handled. ~~~ 15. + appendStringInfo(buf, "%s%s", + foreach_current_index(excl_cell) > 0 ? ", " : "", + quote_qualified_identifier(schemaname, NameStr(reltup->relname))); Another place where avoiding a tricky format string may be tidier. (change similar to previous review comments) ~~~ 16. + /* + * We need to know if we're the second permission added to prefix with a + * ", " string + */ + if (pub->pubactions.pubinsert) + { + /* + * By precedence we know that the insert will always be first, no need + * to check previous values + */ + appendStringInfoString(buf, "insert"); Both these comments are doing little more than just saying the same as the code. IMO they are not needed. ~~~ 17. + if (pub->pubactions.pubinsert) + { + /* + * By precedence we know that the insert will always be first, no need + * to check previous values + */ + appendStringInfoString(buf, "insert"); + first_perm = false; + } + + if (pub->pubactions.pubupdate) + { + appendStringInfo(buf, "%supdate", first_perm ? "" : ", "); + first_perm = false; + } + if (pub->pubactions.pubdelete) + { + appendStringInfo(buf, "%sdelete", first_perm ? "" : ", "); + first_perm = false; + } + + if (pub->pubactions.pubtruncate) + { + appendStringInfo(buf, "%struncate", first_perm ? "" : ", "); + } + 17a. There are some random blank lines that seem unnecessary. ~ 17b. IMO it is tidier to simply append the string you want, instead of using a trick format string. SUGGESTION (compare with patch) if (pub->pubactions.pubinsert) { appendStringInfoString(buf, "insert"); first_perm = false; } if (pub->pubactions.pubupdate) { appendStringInfo(buf, first_perm ? "update" : ",update"); first_perm = false; } if (pub->pubactions.pubdelete) { appendStringInfo(buf, first_perm ? "delete" : ",delete"); first_perm = false; } if (pub->pubactions.pubtruncate) { appendStringInfo(buf, first_perm ? "truncate" : ",truncate"); } ====== src/test/regress/expected/publication_ddl.out 18. + CREATE PUBLICATION testpub_ddl_1 + + WITH (publish='insert, update, delete, truncate', publish_generated_columns='none', publish_via_partition_root='false'); ~ This "pretty" output appears to have "+" garbage in it. What's that about -- it looks like some sort of line continuation character? Can it be removed? ~~~ 19. The "pretty" output might be improved if each of the publication options was on a new line. ~~~ 20. The generated boolean values (e.g. 'true'/'false') do not need to be quoted. ====== src/test/regress/sql/publication_ddl.sql (Here are lots of test review comments; the first group are are general so might apply to multiple test cases). 21. I think you can create all the necessary schema and tables together up-front instead of scatering them through the file. ~~~ 22. Make use of the proper publication teminology like "Column Lists" and "Row Filters" instead of vague "columns" and "conditions". ~~~ 23. Here is an idea: Instead of having dozens of test publications, just have 1 test publication, which you CREATE/DROP for each test case. Then, since there is a fixed name publication (e.g. "mypub") for everything, you can make a subroutine to encapsulate the common code: +SELECT pg_get_publication_ddl('mypub'); +SELECT pg_get_publication_ddl((SELECT oid FROM pg_publication WHERE pubname='mypub')); +SELECT pg_get_publication_ddl('mypub', 'pretty', 'true'); It means your test .sql file can become much shorter/simpler I think. ~~~ 24. There is duplication of some tests: e.g. +-- columns in publication must be quoted and +-- identifiers that require quoting: publication, schema, table and column ~~~ 25. It is not needed to quote the booleans 'true'/'false' for the options. ////// 26. +-- create base table to test basic table publication What does "basic table publication" mean? I expect it means different things to different people. Better to be explicit about what this is really testing. ~~~ 27. +-- create publication for one table with two columns and a condition with an expression What does "with an expression" mean? All Row-Filters are expressions aren't they? ~~~ 28. +-- create a publication for a list of tables Not really describing what this test is doing, which is mixing FOR TABLE and FOR TABLES IN SCHEMA. ~~~ 29. +CREATE PUBLICATION testpub_ddl_part3 FOR TABLE testpub_ddl_part WITH (publish_via_partition_root='true'); I guess it make no difference since these are only DDL syntax tests, but it didn't really make much sense to set publish_via_partition_root=true when testpub_ddl_part is the ROOT anyway. ~~~ 30. +-- create publication for all tables except two tables Actually this is also combining with an ALL SEQUENCES test. ~~~ 31. +-- cleanup publications +DROP PUBLICATION testpub_ddl_1; +DROP PUBLICATION testpub_ddl_2; +DROP PUBLICATION testpub_ddl_3; +DROP PUBLICATION testpub_ddl_4; +DROP PUBLICATION testpub_ddl_5; +DROP PUBLICATION testpub_ddl_6; +DROP PUBLICATION testpub_ddl_7; +DROP PUBLICATION testpub_ddl_8; +DROP PUBLICATION testpub_ddl_9; +DROP PUBLICATION testpub_ddl_10; +DROP PUBLICATION testpub_ddl_schema_1; +DROP PUBLICATION testpub_ddl_schema_2; +DROP PUBLICATION testpub_ddl_schema_3; +DROP PUBLICATION testpub_ddl_schema_4; +DROP PUBLICATION testpub_ddl_schema_5; +DROP PUBLICATION testpub_ddl_part1; +DROP PUBLICATION testpub_ddl_part2; +DROP PUBLICATION testpub_ddl_part3; +DROP PUBLICATION testpub_ddl_part4; +DROP PUBLICATION "Quoted Pub"; +DROP PUBLICATION testpub_ddl_except1; +DROP PUBLICATION testpub_ddl_except2; As per earlier review comment IMO it would make the test code simpler to have just 1 publication that you CREATE/DROP om the fly. ~~~ 32. +-- cleanup tables in schemas Not sure why this is done separately. Probably easier just to drop the schemas with CASCADE so their tables will be auto-deleted. ====== Kind Regards, Peter Smith. Fujitsu Australia