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

Lines changed: 61 additions & 30 deletions
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

Lines changed: 42 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy