mirror of
https://github.com/postgres/postgres.git
synced 2026-07-09 09:41:00 -04:00
Whole-row fixes for ALTER COLUMN SET EXPRESSION
When changing the expression of a generated column via ALTER TABLE
ALTER COLUMN SET EXPRESSION, objects that depend on the column via
indirect whole-row references (such as CHECK constraints, indexes)
must be handled specially, because technically pg_depend does not
contain such dependencies, see
recordDependencyOnSingleRelExpr->find_expr_references_walker.
This is a fix for commit f80bedd52, "Allow ALTER COLUMN SET EXPRESSION
on virtual columns with CHECK constraints".
Author: jian he <jian.universality@gmail.com>
Co-authored-by: Peter Eisentraut <peter@eisentraut.org>
Reported-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Reviewed-by: solai v <solai.cdac@gmail.com>
Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
Discussion: https://www.postgresql.org/message-id/flat/CAJTYsWXOkyeDVbzymWc9sKrq7Y_MUv6XJXN4H9GfsBOPd3NJ+w@mail.gmail.com
This commit is contained in:
parent
165dc09b28
commit
9530898940
5 changed files with 214 additions and 0 deletions
|
|
@ -693,6 +693,7 @@ static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
|
|||
AlterTableCmd *cmd, LOCKMODE lockmode);
|
||||
static void RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
|
||||
Relation rel, AttrNumber attnum, const char *colName);
|
||||
static void RememberWholeRowDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype, Relation rel);
|
||||
static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
|
||||
static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
|
||||
static void RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab);
|
||||
|
|
@ -8773,6 +8774,13 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
|
|||
*/
|
||||
RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName);
|
||||
|
||||
/*
|
||||
* Find whole-row referenced objects that depend on the column
|
||||
* (constraints, indexes, etc.), and record enough information to let us
|
||||
* recreate the objects.
|
||||
*/
|
||||
RememberWholeRowDependentForRebuilding(tab, AT_SetExpression, rel);
|
||||
|
||||
/*
|
||||
* Drop the dependency records of the GENERATED expression, in particular
|
||||
* its INTERNAL dependency on the column, which would otherwise cause
|
||||
|
|
@ -15735,6 +15743,174 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
|
|||
table_close(depRel, NoLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Record information about dependencies between objects with whole-row Var
|
||||
* references (indexes, check constraints, etc.) and the relation.
|
||||
*
|
||||
* See also RememberAllDependentForRebuilding, which handles non-whole-row Var
|
||||
* references.
|
||||
*
|
||||
* This function currently applies only to ALTER COLUMN SET EXPRESSION.
|
||||
*/
|
||||
static void
|
||||
RememberWholeRowDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype, Relation rel)
|
||||
{
|
||||
ScanKeyData skey;
|
||||
Relation pg_constraint;
|
||||
Relation pg_index;
|
||||
SysScanDesc conscan;
|
||||
SysScanDesc indscan;
|
||||
HeapTuple constrTuple;
|
||||
HeapTuple indexTuple;
|
||||
bool isnull;
|
||||
|
||||
Assert(subtype == AT_SetExpression);
|
||||
|
||||
/*
|
||||
* Check CHECK constraints with whole-row references first.
|
||||
*/
|
||||
if (RelationGetDescr(rel)->constr &&
|
||||
RelationGetDescr(rel)->constr->num_check > 0)
|
||||
{
|
||||
pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
|
||||
|
||||
ScanKeyInit(&skey,
|
||||
Anum_pg_constraint_conrelid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(RelationGetRelid(rel)));
|
||||
|
||||
conscan = systable_beginscan(pg_constraint,
|
||||
ConstraintRelidTypidNameIndexId,
|
||||
true,
|
||||
NULL,
|
||||
1,
|
||||
&skey);
|
||||
|
||||
while (HeapTupleIsValid(constrTuple = systable_getnext(conscan)))
|
||||
{
|
||||
Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(constrTuple);
|
||||
Datum exprDatum;
|
||||
|
||||
if (conform->contype != CONSTRAINT_CHECK)
|
||||
continue;
|
||||
|
||||
exprDatum = heap_getattr(constrTuple,
|
||||
Anum_pg_constraint_conbin,
|
||||
RelationGetDescr(pg_constraint),
|
||||
&isnull);
|
||||
if (isnull)
|
||||
elog(ERROR, "null conbin for relation \"%s\"",
|
||||
RelationGetRelationName(rel));
|
||||
else
|
||||
{
|
||||
char *exprString;
|
||||
Node *expr;
|
||||
Bitmapset *expr_attrs = NULL;
|
||||
|
||||
exprString = TextDatumGetCString(exprDatum);
|
||||
expr = stringToNode(exprString);
|
||||
pfree(exprString);
|
||||
|
||||
/* Find all attributes referenced */
|
||||
pull_varattnos(expr, 1, &expr_attrs);
|
||||
|
||||
/*
|
||||
* If the CHECK constraint contains whole-row reference then
|
||||
* remember it.
|
||||
*/
|
||||
if (bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, expr_attrs))
|
||||
{
|
||||
RememberConstraintForRebuilding(conform->oid, tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
systable_endscan(conscan);
|
||||
table_close(pg_constraint, AccessShareLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now check indexes with whole-row references. Prepare to scan pg_index
|
||||
* for entries having indrelid matching this relation.
|
||||
*/
|
||||
ScanKeyInit(&skey,
|
||||
Anum_pg_index_indrelid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(RelationGetRelid(rel)));
|
||||
|
||||
pg_index = table_open(IndexRelationId, AccessShareLock);
|
||||
|
||||
indscan = systable_beginscan(pg_index,
|
||||
IndexIndrelidIndexId,
|
||||
true,
|
||||
NULL,
|
||||
1,
|
||||
&skey);
|
||||
|
||||
while (HeapTupleIsValid(indexTuple = systable_getnext(indscan)))
|
||||
{
|
||||
Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
|
||||
Datum exprDatum;
|
||||
|
||||
exprDatum = heap_getattr(indexTuple,
|
||||
Anum_pg_index_indexprs,
|
||||
RelationGetDescr(pg_index),
|
||||
&isnull);
|
||||
if (!isnull)
|
||||
{
|
||||
char *exprString;
|
||||
Node *expr;
|
||||
Bitmapset *expr_attrs = NULL;
|
||||
|
||||
exprString = TextDatumGetCString(exprDatum);
|
||||
expr = stringToNode(exprString);
|
||||
pfree(exprString);
|
||||
|
||||
/* Find all attributes referenced */
|
||||
pull_varattnos(expr, 1, &expr_attrs);
|
||||
|
||||
/*
|
||||
* If the index expression contains a whole-row reference then
|
||||
* remember it.
|
||||
*/
|
||||
if (bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, expr_attrs))
|
||||
{
|
||||
RememberIndexForRebuilding(index->indexrelid, tab);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
exprDatum = heap_getattr(indexTuple,
|
||||
Anum_pg_index_indpred,
|
||||
RelationGetDescr(pg_index),
|
||||
&isnull);
|
||||
if (!isnull)
|
||||
{
|
||||
char *exprString;
|
||||
Node *expr;
|
||||
Bitmapset *expr_attrs = NULL;
|
||||
|
||||
exprString = TextDatumGetCString(exprDatum);
|
||||
expr = stringToNode(exprString);
|
||||
pfree(exprString);
|
||||
|
||||
/* Find all attributes referenced */
|
||||
pull_varattnos(expr, 1, &expr_attrs);
|
||||
|
||||
/*
|
||||
* If the index predicate expression contains a whole-row
|
||||
* reference then remember it.
|
||||
*/
|
||||
if (bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, expr_attrs))
|
||||
{
|
||||
RememberIndexForRebuilding(index->indexrelid, tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
systable_endscan(indscan);
|
||||
table_close(pg_index, AccessShareLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Subroutine for ATExecAlterColumnType: remember that a replica identity
|
||||
* needs to be reset.
|
||||
|
|
|
|||
|
|
@ -688,6 +688,15 @@ INSERT INTO gtest20c VALUES (1); -- ok
|
|||
INSERT INTO gtest20c VALUES (NULL); -- fails
|
||||
ERROR: new row for relation "gtest20c" violates check constraint "whole_row_check"
|
||||
DETAIL: Failing row contains (null, null).
|
||||
ALTER TABLE gtest20c ALTER COLUMN b SET EXPRESSION AS (NULL::int); -- violates constraint
|
||||
ERROR: check constraint "whole_row_check" of relation "gtest20c" is violated by some row
|
||||
-- index with whole-row reference needs rebuild
|
||||
CREATE TABLE gtest20d (a int, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
INSERT INTO gtest20d VALUES (1), (1);
|
||||
CREATE INDEX gtest20d_idx1 ON gtest20d (a) WHERE gtest20d = ROW (1, 2);
|
||||
ALTER TABLE gtest20d ALTER COLUMN b SET EXPRESSION AS (a * 2::bigint); -- index rebuild
|
||||
CREATE INDEX gtest20d_idx2 ON gtest20d ((gtest20d = ROW (1, 2)));
|
||||
ALTER TABLE gtest20d ALTER COLUMN b SET EXPRESSION AS (a * 3); -- index rebuild
|
||||
-- not-null constraints
|
||||
CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED NOT NULL);
|
||||
INSERT INTO gtest21a (a) VALUES (1); -- ok
|
||||
|
|
|
|||
|
|
@ -694,6 +694,15 @@ INSERT INTO gtest20c VALUES (1); -- ok
|
|||
INSERT INTO gtest20c VALUES (NULL); -- fails
|
||||
ERROR: new row for relation "gtest20c" violates check constraint "whole_row_check"
|
||||
DETAIL: Failing row contains (null, virtual).
|
||||
ALTER TABLE gtest20c ALTER COLUMN b SET EXPRESSION AS (NULL::int); -- violates constraint
|
||||
ERROR: check constraint "whole_row_check" of relation "gtest20c" is violated by some row
|
||||
-- index with whole-row reference needs rebuild
|
||||
CREATE TABLE gtest20d (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
|
||||
INSERT INTO gtest20d VALUES (1), (1);
|
||||
CREATE INDEX gtest20d_idx1 ON gtest20d (a) WHERE gtest20d = ROW (1, 2);
|
||||
ALTER TABLE gtest20d ALTER COLUMN b SET EXPRESSION AS (a * 2::bigint); -- index rebuild
|
||||
CREATE INDEX gtest20d_idx2 ON gtest20d ((gtest20d = ROW (1, 2)));
|
||||
ALTER TABLE gtest20d ALTER COLUMN b SET EXPRESSION AS (a * 3); -- index rebuild
|
||||
-- not-null constraints
|
||||
CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL NOT NULL);
|
||||
INSERT INTO gtest21a (a) VALUES (1); -- ok
|
||||
|
|
|
|||
|
|
@ -341,6 +341,16 @@ CREATE TABLE gtest20c (a int, b int GENERATED ALWAYS AS (a * 2) STORED);
|
|||
ALTER TABLE gtest20c ADD CONSTRAINT whole_row_check CHECK (gtest20c IS NOT NULL);
|
||||
INSERT INTO gtest20c VALUES (1); -- ok
|
||||
INSERT INTO gtest20c VALUES (NULL); -- fails
|
||||
ALTER TABLE gtest20c ALTER COLUMN b SET EXPRESSION AS (NULL::int); -- violates constraint
|
||||
|
||||
-- index with whole-row reference needs rebuild
|
||||
CREATE TABLE gtest20d (a int, b int GENERATED ALWAYS AS (a * 2) STORED);
|
||||
INSERT INTO gtest20d VALUES (1), (1);
|
||||
CREATE INDEX gtest20d_idx1 ON gtest20d (a) WHERE gtest20d = ROW (1, 2);
|
||||
|
||||
ALTER TABLE gtest20d ALTER COLUMN b SET EXPRESSION AS (a * 2::bigint); -- index rebuild
|
||||
CREATE INDEX gtest20d_idx2 ON gtest20d ((gtest20d = ROW (1, 2)));
|
||||
ALTER TABLE gtest20d ALTER COLUMN b SET EXPRESSION AS (a * 3); -- index rebuild
|
||||
|
||||
-- not-null constraints
|
||||
CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED NOT NULL);
|
||||
|
|
|
|||
|
|
@ -347,6 +347,16 @@ CREATE TABLE gtest20c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
|
|||
ALTER TABLE gtest20c ADD CONSTRAINT whole_row_check CHECK (gtest20c IS NOT NULL);
|
||||
INSERT INTO gtest20c VALUES (1); -- ok
|
||||
INSERT INTO gtest20c VALUES (NULL); -- fails
|
||||
ALTER TABLE gtest20c ALTER COLUMN b SET EXPRESSION AS (NULL::int); -- violates constraint
|
||||
|
||||
-- index with whole-row reference needs rebuild
|
||||
CREATE TABLE gtest20d (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
|
||||
INSERT INTO gtest20d VALUES (1), (1);
|
||||
CREATE INDEX gtest20d_idx1 ON gtest20d (a) WHERE gtest20d = ROW (1, 2);
|
||||
|
||||
ALTER TABLE gtest20d ALTER COLUMN b SET EXPRESSION AS (a * 2::bigint); -- index rebuild
|
||||
CREATE INDEX gtest20d_idx2 ON gtest20d ((gtest20d = ROW (1, 2)));
|
||||
ALTER TABLE gtest20d ALTER COLUMN b SET EXPRESSION AS (a * 3); -- index rebuild
|
||||
|
||||
-- not-null constraints
|
||||
CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL NOT NULL);
|
||||
|
|
|
|||
Loading…
Reference in a new issue