Skip to content

Commit 8fcd802

Browse files
author
Amit Kapila
committed
Improve error message for replication of generated columns.
Currently, logical replication produces a generic error message when targeting a subscriber-side table column that is either missing or generated. The error message can be misleading for generated columns. This patch introduces a specific error message to clarify the issue when generated columns are involved. Author: Shubham Khanna Reviewed-by: Peter Smith, Vignesh C, Amit Kapila Discussion: https://postgr.es/m/CAHv8RjJBvYtqU7OAofBizOmQOK2Q8h+w9v2_cQWxT_gO7er3Aw@mail.gmail.com
1 parent d0eb429 commit 8fcd802

File tree

2 files changed

+103
-30
lines changed

2 files changed

+103
-30
lines changed

src/backend/replication/logical/relation.c

+61-30
Original file line numberDiff line numberDiff line change
@@ -220,41 +220,63 @@ logicalrep_rel_att_by_name(LogicalRepRelation *remoterel, const char *attname)
220220
}
221221

222222
/*
223-
* Report error with names of the missing local relation column(s), if any.
223+
* Returns a comma-separated string of attribute names based on the provided
224+
* relation and bitmap indicating which attributes to include.
224225
*/
225-
static void
226-
logicalrep_report_missing_attrs(LogicalRepRelation *remoterel,
227-
Bitmapset *missingatts)
226+
static char *
227+
logicalrep_get_attrs_str(LogicalRepRelation *remoterel, Bitmapset *atts)
228228
{
229-
if (!bms_is_empty(missingatts))
229+
StringInfoData attsbuf;
230+
int attcnt = 0;
231+
int i = -1;
232+
233+
Assert(!bms_is_empty(atts));
234+
235+
initStringInfo(&attsbuf);
236+
237+
while ((i = bms_next_member(atts, i)) >= 0)
230238
{
231-
StringInfoData missingattsbuf;
232-
int missingattcnt = 0;
233-
int i;
239+
attcnt++;
240+
if (attcnt > 1)
241+
appendStringInfo(&attsbuf, _(", "));
234242

235-
initStringInfo(&missingattsbuf);
243+
appendStringInfo(&attsbuf, _("\"%s\""), remoterel->attnames[i]);
244+
}
236245

237-
i = -1;
238-
while ((i = bms_next_member(missingatts, i)) >= 0)
239-
{
240-
missingattcnt++;
241-
if (missingattcnt == 1)
242-
appendStringInfo(&missingattsbuf, _("\"%s\""),
243-
remoterel->attnames[i]);
244-
else
245-
appendStringInfo(&missingattsbuf, _(", \"%s\""),
246-
remoterel->attnames[i]);
247-
}
246+
return attsbuf.data;
247+
}
248248

249+
/*
250+
* If attempting to replicate missing or generated columns, report an error.
251+
* Prioritize 'missing' errors if both occur though the prioritization is
252+
* arbitrary.
253+
*/
254+
static void
255+
logicalrep_report_missing_or_gen_attrs(LogicalRepRelation *remoterel,
256+
Bitmapset *missingatts,
257+
Bitmapset *generatedatts)
258+
{
259+
if (!bms_is_empty(missingatts))
249260
ereport(ERROR,
250-
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
251-
errmsg_plural("logical replication target relation \"%s.%s\" is missing replicated column: %s",
252-
"logical replication target relation \"%s.%s\" is missing replicated columns: %s",
253-
missingattcnt,
254-
remoterel->nspname,
255-
remoterel->relname,
256-
missingattsbuf.data)));
257-
}
261+
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
262+
errmsg_plural("logical replication target relation \"%s.%s\" is missing replicated column: %s",
263+
"logical replication target relation \"%s.%s\" is missing replicated columns: %s",
264+
bms_num_members(missingatts),
265+
remoterel->nspname,
266+
remoterel->relname,
267+
logicalrep_get_attrs_str(remoterel,
268+
missingatts)));
269+
270+
if (!bms_is_empty(generatedatts))
271+
ereport(ERROR,
272+
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
273+
errmsg_plural("logical replication target relation \"%s.%s\" has incompatible generated column: %s",
274+
"logical replication target relation \"%s.%s\" has incompatible generated columns: %s",
275+
bms_num_members(generatedatts),
276+
remoterel->nspname,
277+
remoterel->relname,
278+
logicalrep_get_attrs_str(remoterel,
279+
generatedatts)));
258280
}
259281

