v46-0005-Row-filter-handle-FOR-ALL-TABLES.patch

application/octet-stream

Filename: v46-0005-Row-filter-handle-FOR-ALL-TABLES.patch
Type: application/octet-stream
Part: 1
Message: Re: row filtering for logical replication

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v46-0005
Subject: Row filter handle FOR ALL TABLES
File+
src/backend/replication/logical/tablesync.c 58 6
src/backend/replication/pgoutput/pgoutput.c 18 0
From 59f959ff6a9f81e1c152ca7f2351d2f6e4e7c886 Mon Sep 17 00:00:00 2001
From: Peter Smith <peter.b.smith@fujitsu.com>
Date: Mon, 13 Dec 2021 20:39:33 +1100
Subject: [PATCH v46] Row filter handle FOR ALL TABLES

If one of the subscriber's publications was created using FOR ALL TABLES then
that implies NO row-filtering will be applied.

This overides any other row-filters from other subscribed publications.

Note that the initial COPY does not take publication operations into account.

Author: Peter Smith
Reported By: Tang
Discussion: https://www.postgresql.org/message-id/OS0PR01MB6113D82113AA081ACF710D0CFB6E9%40OS0PR01MB6113.jpnprd01.prod.outlook.com

TODO
- PG docs
- TAP test cases
- Similar case for FOR ALL TABLES IN SCHEMA?
---
 src/backend/replication/logical/tablesync.c | 64 ++++++++++++++++++++++++++---
 src/backend/replication/pgoutput/pgoutput.c | 18 ++++++++
 2 files changed, 76 insertions(+), 6 deletions(-)

diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 971e037..1f80261 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -700,6 +700,7 @@ fetch_remote_table_info(char *nspname, char *relname,
 	TupleTableSlot *slot;
 	Oid			tableRow[] = {OIDOID, CHAROID, CHAROID};
 	Oid			attrRow[] = {TEXTOID, OIDOID, BOOLOID};
+	Oid			alltablesRow[] = {BOOLOID};
 	Oid			qualRow[] = {TEXTOID};
 	bool		isnull;
 	int			natt;
@@ -802,12 +803,67 @@ fetch_remote_table_info(char *nspname, char *relname,
 	walrcv_clear_result(res);
 
 	/*
+	 * If any publication has puballtable true then all row-filtering is
+	 * ignored.
+	 *
 	 * Get relation qual. DISTINCT avoids the same expression of a table in
 	 * multiple publications from being included multiple times in the final
 	 * expression.
 	 */
 	if (walrcv_server_version(LogRepWorkerWalRcvConn) >= 150000)
 	{
+		bool puballtables = false;
+
+		/*
+		 * Check for puballtables flag
+		 */
+		resetStringInfo(&cmd);
+		appendStringInfo(&cmd,
+						 "SELECT p.puballtables "
+						 "  FROM pg_publication p "
+						 " WHERE p.puballtables IS TRUE "
+						 "   AND p.pubname IN (");
+
+		first = true;
+		foreach(lc, MySubscription->publications)
+		{
+			char	   *pubname = strVal(lfirst(lc));
+
+			if (first)
+				first = false;
+			else
+				appendStringInfoString(&cmd, ", ");
+
+			appendStringInfoString(&cmd, quote_literal_cstr(pubname));
+		}
+		appendStringInfoChar(&cmd, ')');
+
+		res = walrcv_exec(LogRepWorkerWalRcvConn, cmd.data, 1, alltablesRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch puballtables flag for table \"%s.%s\" from publisher: %s",
+							nspname, relname, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		puballtables = tuplestore_gettupleslot(res->tuplestore, true, false, slot);
+		ExecDropSingleTupleTableSlot(slot);
+		walrcv_clear_result(res);
+
+		if (puballtables)
+		{
+			if (*qual)
+			{
+				list_free_deep(*qual);
+				*qual = NIL;
+			}
+			pfree(cmd.data);
+			return;
+		}
+
+		/*
+		 * Check for row-filters
+		 */
 		resetStringInfo(&cmd);
 		appendStringInfo(&cmd,
 						 "SELECT DISTINCT pg_get_expr(prqual, prrelid) "
@@ -847,18 +903,14 @@ fetch_remote_table_info(char *nspname, char *relname,
 		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
 		while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
 		{
-			Datum		rf = slot_getattr(slot, 1, &isnull);
+			Datum rf = slot_getattr(slot, 1, &isnull);
 
 			if (!isnull)
 				*qual = lappend(*qual, makeString(TextDatumGetCString(rf)));
 
 			ExecClearTuple(slot);
 
-			/*
-			 * One entry without a row filter expression means clean up
-			 * previous expressions (if there are any) and return with no
-			 * expressions.
-			 */
+			/* Ignore filters and cleanup as necessary. */
 			if (isnull)
 			{
 				if (*qual)
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index c072b25..1a13d70 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -917,6 +917,8 @@ pgoutput_row_filter_init(PGOutputData *data, Relation relation, RelationSyncEntr
 		 * relation. Since row filter usage depends on the DML operation,
 		 * there are multiple lists (one for each operation) which row filters
 		 * will be appended.
+		 *
+		 * NOTE: FOR ALL TABLES implies "use no filters" so it takes precedence
 		 */
 		foreach(lc, data->publications)
 		{
@@ -926,6 +928,22 @@ pgoutput_row_filter_init(PGOutputData *data, Relation relation, RelationSyncEntr
 			bool		rfisnull;
 
 			/*
+			 * If the publication is FOR ALL TABLES then it is treated same as if this
+			 * table has no filters (even if for some other publication it does).
+			*/
+			if (pub->alltables)
+			{
+				if (pub->pubactions.pubinsert)
+					no_filter[idx_ins] = true;
+				if (pub->pubactions.pubupdate)
+					no_filter[idx_upd] = true;
+				if (pub->pubactions.pubdelete)
+					no_filter[idx_del] = true;
+
+				continue;
+			}
+
+			/*
 			 * Lookup if there is a row-filter, and if yes remember it in a list (per
 			 * pubaction). If no, then remember there was no filter for this pubaction.
 			 * Code following this 'publications' loop will combine all filters.
-- 
1.8.3.1