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 1wQIT7-001Pt9-0i for pgsql-hackers@arkaria.postgresql.org; Fri, 22 May 2026 05:30:45 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wQIT5-00CPG2-0f for pgsql-hackers@arkaria.postgresql.org; Fri, 22 May 2026 05:30:44 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wQIT4-00CPFu-2v for pgsql-hackers@lists.postgresql.org; Fri, 22 May 2026 05:30:43 +0000 Received: from mail-qk1-x72d.google.com ([2607:f8b0:4864:20::72d]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wQIT3-00000000qPq-3hFM for pgsql-hackers@lists.postgresql.org; Fri, 22 May 2026 05:30:43 +0000 Received: by mail-qk1-x72d.google.com with SMTP id af79cd13be357-90b2fcf90a0so963256085a.1 for ; Thu, 21 May 2026 22:30:41 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1779427840; cv=none; d=google.com; s=arc-20240605; b=B8CwwD9igHfG/D7e+VrFZhoZfRgI4Y3EHIm2/JAjiF2patWxPE4AJLuFrh/kiwVZkh LrpPdQbQha/37Hmr3GyarZRFDjYYXXkAShZksSxBsblYfxDs40aQURVvmmWL00cVguBo DxR9YGZivj/d3Eb87mQjlLP5LAM7pJYerkP5/RuaoyC/Ex/HNWUa7M/BhmMaXvvA0XKC pK8bVuua/HasLoHVwbfeEE2kv/jl426beYW+qPzvjTTzOxJPJoENkI8HuarLWpkNosLs VyS7doGgIhV5aV3SRDbP/3/QeBpHXmMsQMByETwWn7cSP5/NsRWcmF6mbUo2ZB/Kx2+n zKxA== 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=0E1LOrEQRqSSZOGIvEYbQTA3AA28bP9y2iQELEEtBf0=; fh=N8I93wfBvy58pjCNWDCfF1SQST9CQov8w4fs/70JXNE=; b=N/NV1dxjyndm15dWHBPxgVpncKm6pRi74Q+Kll5UYEg7kg1olYA7h1J4KFq83aEsUS /IIVVkpCDEU7KXBKOV9ZE2FbXdrLbC7iyYnS54RTFaWClntn1h/PIuvSwNxDv6Pw504r X9MLEo3pVIm3gkIQt0pdpsOXACnp5y5I/8RLbn4WhILpia4UhM3SQp8hb7WZAvoCfZSb mR9oIem7tntbSAuH9L4GKpfnsLmWZH8VXLCjMVLjOO2prWyZE8+qgUZBy6Ue2a9NlvoW t0OicvMcnU5lWv3xzHAnua6nrB7H+RXJG6MGE+xCd83O4UYhTJKLWMPb1cIoFgxRbl48 zwIw==; 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=1779427840; x=1780032640; 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=0E1LOrEQRqSSZOGIvEYbQTA3AA28bP9y2iQELEEtBf0=; b=naUWQCWQLMtkIDiH6VpBl/pNQ2K3cn4VvYFwi409Uz3eU5YJnB4nt6LLxk/iKPcsur 8fLtjB7QLom7Gq9ZMnQdnxe1yI+bJMNU8wyeydSqVjKv+jDocuujQIpRRUmm2ATo5sSa dADOCnj0PQu6/gkZ1eKHzTvMH3UEzZRyefpD8z/qLiI61MoFmwYXUM+3U1m7gHUNloP/ kwf8syQTyDIXlGfCZhfrh42xKW23Bm94zBxEWNqNedpfTLKVM71KTNWSowK9L90Q26/I KiQPWQ3YKnF7hIIQFEbZBzZQuAyL4/76neGtII12BPItJt0KdGv9HPjEzGHyd2VU46nm vq/Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779427840; x=1780032640; 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=0E1LOrEQRqSSZOGIvEYbQTA3AA28bP9y2iQELEEtBf0=; b=AqJBRxF+USjQpJtM5k3fqeI6d7/nX6o/DC9jzY5AUndS0tECAXIR5yKQsD+VgGNQTR 43LIXX3yXuHMKs20moeugvvRJ0HqKrLSZau4TER4z803RB6uMFHJyTehv05nuHh/RreZ XS2h+asMoQ5y7UxWWAr/fSnoEYkOKDijeqC6lyocMOV9TIzgGQCAsRVcrdEuwHiHamch 5w6gEIGlSG0Xs6REGXFcNipqRKYFRnihPx64MVtqw3BpVe8sEJPTmKQMc23r7pqibodz fYK+yD3Z4N1fDR0xpE8JfCcCaNEZ7Ha7bn1O+ssQ7082HdpYKn3ZCBnRwKE3T5ksZ1df uWcg== X-Forwarded-Encrypted: i=1; AFNElJ89TNGaJ3+x3DZ7+inO8ntgaseOmZB2PGEOq7MopFfife54WkfveumfKtzpb9Os6m0Teky9/kcS7NFFFnS5@lists.postgresql.org X-Gm-Message-State: AOJu0YwiidtYz6LKPXP0mMEtcEpc6sRvO7/JcEs16v8bd9G68ouue346 1ZTqbPr6COteiH/yffwvr9DN6vqJEHrnC9oCKK24CqKYR4Hsohp9IP3f/6FGQRaNHjr4+IDlgNp dqKB7SPfz66F2UBfyc1kpIoarrPITFHA= X-Gm-Gg: Acq92OGiqqMZMgRzACyA1E8kUjA/OCBWXKz8A/3A7sUT3ikIppYt0hs0FvP8U8KIeXc bYXrOkwmkYqPF5+9YPbrXwwsmS4oNcHxcGSgk5LuwVeDyzFgxnj8XtrCehE+wmxTV0Tw0nGU/1U 1VYDJae9rs3cXYi1C4x613iWQLFfHeLnR4PQKL+uRv90MUI7aBptDSJcSxa74yDLAfOWV08INB1 AQedJtmy4DAU5o3m7kpg9fDthrY/o9d7fpEcABK4G/IQBKe5vt1oQz6Xi7Ib7T5riQSHjpbI3T/ TaNzdTuouVZIAkSlrw== X-Received: by 2002:a05:620a:40c5:b0:90f:786c:4a82 with SMTP id af79cd13be357-914a240d00cmr774668785a.39.1779427839284; Thu, 21 May 2026 22:30:39 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Peter Smith Date: Fri, 22 May 2026 15:30:12 +1000 X-Gm-Features: AVHnY4Ks_kH4IIgwOH88YnPqoHihexKfruFUY9YijKE147oEdFsADvexbpPZvXM Message-ID: Subject: Re: Support EXCEPT for TABLES IN SCHEMA publications To: Nisha Moond Cc: shveta malik , Amit Kapila , PostgreSQL Hackers Content-Type: text/plain; charset="UTF-8" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi Nisha. Here are some review comments for patch v6-0002. ====== Commit message 1. Extend the EXCEPT clause support to allow tables to be excluded when adding a schema to a publication via ALTER PUBLICATION ... ADD: ~ /ADD:/ADD./ ====== doc/src/sgml/ref/alter_publication.sgml Synopsis. 2. - TABLES IN SCHEMA { schema_name | CURRENT_SCHEMA } [, ... ] + TABLES IN SCHEMA { schema_name | CURRENT_SCHEMA } [ EXCEPT ( except_table_object [, ... ] ) ] [, ... ] ~ Probably needs to change to introduce the 'tables_in_schema' part, same as in the CREATE PUBLICATION synopsis. ~~~ 3. + +and except_table_object is: + + [ ONLY ] table_name [ * ] Something is wrong. Now the synopsis has 'except_table_object' 2x. ~~~ 4. + + The EXCEPT clause can be used with + ADD TABLES IN SCHEMA to exclude specific tables from a + schema-level publication. EXCEPT is not supported with + DROP TABLES IN SCHEMA; instead, dropping a schema from + the publication automatically removes all of its associated + EXCEPT entries. + 4a. I didn't think you need to say it is a "schema-level" publication. That much is obvious because it already says "TABLES IN SCHEMA". ~ 4b. Maybe do not say "is not supported", because IMO that implies it will cause an error. SUGGESTION (or something like this) Using DROP TABLES IN SCHEMA on a publication will automatically also remove any associated EXCEPT entries. ~~~ EXCEPT 5. + + EXCEPT ( except_table_object [, ... ] ) + + + Specifies tables to be excluded from a schema-level publication entry. + This clause may be used with ADD TABLES IN SCHEMA + and not with DROP TABLES IN SCHEMA. Each named + table must belong to the schema specified in the same + TABLES IN SCHEMA clause. Table names may be + schema-qualified or unqualified; unqualified names are implicitly + qualified with the schema named in the same clause. See + for further details on the + semantics of EXCEPT. + + + + 5a. Oh! If this EXCEPT part was previously missing even for the "FOR ALL TABLES", then IMO that is a separate bug that should be in another thread and patched/fixed asap, then your patch should just make small changes to to it. ~ 5b. I don't think you need to say "schema-level" here... Maybe reword like "When used with ADD TABLES IN SCHEMA...". Anyway, all this wording will need to change a bit after the aforementioned fix for "FOR ALL TABLES EXCEPT" patched/pushed. ~~~ Examples 6. + +ALTER PUBLICATION sales_publication ADD TABLES IN SCHEMA sales EXCEPT (TABLE sales.internal, sales.drafts); + It is OK left in the description, but IMO it is better if you don't use the schema-qualified name in the actual code fragment. ====== src/backend/commands/publicationcmds.c AlterPublicationSchemas: 7. + /* + * Increment the command counter so that is_schema_publication() in + * GetExcludedPublicationTables() can see the just-inserted schema + * rows when AlterPublicationExceptTables runs next. + */ + CommandCounterIncrement(); Should this cut/paste common code be done afterwards just if stmt->action == AP_AddObjects/SetObjects ? ~~~ AlterPublicationExceptTables: 8. + /* + * This function handles EXCEPT entries for schema-level publications + * only. For FOR ALL TABLES publications, EXCEPT entries are already + * processed by AlterPublicationTables(). + */ + if (schemaidlist == NIL && !is_schema_publication(pubid)) + return; Huh? It seems contrary to the function comment that was also talking about "FOR ALL TABLES". Should this function really be called something different like 'AlterPublicationSchemaExceptTables'? ~~~ 9. + /* + * EXCEPT with SET is not supported: SET replaces the schema list but does + * not have a well-defined semantics for merging or replacing existing + * except entries. Users should DROP and re-ADD the schema with the + * desired EXCEPT list instead. + */ + if (stmt->action == AP_SetObjects) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("EXCEPT clause is not supported with SET in ALTER PUBLICATION"))); 9a. Not sure about this comment saying "does not have a well-defined semantics". Should you instead just have XXX comment to simply say "Not yet implemented", because this is getting replaced later by your patch 0003 I think. SUGGESTION XXX EXCEPT with SET is not currently implemented. Workaround: Users should DROP and re-ADD the schema with the desired EXCEPT list. ~ 9b. The ereport should be temporarily (until patch 0003 is pushed) have using an errhint to say the workaround. ~~~ 10. + if (stmt->action == AP_AddObjects) + { + List *rels; + List *explicitrelids; + ListCell *lc; + + rels = OpenTableList(exceptrelations); + + explicitrelids = GetIncludedPublicationRelations(pubid, + PUBLICATION_PART_ROOT); + + /* + * Validate that each excluded table is not also in the explicit table + * list (which would be contradictory). + */ + foreach(lc, rels) Can tidy this using a foreach_ptr look instead of 'lc'. ~~~ 11. + if (list_member_oid(explicitrelids, relid)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("table \"%s.%s\" cannot appear in both the table list and the EXCEPT clause", + get_namespace_name(relns), + RelationGetRelationName(pri->relation))); Make use of the new function to get fully qualified names and replace \"%s.%s\" with \"%s\". ~~~ 12. + /* + * For FOR ALL TABLES, EXCEPT entries are processed by + * AlterPublicationTables(), so merge them in. For TABLES IN SCHEMA, + * they are handled separately by AlterPublicationExceptTables(). + */ + if (stmt->for_all_tables) + relations = list_concat(relations, exceptrelations); AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext, schemaidlist != NIL); AlterPublicationSchemas(stmt, tup, schemaidlist); + AlterPublicationExceptTables(stmt, tup, exceptrelations, schemaidlist); Would it be simpler if AlterPublicationExceptTables was merged (or delegated from) the AlterPublicationSchemas? ====== src/bin/pg_dump/t/002_pg_dump.pl 13. + 'CREATE PUBLICATION pub11' => { + create_order => 50, + create_sql => + 'CREATE PUBLICATION pub11 FOR TABLES IN SCHEMA dump_test EXCEPT (TABLE dump_test.test_table);', + regexp => qr/^ + \QCREATE PUBLICATION pub11 WITH (publish = 'insert, update, delete, truncate');\E + /xm, + like => { %full_runs, section_post_data => 1, }, + }, + + 'ALTER PUBLICATION pub11 ADD TABLES IN SCHEMA dump_test EXCEPT (dump_test.test_table)' + => { + regexp => qr/^ + \QALTER PUBLICATION pub11 ADD TABLES IN SCHEMA dump_test EXCEPT (TABLE ONLY test_table);\E + /xm, + like => { %full_runs, section_post_data => 1, }, + }, + + 'CREATE PUBLICATION pub12' => { + create_order => 50, + create_sql => + 'CREATE PUBLICATION pub12 FOR TABLES IN SCHEMA dump_test EXCEPT (TABLE dump_test.test_table, dump_test.test_second_table);', + regexp => qr/^ + \QCREATE PUBLICATION pub12 WITH (publish = 'insert, update, delete, truncate');\E + /xm, + like => { %full_runs, section_post_data => 1, }, + }, + + 'ALTER PUBLICATION pub12 ADD TABLES IN SCHEMA dump_test EXCEPT (dump_test.test_table, dump_test.test_second_table)' + => { + regexp => qr/^ + \QALTER PUBLICATION pub12 ADD TABLES IN SCHEMA dump_test EXCEPT (TABLE ONLY test_table, TABLE ONLY test_second_table);\E + /xm, + like => { %full_runs, section_post_data => 1, }, + }, + Should not need to specify schema-qualified names in the CREATE PUBLICATION or the ALTER PUBLICATION. I think a better test would have one of each (e.g. don't qualify the 'dump_test.test_table') in any of those SQL. ====== src/bin/psql/tab-complete.in.c 14. + else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD", "TABLES", "IN", "SCHEMA", MatchAny, "EXCEPT", "(", "TABLE", MatchAnyN) && ends_with(prev_wd, ',')) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD", "TABLES", "IN", "SCHEMA", MatchAny, "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ',')) + COMPLETE_WITH(")"); I've not tested this, but the code looks the same. so I suspect this suffers the same problem where it lists potentially all tables instead of just the table of the current schema. Maybe this is an unavoidable quirk... ====== src/test/regress/sql/publication.sql 15. Should you add a some more test cases? e.g. Pass with EXCEPT without the schema-qualified name. e.g. Pass with multiple excepted tables. e.g. Fail because non-existing table name. e.g. Fail because table not in schema. e.g. Fail syntax because missing keyword TABLE. e.g. do a DROP TABLES IN SCHEMA to test that the except tables gove removed too ====== Kind Regards, Peter Smith. Fujitsu Australia.