[PATCH v1] Fix initial sync of zero-column published tables

From: mingjiegao <mingjie(dot)gmj(at)gmail(dot)com>
To: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: [PATCH v1] Fix initial sync of zero-column published tables
Date: 2026-11-01 04:19:34
Message-ID: CAL5_ZS1bnYkK_GrA4N4SVKYgYsMMJxL9416m=vECAjrcwmQkkg@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

Initial table synchronization mishandles a zero-column published table
when the subscriber has additional columns. In text mode, COPY can put
an empty string into a subscriber-only column instead of its default.
Subsequent inserts through the apply worker use the default correctly.
In binary mode, the initial copy fails with a field-count mismatch.

I reproduced this on master (20devel) at:
77705d1e5a616339c3e92f586bbfa7b6bb562beb

For example, use two fresh instances with logical replication enabled
on the publisher:

-- Publisher:
CREATE TABLE lr02_t ();
INSERT INTO lr02_t DEFAULT VALUES;
CREATE PUBLICATION lr02_pub FOR TABLE lr02_t WITH (publish = 'insert');

-- Subscriber (replace the connection string):
CREATE TABLE lr02_t (v text DEFAULT 'subscriber-default');
CREATE SUBSCRIPTION lr02_sub
CONNECTION '<publisher connection string>'
PUBLICATION lr02_pub WITH (copy_data = true, binary = false);

On the subscriber, wait for this query to return 'r':

SELECT srsubstate FROM pg_subscription_rel
WHERE srsubid = (SELECT oid FROM pg_subscription
WHERE subname = 'lr02_sub');

Then run:

SELECT quote_nullable(v) FROM lr02_t;

The result is '' instead of 'subscriber-default'. Inserting another row
with DEFAULT VALUES on the publisher produces a correctly defaulted row
on the subscriber once replication catches up. Thus initial and
incremental replication disagree for the same source data.

Repeating the example on fresh instances with binary = true instead
makes initial synchronization fail with:

ERROR: row field count is 0, expected 1

make_copy_attnamelist() returns NIL for the zero-column source, but
BeginCopyFrom() interprets NIL as an unspecified column list and selects
all target non-dropped, non-generated columns. Those columns are then
treated as input columns, so their default expressions are not prepared.

The attached patch adds BeginCopyFromExtended() with a flag indicating
that the column list is explicit. Tablesync uses that entry point, so
an empty list remains empty. This is resolved before default expressions
and parser state are initialized. The existing BeginCopyFrom() interface
retains its behavior for SQL COPY, file_fdw and other callers.

The change keeps row parsing, default evaluation, constraints, triggers
and partition routing in the existing COPY path. There are no SQL syntax
or replication protocol changes. Its scope is the handling of an empty
input column list after remote column discovery; it does not change
which publisher columns are selected for replication.

The patch includes text and binary regression coverage in
008_diff_schema.pl. With only the test changes applied to the baseline,
three assertions fail and two dependent assertions are skipped. With
the fix, all 11 assertions pass.

To reproduce the failure, apply just the test portion to a clean
checkout of the baseline:

git apply --include=src/test/subscription/t/008_diff_schema.pl \
/path/to/v1-0001-Fix-initial-sync-of-zero-column-published-tables.patch

From a configured and built tree with TAP tests enabled, run:

make -j8 -C src/test/subscription check \
PROVE_TESTS=t/008_diff_schema.pl

For an out-of-tree build, run the make command from the build directory.
Applying the remaining source/header changes and rebuilding makes the
test pass.

Testing used Linux x86_64, GCC 15.2, -O2 -g, with assertions disabled.
The targeted subscription tests 004_sync, 008_diff_schema, 014_binary
and 031_column_list pass (64 assertions). A separate 24-assertion test
also passes, covering local defaults, identity values, NULLs, subscriber
stored generated columns, replica triggers, partition routing, dropped
publisher columns and NOT NULL checks in text and binary modes.

The test_setup, copy, copyselect, copydml and copyencoding regression
tests, and the file_fdw regression test, also pass. I have not tested
older major versions or run the full test suite.

Comments on the internal COPY interface change are welcome.

Best regards,
mingjiegao

Attachment Content-Type Size
v1-0001-Fix-initial-sync-of-zero-column-published-tables.patch text/x-patch 7.5 KB

Browse pgsql-hackers by date

  From Date Subject
Previous Message jian he 2026-11-01 04:16:45 Re: Row pattern recognition