260282
/*
@@ -380,6 +402,7 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode)
380402
MemoryContext oldctx;
381403
int i;
382404
Bitmapset *missingatts;
405+
Bitmapset *generatedattrs = NULL;
383406

384407
/* Release the no-longer-useful attrmap, if any. */
385408
if (entry->attrmap)
@@ -421,7 +444,7 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode)
421444
int attnum;
422445
Form_pg_attribute attr = TupleDescAttr(desc, i);
423446

424-
if (attr->attisdropped || attr->attgenerated)
447+
if (attr->attisdropped)
425448
{
426449
entry->attrmap->attnums[i] = -1;
427450
continue;
@@ -432,12 +455,20 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode)
432455

433456
entry->attrmap->attnums[i] = attnum;
434457
if (attnum >= 0)
458+
{
459+
/* Remember which subscriber columns are generated. */
460+
if (attr->attgenerated)
461+
generatedattrs = bms_add_member(generatedattrs, attnum);
462+
435463
missingatts = bms_del_member(missingatts, attnum);
464+
}
436465
}
437466

438-
logicalrep_report_missing_attrs(remoterel, missingatts);
467+
logicalrep_report_missing_or_gen_attrs(remoterel, missingatts,
468+
generatedattrs);
439469

440470
/* be tidy */
471+
bms_free(generatedattrs);
441472
bms_free(missingatts);
442473

443474
/*

src/test/subscription/t/011_generated.pl

+42
Original file line numberDiff line numberDiff line change
@@ -326,4 +326,46 @@ BEGIN
326326
$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION sub1");
327327
$node_publisher->safe_psql('postgres', "DROP PUBLICATION pub1");
328328

329+
# =============================================================================
330+
# The following test verifies the expected error when replicating to a
331+
# generated subscriber column. Test the following combinations:
332+
# - regular -> generated
333+
# - generated -> generated
334+
# =============================================================================
335+
336+
# --------------------------------------------------
337+
# A "regular -> generated" or "generated -> generated" replication fails,
338+
# reporting an error that the generated column on the subscriber side cannot
339+
# be replicated.
340+
#
341+
# Test Case: regular -> generated and generated -> generated
342+
# Publisher table has regular column 'c2' and generated column 'c3'.
343+
# Subscriber table has generated columns 'c2' and 'c3'.
344+
# --------------------------------------------------
345+
346+
# Create table and publication. Insert data into the table.
347+
$node_publisher->safe_psql(
348+
'postgres', qq(
349+
CREATE TABLE t1(c1 int, c2 int, c3 int GENERATED ALWAYS AS (c1 * 2) STORED);
350+
CREATE PUBLICATION pub1 for table t1(c1, c2, c3);
351+
INSERT INTO t1 VALUES (1);
352+
));
353+
354+
# Create table and subscription.
355+
$node_subscriber->safe_psql(
356+
'postgres', qq(
357+
CREATE TABLE t1(c1 int, c2 int GENERATED ALWAYS AS (c1 + 2) STORED, c3 int GENERATED ALWAYS AS (c1 + 2) STORED);
358+
CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1;
359+
));
360+
361+
# Verify that an error occurs.
362+
my $offset = -s $node_subscriber->logfile;
363+
$node_subscriber->wait_for_log(
364+
qr/ERROR: ( [A-Z0-9]:)? logical replication target relation "public.t1" has incompatible generated columns: "c2", "c3"/,
365+
$offset);
366+
367+
# cleanup
368+
$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION sub1");
369+
$node_publisher->safe_psql('postgres', "DROP PUBLICATION pub1");
370+
329371
done_testing();

0 commit comments

Comments
 (0)