Re: Proposal: Conflict log history table for Logical Replication

From: Peter Smith <smithpb2250(at)gmail(dot)com>
To: Dilip Kumar <dilipbalaut(at)gmail(dot)com>
Cc: vignesh C <vignesh21(at)gmail(dot)com>, Amit Kapila <amit(dot)kapila16(at)gmail(dot)com>, shveta malik <shveta(dot)malik(at)gmail(dot)com>, Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com>, Bharath Rupireddy <bharath(dot)rupireddyforpostgres(at)gmail(dot)com>, PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Re: Proposal: Conflict log history table for Logical Replication
Date: 2025-12-24 07:41:57
Message-ID: CAHut+PvHYFsAXv6i2QHMz1VN0fN1-W3U_5+D5FM2BA6WuaQ1qg@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Tue, Dec 23, 2025 at 5:49 PM Peter Smith <smithpb2250(at)gmail(dot)com> wrote:
>
> Hi Dilip.
>
> Here are some review comments after a first pass of patch v15-0001.
>

And, some more review comments for patch v15-0001.

======
src/backend/catalog/pg_subscription.c

1.
+ /* Always set the destination, default will be log. */
+ values[Anum_pg_subscription_sublogdestination - 1] =
+ CStringGetTextDatum(ConflictLogDestLabels[opts.logdest]);
+

None of the other values[] assignments here have a comment talking
about defaults, etc, so I don't think this needs one either.

======
src/backend/commands/subscriptioncmds.c

CreateSubscription:

2.
+ {
+ char conflict_table_name[NAMEDATALEN];
+ Oid namespaceId, logrelid;

In similar code in AlterSubscription, this was just called 'relname'.
Better to be consistent where possible. I think 'relname' would be
fine here too.

~~~

3.
+ else
+ {
+ /* Destination is "log"; no table is needed. */
+ values[Anum_pg_subscription_subconflictlogrelid - 1] =
+ ObjectIdGetDatum(InvalidOid);
+ }

I think it's better to say this using coded Asserts instead of just
assertions in comments.

e.g.

/* There is no conflict log table */
Assert(opts.logdest == CONFLICT_LOG_DEST_LOG)
values[...] = ObjectIdGetDatum(InvalidOid);

~~~

4.
+ if (isTempNamespace(namespaceId))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not generate conflict log table \"%s\"",
+ conflictrel),
+ errdetail("Conflict log tables cannot be created in a temporary namespace."),
+ errhint("Ensure your 'search_path' is set to permanent schema.")));
+
+ /* Report an error if the specified conflict log table already exists. */
+ if (OidIsValid(get_relname_relid(conflictrel, namespaceId)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("could not generate conflict log table \"%s.%s\"",
+ get_namespace_name(namespaceId), conflictrel),
+ errdetail("A table with the internally generated name already exists."),
+ errhint("Drop the existing table or change your 'search_path' to use
a different schema.")));

I'm not sure about these messages:

4a.
"could not generate conflict log table".
- Why say "generate"?
- We don't need to say "conflict log table" -- that's already in the detail

SUGGESTION (something like)
"could not create relation \"%s\""

~

4b.
For the 2nd error, I think errmsg should look like below, same as any
other duplicate table error.
"relation \"%s.%s\" already exists"

~

4c.
+ errdetail("A table with the internally generated name already exists."),

I don't think this errdetail added anything useful. It already exists
-- that's all you need to know. Why does it matter that the name was
generated automatically?

~~~

GetLogDestination:

5.
+ for (int i = CONFLICT_LOG_DEST_LOG; i <= CONFLICT_LOG_DEST_ALL; i++)
+ {
+ if (pg_strcasecmp(dest, ConflictLogDestLabels[i]) == 0)
+ return (ConflictLogDest) i;
+ }
+
+ /* Unrecognized string. */
+ return CONFLICT_LOG_DEST_INVALID;

This code is making rash assumptions about the enums values being the
same as ordinals.

IMO it should be written like:

if (strcmp(dest, "log") == 0)
return CONFLICT_LOG_DEST_LOG;

if (strcmp(dest, "table") == 0)
return CONFLICT_LOG_DEST_TABLE;

if (strcmp(dest, "all") == 0)
return CONFLICT_LOG_DEST_ALL;

/* Unrecognized dest. */
ereport(ERROR, ...);

~~~

IsConflictLogTable

6.
+bool
+IsConflictLogTable(Oid relid)
+{
+ Relation rel;

If you enforce (as I've suggested elsewhere previously) a name
convention that the CLT must have "pg_" prefix, then perhaps you can
exit early from this function without having to scan all the OIDs,
just by checking first that the RelationGetRelationName(rel) must
start with "pg_".

======
src/test/regress/sql/subscription.sql

7.
+-- fail - unrecognized format value

/format/parameter/

~~

8.
Some of these tests are grouped together like

"ALTER: State transitions"
and
"Ensure drop table is not allowed, and DROP SUBSCRIPTION reaps the table"
etc.

These group boundaries should be identified more clearly with more
substantial comments.
e.g
#-- ==================================
#-- ALTER - state transition tests
#-- ==================================

~~~

9.
The "pg_relation_is_publishable" seems misplaced because it is buried
among the drop/reap tests. Maybe it should come before all that.

======
src/tools/pgindent/typedefs.list

10.
What about "typedef enum ConflictLogDest"

======
Kind Regards,
Peter Smith.
Fujitsu Australia

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Michael Paquier 2025-12-24 08:10:47 Re: correct a word
Previous Message wenhui qiu 2025-12-24 07:33:07 Re: correct a word