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:
@ -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),
|
||||
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,
|
||||
remoterel->nspname,
|
||||
remoterel->relname,
|
||||
missingattsbuf.data)));
|
||||
}
|
||||
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",
|
||||
bms_num_members(missingatts),
|
||||
remoterel->nspname,
|
||||
remoterel->relname,
|
||||
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);
|
||||
|
||||
/*
|
||||
|
@ -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();
|
||||
|
Reference in New Issue
Block a user