1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-27 12:41:57 +03:00

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
This commit is contained in:
Amit Kapila
2024-11-27 09:09:20 +05:30
parent d0eb4297cc
commit 8fcd80258b
2 changed files with 105 additions and 32 deletions

View File

@ -220,41 +220,63 @@ logicalrep_rel_att_by_name(LogicalRepRelation *remoterel, const char *attname)
}
/*
* Report error with names of the missing local relation column(s), if any.
* Returns a comma-separated string of attribute names based on the provided
* relation and bitmap indicating which attributes to include.
*/
static char *
logicalrep_get_attrs_str(LogicalRepRelation *remoterel, Bitmapset *atts)
{
StringInfoData attsbuf;
int attcnt = 0;
int i = -1;
Assert(!bms_is_empty(atts));
initStringInfo(&attsbuf);
while ((i = bms_next_member(atts, i)) >= 0)
{
attcnt++;
if (attcnt > 1)
appendStringInfo(&attsbuf, _(", "));
appendStringInfo(&attsbuf, _("\"%s\""), remoterel->attnames[i]);
}
return attsbuf.data;
}
/*
* If attempting to replicate missing or generated columns, report an error.
* Prioritize 'missing' errors if both occur though the prioritization is
* arbitrary.
*/
static void
logicalrep_report_missing_attrs(LogicalRepRelation *remoterel,
Bitmapset *missingatts)
logicalrep_report_missing_or_gen_attrs(LogicalRepRelation *remoterel,
Bitmapset *missingatts,
Bitmapset *generatedatts)
{
if (!bms_is_empty(missingatts))
{
StringInfoData missingattsbuf;
int missingattcnt = 0;
int i;
initStringInfo(&missingattsbuf);
i = -1;
while ((i = bms_next_member(missingatts, i)) >= 0)
{
missingattcnt++;
if (missingattcnt == 1)
appendStringInfo(&missingattsbuf, _("\"%s\""),
remoterel->attnames[i]);
else
appendStringInfo(&missingattsbuf, _(", \"%s\""),
remoterel->attnames[i]);
}
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg_plural("logical replication target relation \"%s.%s\" is missing replicated column: %s",
"logical replication target relation \"%s.%s\" is missing replicated columns: %s",
missingattcnt,
bms_num_members(missingatts),
remoterel->nspname,
remoterel->relname,
missingattsbuf.data)));
}
logicalrep_get_attrs_str(remoterel,
missingatts)));
if (!bms_is_empty(generatedatts))
ereport(ERROR,
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg_plural("logical replication target relation \"%s.%s\" has incompatible generated column: %s",
"logical replication target relation \"%s.%s\" has incompatible generated columns: %s",
bms_num_members(generatedatts),
remoterel->nspname,
remoterel->relname,
logicalrep_get_attrs_str(remoterel,
generatedatts)));
}
/*
@ -380,6 +402,7 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode)
MemoryContext oldctx;
int i;
Bitmapset *missingatts;
Bitmapset *generatedattrs = NULL;
/* Release the no-longer-useful attrmap, if any. */
if (entry->attrmap)
@ -421,7 +444,7 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode)
int attnum;
Form_pg_attribute attr = TupleDescAttr(desc, i);
if (attr->attisdropped || attr->attgenerated)
if (attr->attisdropped)
{
entry->attrmap->attnums[i] = -1;
continue;
@ -432,12 +455,20 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode)
entry->attrmap->attnums[i] = attnum;
if (attnum >= 0)
{
/* Remember which subscriber columns are generated. */
if (attr->attgenerated)
generatedattrs = bms_add_member(generatedattrs, attnum);
missingatts = bms_del_member(missingatts, attnum);
}
}
logicalrep_report_missing_attrs(remoterel, missingatts);
logicalrep_report_missing_or_gen_attrs(remoterel, missingatts,
generatedattrs);
/* be tidy */
bms_free(generatedattrs);
bms_free(missingatts);
/*

View File

@ -326,4 +326,46 @@ is( $result, qq(|2|
$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION sub1");
$node_publisher->safe_psql('postgres', "DROP PUBLICATION pub1");
# =============================================================================
# The following test verifies the expected error when replicating to a
# generated subscriber column. Test the following combinations:
# - regular -> generated
# - generated -> generated
# =============================================================================
# --------------------------------------------------
# A "regular -> generated" or "generated -> generated" replication fails,
# reporting an error that the generated column on the subscriber side cannot
# be replicated.
#
# Test Case: regular -> generated and generated -> generated
# Publisher table has regular column 'c2' and generated column 'c3'.
# Subscriber table has generated columns 'c2' and 'c3'.
# --------------------------------------------------
# Create table and publication. Insert data into the table.
$node_publisher->safe_psql(
'postgres', qq(
CREATE TABLE t1(c1 int, c2 int, c3 int GENERATED ALWAYS AS (c1 * 2) STORED);
CREATE PUBLICATION pub1 for table t1(c1, c2, c3);
INSERT INTO t1 VALUES (1);
));
# Create table and subscription.
$node_subscriber->safe_psql(
'postgres', qq(
CREATE TABLE t1(c1 int, c2 int GENERATED ALWAYS AS (c1 + 2) STORED, c3 int GENERATED ALWAYS AS (c1 + 2) STORED);
CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1;
));
# Verify that an error occurs.
my $offset = -s $node_subscriber->logfile;
$node_subscriber->wait_for_log(
qr/ERROR: ( [A-Z0-9]:)? logical replication target relation "public.t1" has incompatible generated columns: "c2", "c3"/,
$offset);
# cleanup
$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION sub1");
$node_publisher->safe_psql('postgres', "DROP PUBLICATION pub1");
done_testing();