Fix application of identity values in some cases
Investigation of 2d2d06b7e27e3177d5bef0061801c75946871db3 revealed that identity values were not applied in some further cases, including logical replication subscribers, VALUES RTEs, and ALTER TABLE ... ADD COLUMN. To fix all that, apply the identity column expression in build_column_default() instead of repeating the same logic at each call site. For ALTER TABLE ... ADD COLUMN ... IDENTITY, the previous coding completely ignored that existing rows for the new column should have values filled in from the identity sequence. The coding using build_column_default() fails for this because the sequence ownership isn't registered until after ALTER TABLE, and we can't do it before because we don't have the column in the catalog yet. So we specially remember in ColumnDef the sequence name that we decided on and build a custom NextValueExpr using that. Reviewed-by: Michael Paquier <michael.paquier@gmail.com>
This commit is contained in:
parent
9da0cc3528
commit
533c5d8bdd
@ -23,7 +23,6 @@
|
|||||||
#include "access/sysattr.h"
|
#include "access/sysattr.h"
|
||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "access/xlog.h"
|
#include "access/xlog.h"
|
||||||
#include "catalog/dependency.h"
|
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/copy.h"
|
#include "commands/copy.h"
|
||||||
#include "commands/defrem.h"
|
#include "commands/defrem.h"
|
||||||
@ -2996,19 +2995,8 @@ BeginCopyFrom(ParseState *pstate,
|
|||||||
{
|
{
|
||||||
/* attribute is NOT to be copied from input */
|
/* attribute is NOT to be copied from input */
|
||||||
/* use default value if one exists */
|
/* use default value if one exists */
|
||||||
Expr *defexpr;
|
Expr *defexpr = (Expr *) build_column_default(cstate->rel,
|
||||||
|
attnum);
|
||||||
if (att->attidentity)
|
|
||||||
{
|
|
||||||
NextValueExpr *nve = makeNode(NextValueExpr);
|
|
||||||
|
|
||||||
nve->seqid = getOwnedSequence(RelationGetRelid(cstate->rel),
|
|
||||||
attnum);
|
|
||||||
nve->typeId = att->atttypid;
|
|
||||||
defexpr = (Expr *) nve;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
defexpr = (Expr *) build_column_default(cstate->rel, attnum);
|
|
||||||
|
|
||||||
if (defexpr != NULL)
|
if (defexpr != NULL)
|
||||||
{
|
{
|
||||||
|
@ -5486,7 +5486,21 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
|||||||
if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE
|
if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE
|
||||||
&& relkind != RELKIND_FOREIGN_TABLE && attribute.attnum > 0)
|
&& relkind != RELKIND_FOREIGN_TABLE && attribute.attnum > 0)
|
||||||
{
|
{
|
||||||
defval = (Expr *) build_column_default(rel, attribute.attnum);
|
/*
|
||||||
|
* For an identity column, we can't use build_column_default(),
|
||||||
|
* because the sequence ownership isn't set yet. So do it manually.
|
||||||
|
*/
|
||||||
|
if (colDef->identity)
|
||||||
|
{
|
||||||
|
NextValueExpr *nve = makeNode(NextValueExpr);
|
||||||
|
|
||||||
|
nve->seqid = RangeVarGetRelid(colDef->identitySequence, NoLock, false);
|
||||||
|
nve->typeId = typeOid;
|
||||||
|
|
||||||
|
defval = (Expr *) nve;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
defval = (Expr *) build_column_default(rel, attribute.attnum);
|
||||||
|
|
||||||
if (!defval && DomainHasConstraints(typeOid))
|
if (!defval && DomainHasConstraints(typeOid))
|
||||||
{
|
{
|
||||||
|
@ -2819,6 +2819,7 @@ _copyColumnDef(const ColumnDef *from)
|
|||||||
COPY_NODE_FIELD(raw_default);
|
COPY_NODE_FIELD(raw_default);
|
||||||
COPY_NODE_FIELD(cooked_default);
|
COPY_NODE_FIELD(cooked_default);
|
||||||
COPY_SCALAR_FIELD(identity);
|
COPY_SCALAR_FIELD(identity);
|
||||||
|
COPY_NODE_FIELD(identitySequence);
|
||||||
COPY_NODE_FIELD(collClause);
|
COPY_NODE_FIELD(collClause);
|
||||||
COPY_SCALAR_FIELD(collOid);
|
COPY_SCALAR_FIELD(collOid);
|
||||||
COPY_NODE_FIELD(constraints);
|
COPY_NODE_FIELD(constraints);
|
||||||
|
@ -2564,6 +2564,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
|
|||||||
COMPARE_NODE_FIELD(raw_default);
|
COMPARE_NODE_FIELD(raw_default);
|
||||||
COMPARE_NODE_FIELD(cooked_default);
|
COMPARE_NODE_FIELD(cooked_default);
|
||||||
COMPARE_SCALAR_FIELD(identity);
|
COMPARE_SCALAR_FIELD(identity);
|
||||||
|
COMPARE_NODE_FIELD(identitySequence);
|
||||||
COMPARE_NODE_FIELD(collClause);
|
COMPARE_NODE_FIELD(collClause);
|
||||||
COMPARE_SCALAR_FIELD(collOid);
|
COMPARE_SCALAR_FIELD(collOid);
|
||||||
COMPARE_NODE_FIELD(constraints);
|
COMPARE_NODE_FIELD(constraints);
|
||||||
|
@ -2814,6 +2814,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
|
|||||||
WRITE_NODE_FIELD(raw_default);
|
WRITE_NODE_FIELD(raw_default);
|
||||||
WRITE_NODE_FIELD(cooked_default);
|
WRITE_NODE_FIELD(cooked_default);
|
||||||
WRITE_CHAR_FIELD(identity);
|
WRITE_CHAR_FIELD(identity);
|
||||||
|
WRITE_NODE_FIELD(identitySequence);
|
||||||
WRITE_NODE_FIELD(collClause);
|
WRITE_NODE_FIELD(collClause);
|
||||||
WRITE_OID_FIELD(collOid);
|
WRITE_OID_FIELD(collOid);
|
||||||
WRITE_NODE_FIELD(constraints);
|
WRITE_NODE_FIELD(constraints);
|
||||||
|
@ -472,6 +472,14 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
|
|||||||
|
|
||||||
cxt->blist = lappend(cxt->blist, seqstmt);
|
cxt->blist = lappend(cxt->blist, seqstmt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store the identity sequence name that we decided on. ALTER TABLE
|
||||||
|
* ... ADD COLUMN ... IDENTITY needs this so that it can fill the new
|
||||||
|
* column with values from the sequence, while the association of the
|
||||||
|
* sequence with the table is not set until after the ALTER TABLE.
|
||||||
|
*/
|
||||||
|
column->identitySequence = seqstmt->sequence;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence as
|
* Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence as
|
||||||
* owned by this column, and add it to the list of things to be done after
|
* owned by this column, and add it to the list of things to be done after
|
||||||
|
@ -844,17 +844,7 @@ rewriteTargetListIU(List *targetList,
|
|||||||
{
|
{
|
||||||
Node *new_expr;
|
Node *new_expr;
|
||||||
|
|
||||||
if (att_tup->attidentity)
|
new_expr = build_column_default(target_relation, attrno);
|
||||||
{
|
|
||||||
NextValueExpr *nve = makeNode(NextValueExpr);
|
|
||||||
|
|
||||||
nve->seqid = getOwnedSequence(RelationGetRelid(target_relation), attrno);
|
|
||||||
nve->typeId = att_tup->atttypid;
|
|
||||||
|
|
||||||
new_expr = (Node *) nve;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
new_expr = build_column_default(target_relation, attrno);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If there is no default (ie, default is effectively NULL), we
|
* If there is no default (ie, default is effectively NULL), we
|
||||||
@ -1123,6 +1113,16 @@ build_column_default(Relation rel, int attrno)
|
|||||||
Node *expr = NULL;
|
Node *expr = NULL;
|
||||||
Oid exprtype;
|
Oid exprtype;
|
||||||
|
|
||||||
|
if (att_tup->attidentity)
|
||||||
|
{
|
||||||
|
NextValueExpr *nve = makeNode(NextValueExpr);
|
||||||
|
|
||||||
|
nve->seqid = getOwnedSequence(RelationGetRelid(rel), attrno);
|
||||||
|
nve->typeId = att_tup->atttypid;
|
||||||
|
|
||||||
|
return (Node *) nve;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Scan to see if relation has a default for this column.
|
* Scan to see if relation has a default for this column.
|
||||||
*/
|
*/
|
||||||
|
@ -647,6 +647,8 @@ typedef struct ColumnDef
|
|||||||
Node *raw_default; /* default value (untransformed parse tree) */
|
Node *raw_default; /* default value (untransformed parse tree) */
|
||||||
Node *cooked_default; /* default value (transformed expr tree) */
|
Node *cooked_default; /* default value (transformed expr tree) */
|
||||||
char identity; /* attidentity setting */
|
char identity; /* attidentity setting */
|
||||||
|
RangeVar *identitySequence; /* to store identity sequence name for ALTER
|
||||||
|
* TABLE ... ADD COLUMN */
|
||||||
CollateClause *collClause; /* untransformed COLLATE spec, if any */
|
CollateClause *collClause; /* untransformed COLLATE spec, if any */
|
||||||
Oid collOid; /* collation OID (InvalidOid if not set) */
|
Oid collOid; /* collation OID (InvalidOid if not set) */
|
||||||
List *constraints; /* other constraints on column */
|
List *constraints; /* other constraints on column */
|
||||||
|
@ -104,6 +104,19 @@ SELECT * FROM itest4;
|
|||||||
2 |
|
2 |
|
||||||
(2 rows)
|
(2 rows)
|
||||||
|
|
||||||
|
-- VALUES RTEs
|
||||||
|
INSERT INTO itest3 VALUES (DEFAULT, 'a');
|
||||||
|
INSERT INTO itest3 VALUES (DEFAULT, 'b'), (DEFAULT, 'c');
|
||||||
|
SELECT * FROM itest3;
|
||||||
|
a | b
|
||||||
|
----+---
|
||||||
|
7 |
|
||||||
|
12 |
|
||||||
|
17 | a
|
||||||
|
22 | b
|
||||||
|
27 | c
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
-- OVERRIDING tests
|
-- OVERRIDING tests
|
||||||
INSERT INTO itest1 VALUES (10, 'xyz');
|
INSERT INTO itest1 VALUES (10, 'xyz');
|
||||||
INSERT INTO itest1 OVERRIDING USER VALUE VALUES (10, 'xyz');
|
INSERT INTO itest1 OVERRIDING USER VALUE VALUES (10, 'xyz');
|
||||||
@ -237,6 +250,21 @@ SELECT * FROM itestv11;
|
|||||||
11 | xyz
|
11 | xyz
|
||||||
(3 rows)
|
(3 rows)
|
||||||
|
|
||||||
|
-- ADD COLUMN
|
||||||
|
CREATE TABLE itest13 (a int);
|
||||||
|
-- add column to empty table
|
||||||
|
ALTER TABLE itest13 ADD COLUMN b int GENERATED BY DEFAULT AS IDENTITY;
|
||||||
|
INSERT INTO itest13 VALUES (1), (2), (3);
|
||||||
|
-- add column to populated table
|
||||||
|
ALTER TABLE itest13 ADD COLUMN c int GENERATED BY DEFAULT AS IDENTITY;
|
||||||
|
SELECT * FROM itest13;
|
||||||
|
a | b | c
|
||||||
|
---+---+---
|
||||||
|
1 | 1 | 1
|
||||||
|
2 | 2 | 2
|
||||||
|
3 | 3 | 3
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
-- various ALTER COLUMN tests
|
-- various ALTER COLUMN tests
|
||||||
-- fail, not allowed for identity columns
|
-- fail, not allowed for identity columns
|
||||||
ALTER TABLE itest1 ALTER COLUMN a SET DEFAULT 1;
|
ALTER TABLE itest1 ALTER COLUMN a SET DEFAULT 1;
|
||||||
|
@ -54,6 +54,14 @@ SELECT * FROM itest3;
|
|||||||
SELECT * FROM itest4;
|
SELECT * FROM itest4;
|
||||||
|
|
||||||
|
|
||||||
|
-- VALUES RTEs
|
||||||
|
|
||||||
|
INSERT INTO itest3 VALUES (DEFAULT, 'a');
|
||||||
|
INSERT INTO itest3 VALUES (DEFAULT, 'b'), (DEFAULT, 'c');
|
||||||
|
|
||||||
|
SELECT * FROM itest3;
|
||||||
|
|
||||||
|
|
||||||
-- OVERRIDING tests
|
-- OVERRIDING tests
|
||||||
|
|
||||||
INSERT INTO itest1 VALUES (10, 'xyz');
|
INSERT INTO itest1 VALUES (10, 'xyz');
|
||||||
@ -138,6 +146,17 @@ INSERT INTO itestv11 OVERRIDING SYSTEM VALUE VALUES (11, 'xyz');
|
|||||||
SELECT * FROM itestv11;
|
SELECT * FROM itestv11;
|
||||||
|
|
||||||
|
|
||||||
|
-- ADD COLUMN
|
||||||
|
|
||||||
|
CREATE TABLE itest13 (a int);
|
||||||
|
-- add column to empty table
|
||||||
|
ALTER TABLE itest13 ADD COLUMN b int GENERATED BY DEFAULT AS IDENTITY;
|
||||||
|
INSERT INTO itest13 VALUES (1), (2), (3);
|
||||||
|
-- add column to populated table
|
||||||
|
ALTER TABLE itest13 ADD COLUMN c int GENERATED BY DEFAULT AS IDENTITY;
|
||||||
|
SELECT * FROM itest13;
|
||||||
|
|
||||||
|
|
||||||
-- various ALTER COLUMN tests
|
-- various ALTER COLUMN tests
|
||||||
|
|
||||||
-- fail, not allowed for identity columns
|
-- fail, not allowed for identity columns
|
||||||
|
@ -3,7 +3,7 @@ use strict;
|
|||||||
use warnings;
|
use warnings;
|
||||||
use PostgresNode;
|
use PostgresNode;
|
||||||
use TestLib;
|
use TestLib;
|
||||||
use Test::More tests => 3;
|
use Test::More tests => 4;
|
||||||
|
|
||||||
# Create publisher node
|
# Create publisher node
|
||||||
my $node_publisher = get_new_node('publisher');
|
my $node_publisher = get_new_node('publisher');
|
||||||
@ -22,7 +22,7 @@ $node_publisher->safe_psql('postgres',
|
|||||||
"INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')");
|
"INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')");
|
||||||
|
|
||||||
# Setup structure on subscriber
|
# Setup structure on subscriber
|
||||||
$node_subscriber->safe_psql('postgres', "CREATE TABLE test_tab (a int primary key, b text, c timestamptz DEFAULT now(), d bigint DEFAULT 999)");
|
$node_subscriber->safe_psql('postgres', "CREATE TABLE test_tab (a int primary key, b text, c timestamptz DEFAULT now(), d bigint DEFAULT 999, e int GENERATED BY DEFAULT AS IDENTITY)");
|
||||||
|
|
||||||
# Setup logical replication
|
# Setup logical replication
|
||||||
my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
|
my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
|
||||||
@ -52,8 +52,8 @@ $node_publisher->safe_psql('postgres', "UPDATE test_tab SET b = md5(b)");
|
|||||||
$node_publisher->wait_for_catchup($appname);
|
$node_publisher->wait_for_catchup($appname);
|
||||||
|
|
||||||
$result =
|
$result =
|
||||||
$node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999) FROM test_tab");
|
$node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999), count(e) FROM test_tab");
|
||||||
is($result, qq(2|2|2), 'check extra columns contain local defaults');
|
is($result, qq(2|2|2|2), 'check extra columns contain local defaults after copy');
|
||||||
|
|
||||||
# Change the local values of the extra columns on the subscriber,
|
# Change the local values of the extra columns on the subscriber,
|
||||||
# update publisher, and check that subscriber retains the expected
|
# update publisher, and check that subscriber retains the expected
|
||||||
@ -67,5 +67,15 @@ $result =
|
|||||||
$node_subscriber->safe_psql('postgres', "SELECT count(*), count(extract(epoch from c) = 987654321), count(d = 999) FROM test_tab");
|
$node_subscriber->safe_psql('postgres', "SELECT count(*), count(extract(epoch from c) = 987654321), count(d = 999) FROM test_tab");
|
||||||
is($result, qq(2|2|2), 'check extra columns contain locally changed data');
|
is($result, qq(2|2|2), 'check extra columns contain locally changed data');
|
||||||
|
|
||||||
|
# Another insert
|
||||||
|
$node_publisher->safe_psql('postgres',
|
||||||
|
"INSERT INTO test_tab VALUES (3, 'baz')");
|
||||||
|
|
||||||
|
$node_publisher->wait_for_catchup($appname);
|
||||||
|
|
||||||
|
$result =
|
||||||
|
$node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999), count(e) FROM test_tab");
|
||||||
|
is($result, qq(3|3|3|3), 'check extra columns contain local defaults after apply');
|
||||||
|
|
||||||
$node_subscriber->stop;
|
$node_subscriber->stop;
|
||||||
$node_publisher->stop;
|
$node_publisher->stop;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user