On Mon, May 25, 2026 at 2:43 PM jian he <[email protected]> wrote: > > Overall, the attached v9 doesn't include any major changes. > It just contains some refactoring to make the coding style consistent with > [1]. >
Hi. Rebase because of https://git.postgresql.org/cgit/postgresql.git/commit/?id=a4639d64e2199885f8e995395b6fe874cb7228bf I also simplified the code a little bit, polished the comments, and merged 3 patches into one, no major changes. Below is the commit message: Subject: [PATCH v14 1/1] Whole-row fixes for DROP COLUMN, SET COLUMN DATA TYPE ALTER TABLE DROP COLUMN should remove indexes or constraints contain whole-row references, just like non-whole-row column. ALTER TABLE DROP COLUMN should fail if a trigger WHEN clause or row-level security policy contains a whole-row reference. To do this, record a dependency between the relation and the trigger or policy in RememberWholeRowDependentForRebuilding; performMultipleDeletions then handles the deletion checks. ALTER COLUMN SET DATA TYPE fundamentally changes the table’s record type; At present, we cannot compare records that contain columns of dissimilar types, see function record_eq. As a result, ALTER COLUMN SET DATA TYPE does not work for whole-row reference objects (such as constraints and indexes), and must therefore raise an error. discussion: https://postgr.es/m/cacjufxga6kvqy7dbhglvw9s9kkmpgyzt5me6c7kefjdpr2w...@mail.gmail.com commitfest: https://commitfest.postgresql.org/patch/6055 ---------------------------------------------------------------------------------------------- -- jian https://www.enterprisedb.com/
From 6973c85d1868ce0a39596a2415a66c47affd9637 Mon Sep 17 00:00:00 2001 From: jian he <[email protected]> Date: Tue, 14 Jul 2026 11:57:52 +0800 Subject: [PATCH v14 1/1] Whole-row fixes for DROP COLUMN, SET COLUMN DATA TYPE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ALTER TABLE DROP COLUMN should remove indexes or constraints contain whole-row references, just like non-whole-row column. ALTER TABLE DROP COLUMN should fail if a trigger WHEN clause or row-level security policy contains a whole-row reference. To do this, record a dependency between the relation and the trigger or policy in RememberWholeRowDependentForRebuilding; performMultipleDeletions then handles the deletion checks. ALTER COLUMN SET DATA TYPE fundamentally changes the table’s record type; At present, we cannot compare records that contain columns of dissimilar types, see function record_eq. As a result, ALTER COLUMN SET DATA TYPE does not work for whole-row reference objects (such as constraints and indexes), and must therefore raise an error. discussion: https://postgr.es/m/cacjufxga6kvqy7dbhglvw9s9kkmpgyzt5me6c7kefjdpr2w...@mail.gmail.com commitfest: https://commitfest.postgresql.org/patch/6055 --- src/backend/commands/tablecmds.c | 319 ++++++++++++++++++++- src/backend/optimizer/util/var.c | 46 +++ src/include/optimizer/optimizer.h | 1 + src/test/regress/expected/constraints.out | 23 ++ src/test/regress/expected/foreign_data.out | 14 + src/test/regress/expected/indexing.out | 32 +++ src/test/regress/expected/rowsecurity.out | 29 +- src/test/regress/expected/triggers.out | 28 ++ src/test/regress/sql/constraints.sql | 17 ++ src/test/regress/sql/foreign_data.sql | 10 + src/test/regress/sql/indexing.sql | 17 ++ src/test/regress/sql/rowsecurity.sql | 17 ++ src/test/regress/sql/triggers.sql | 17 ++ 13 files changed, 560 insertions(+), 10 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index cb93c3e935a..d53f410efe1 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -693,7 +693,13 @@ 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 RememberWholeRowDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype, + Relation rel, AttrNumber attnum, + const char *colName); +static void recordWholeRowDependencyOrError(AlteredTableInfo *tab, AlterTableType subtype, + Relation rel, AttrNumber attnum, + const ObjectAddress *depender, + DependencyType behavior); static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab); static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab); static void RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab); @@ -795,6 +801,8 @@ static List *collectPartitionIndexExtDeps(List *partitionOids); static void applyPartitionIndexExtDeps(Oid newPartOid, List *extDepState); static void freePartitionIndexExtDeps(List *extDepState); +static List *GetAllRelAssociatedPolicies(Relation rel); + /* ---------------------------------------------------------------- * DefineRelation * Creates a new relation. @@ -8822,7 +8830,7 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName, * (constraints, indexes, etc.), and record enough information to let us * recreate the objects. */ - RememberWholeRowDependentForRebuilding(tab, AT_SetExpression, rel); + RememberWholeRowDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName); /* * Drop the dependency records of the GENERATED expression, in particular @@ -9428,6 +9436,10 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, List *children; ObjectAddress object; bool is_expr; + AlteredTableInfo *tab; + + /* Find or create work queue entry for this table */ + tab = ATGetQueueEntry(wqueue, rel); /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) @@ -9500,6 +9512,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, ReleaseSysCache(tuple); + /* + * Record dependencies between this relation and any objects containing + * whole-row Var references. performMultipleDeletions will take care of + * removing these dependencies later. + */ + RememberWholeRowDependentForRebuilding(tab, AT_DropColumn, rel, attnum, colName); + + CommandCounterIncrement(); + /* * Propagate to children as appropriate. Unlike most other ALTER * routines, we have to do this one level of recursion at a time; we can't @@ -15377,6 +15398,14 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, */ RememberAllDependentForRebuilding(tab, AT_AlterColumnType, rel, attnum, colName); + /* + * ALTER COLUMN SET DATA TYPE fundamentally changes the table’s record + * type; At present, we cannot compare records that contain columns of + * dissimilar types, see function record_eq, so errr out for wholerow + * dependencies. + */ + RememberWholeRowDependentForRebuilding(tab, AT_AlterColumnType, rel, attnum, colName); + /* * Now scan for dependencies of this column on other things. The only * things we should find are the dependency on the column datatype and @@ -15805,21 +15834,30 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype, * See also RememberAllDependentForRebuilding, which handles non-whole-row Var * references. * - * This function currently applies only to ALTER COLUMN SET EXPRESSION. + * This function currently applies to ALTER COLUMN SET EXPRESSION, + * ALTER COLUMN SET DATA TYPE, and ALTER TABLE DROP COLUMN. */ static void -RememberWholeRowDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype, Relation rel) +RememberWholeRowDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype, Relation rel, + AttrNumber attnum, const char *colName) { ScanKeyData skey; Relation pg_constraint; + Relation pg_policy; Relation pg_index; SysScanDesc conscan; SysScanDesc indscan; + SysScanDesc policyscan; HeapTuple constrTuple; HeapTuple indexTuple; + HeapTuple policyTuple; bool isnull; + List *pols = NIL; + Oid reltypid; - Assert(subtype == AT_SetExpression); + Assert(subtype == AT_SetExpression || + subtype == AT_AlterColumnType || + subtype == AT_DropColumn); /* * Check CHECK constraints with whole-row references first. @@ -15875,7 +15913,21 @@ RememberWholeRowDependentForRebuilding(AlteredTableInfo *tab, AlterTableType sub */ if (bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, expr_attrs)) { - RememberConstraintForRebuilding(conform->oid, tab); + if (subtype == AT_SetExpression) + RememberConstraintForRebuilding(conform->oid, tab); + else + { + ObjectAddress con_obj; + + ObjectAddressSet(con_obj, ConstraintRelationId, conform->oid); + + /* + * The dependency between the CHECK constraint and its + * relation is DEPENDENCY_AUTO. + */ + recordWholeRowDependencyOrError(tab, subtype, rel, attnum, + &con_obj, DEPENDENCY_AUTO); + } } } } @@ -15929,7 +15981,21 @@ RememberWholeRowDependentForRebuilding(AlteredTableInfo *tab, AlterTableType sub */ if (bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, expr_attrs)) { - RememberIndexForRebuilding(index->indexrelid, tab); + if (subtype == AT_SetExpression) + RememberIndexForRebuilding(index->indexrelid, tab); + else + { + ObjectAddress idx_obj; + + ObjectAddressSet(idx_obj, RelationRelationId, index->indexrelid); + + /* + * The index has a DEPENDENCY_AUTO relationship with its + * relation. + */ + recordWholeRowDependencyOrError(tab, subtype, rel, attnum, + &idx_obj, DEPENDENCY_AUTO); + } continue; } } @@ -15957,13 +16023,172 @@ RememberWholeRowDependentForRebuilding(AlteredTableInfo *tab, AlterTableType sub */ if (bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, expr_attrs)) { - RememberIndexForRebuilding(index->indexrelid, tab); + if (subtype == AT_SetExpression) + RememberIndexForRebuilding(index->indexrelid, tab); + else + { + ObjectAddress idx_obj; + + ObjectAddressSet(idx_obj, RelationRelationId, index->indexrelid); + + /* + * The index has a DEPENDENCY_AUTO relationship with its + * relation + */ + recordWholeRowDependencyOrError(tab, subtype, rel, attnum, + &idx_obj, DEPENDENCY_AUTO); + } } } } systable_endscan(indscan); table_close(pg_index, AccessShareLock); + + /* + * Now checking trigger, policy whole-row references, ALTER COLUMN SET + * EXPRESSION does not do anything about it. + */ + + /* + * For ALTER TABLE SET EXPRESSION: + * + * 1.No need to check trigger with whole-row references. Creation of + * BEFORE triggers with whole-row Vars referencing (some column is + * generated column) is disallowed; see CreateTriggerFiringOn(). + * Additionally, there is no need to worry about AFTER triggers; even if + * the trigger were recreated (which it is not), its WHEN qualification + * would remain unchanged. + * + * 2. No need to recheck policies with whole-row references, since we do + * not recreate and re-evaluate the policy condition when a dependent + * column's generated expression changes. + */ + if (subtype == AT_AlterColumnType || subtype == AT_DropColumn) + { + if (rel->trigdesc != NULL) + { + Node *expr; + + for (int i = 0; i < rel->trigdesc->numtriggers; i++) + { + Bitmapset *expr_attrs = NULL; + Trigger *trig = &rel->trigdesc->triggers[i]; + + if (trig->tgqual == NULL) + continue; + + expr = stringToNode(trig->tgqual); + + pull_varattnos(expr, PRS2_OLD_VARNO, &expr_attrs); + + pull_varattnos(expr, PRS2_NEW_VARNO, &expr_attrs); + + /* + * If the triger WHEN qual contains whole-row reference then + * remember it + */ + if (bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + expr_attrs)) + { + ObjectAddress trigObj; + + ObjectAddressSet(trigObj, TriggerRelationId, trig->tgoid); + + /* + * The dependency between the trigger and its relation is + * DEPENDENCY_NORMAL + */ + recordWholeRowDependencyOrError(tab, subtype, rel, attnum, + &trigObj, DEPENDENCY_NORMAL); + } + } + } + + /* Now checking policy whole-row references */ + reltypid = get_rel_type_id(RelationGetRelid(rel)); + + pg_policy = table_open(PolicyRelationId, AccessShareLock); + + /* + * We cannot simply look up pg_policy->polrelid because a policy's + * USING or CHECK expression may reference other relations. Instead, + * we must scan pg_depend to identify all policies that depend on this + * relation. + */ + pols = GetAllRelAssociatedPolicies(rel); + + foreach_oid(policyoid, pols) + { + Datum exprDatum; + char *exprString; + Node *expr; + ObjectAddress polObj; + + ScanKeyInit(&skey, + Anum_pg_policy_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(policyoid)); + policyscan = systable_beginscan(pg_policy, + PolicyOidIndexId, + true, + NULL, + 1, + &skey); + while (HeapTupleIsValid(policyTuple = systable_getnext(policyscan))) + { + Form_pg_policy policy = (Form_pg_policy) GETSTRUCT(policyTuple); + + exprDatum = heap_getattr(policyTuple, Anum_pg_policy_polqual, + RelationGetDescr(pg_policy), + &isnull); + if (!isnull) + { + exprString = TextDatumGetCString(exprDatum); + expr = (Node *) stringToNode(exprString); + pfree(exprString); + + if (expr_contain_wholerow(expr, reltypid)) + { + ObjectAddressSet(polObj, PolicyRelationId, policy->oid); + + /* + * The dependency between the policy and it's relation + * is DEPENDENCY_NORMAL + */ + recordWholeRowDependencyOrError(tab, subtype, rel, attnum, + &polObj, DEPENDENCY_NORMAL); + + continue; + } + } + + exprDatum = heap_getattr(policyTuple, Anum_pg_policy_polwithcheck, + RelationGetDescr(pg_policy), + &isnull); + if (!isnull) + { + exprString = TextDatumGetCString(exprDatum); + expr = (Node *) stringToNode(exprString); + pfree(exprString); + + if (expr_contain_wholerow(expr, reltypid)) + { + ObjectAddressSet(polObj, PolicyRelationId, policy->oid); + + /* + * The dependency between the policy and it's relation + * is DEPENDENCY_NORMAL + */ + recordWholeRowDependencyOrError(tab, subtype, rel, attnum, + &polObj, DEPENDENCY_NORMAL); + } + } + } + systable_endscan(policyscan); + } + table_close(pg_policy, AccessShareLock); + } } /* @@ -24390,3 +24615,81 @@ ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel, /* Restore the userid and security context. */ SetUserIdAndSecContext(save_userid, save_sec_context); } + +/* + * Record dependencies between objects contains whole-row Var references + * (indexes, CHECK constraints, etc.) and the relation, or report an error. This + * is used for ALTER TABLE DROP COLUMN, ALTER COLUMN SET DATA TYPE only. ALTER + * TABLE SET EXPRESSION relies on the existing Remember...ForRebuilding to + * perform the required work. + */ +static void +recordWholeRowDependencyOrError(AlteredTableInfo *tab, AlterTableType subtype, + Relation rel, AttrNumber attnum, + const ObjectAddress *depender, + DependencyType behavior) +{ + if (subtype == AT_AlterColumnType) + ereport(ERROR, + errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot alter table \"%s\" because %s uses its row type", + RelationGetRelationName(rel), + getObjectDescription(depender, false)), + errhint("You might need to drop %s first", + getObjectDescription(depender, false))); + else + { + ObjectAddress referenced; + + Assert(subtype == AT_DropColumn); + + ObjectAddressSubSet(referenced, RelationRelationId, + RelationGetRelid(rel), attnum); + + recordDependencyOn(depender, &referenced, behavior); + } +} + +/* + * GetAllRelAssociatedPolicies + * + * Returns a list of OIDs of all row-level security policies associated with the + * given relation. + */ +static List * +GetAllRelAssociatedPolicies(Relation rel) +{ + Relation depRel; + ScanKeyData key[3]; + SysScanDesc scan; + HeapTuple depTup; + List *result = NIL; + + depRel = table_open(DependRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(rel))); + ScanKeyInit(&key[2], + Anum_pg_depend_refobjsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum((int32) 0)); + + scan = systable_beginscan(depRel, DependReferenceIndexId, true, + NULL, 3, key); + while (HeapTupleIsValid(depTup = systable_getnext(scan))) + { + Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup); + + if (foundDep->classid == PolicyRelationId) + result = list_append_unique_oid(result, foundDep->objid); + } + systable_endscan(scan); + table_close(depRel, AccessShareLock); + + return result; +} diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 907a255c36f..8fcf04c3057 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -73,6 +73,7 @@ typedef struct static bool pull_varnos_walker(Node *node, pull_varnos_context *context); static bool pull_varattnos_walker(Node *node, pull_varattnos_context *context); +static bool expr_contain_wholerow_walker(Node *node, Oid *context); static bool pull_vars_walker(Node *node, pull_vars_context *context); static bool contain_var_clause_walker(Node *node, void *context); static bool contain_vars_of_level_walker(Node *node, int *sublevels_up); @@ -327,6 +328,51 @@ pull_varattnos_walker(Node *node, pull_varattnos_context *context) return expression_tree_walker(node, pull_varattnos_walker, context); } +static bool +expr_contain_wholerow_walker(Node *node, Oid *context) +{ + if (node == NULL) + return false; + + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + if (var->varattno == InvalidAttrNumber && + var->vartype == *context) + return true; + + return false; + } + + if (IsA(node, Query)) + return query_tree_walker((Query *) node, expr_contain_wholerow_walker, + context, 0); + + return expression_tree_walker(node, expr_contain_wholerow_walker, context); +} + +/* + * expr_contain_wholerow - + * + * Determine whether an expression contains whole-row Var reference, recursing as needed. + * For simple expressions without sublinks, pull_varattnos is usually sufficient + * to detect a whole-row Var. But if the node contains sublinks (unplanned + * subqueries), the check must instead rely on the whole-row type OID. + * + * Use expr_contain_wholerow to check whole-row var existsence when in doubt. + */ +bool +expr_contain_wholerow(Node *node, Oid reltypid) +{ + Assert(OidIsValid(reltypid)); + + return query_or_expression_tree_walker(node, + expr_contain_wholerow_walker, + &reltypid, + 0); +} + /* * pull_vars_of_level diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h index cb6241e2bdd..0491059c7cc 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -206,6 +206,7 @@ extern SortGroupClause *get_sortgroupref_clause_noerr(Index sortref, extern Bitmapset *pull_varnos(PlannerInfo *root, Node *node); extern Bitmapset *pull_varnos_of_level(PlannerInfo *root, Node *node, int levelsup); extern void pull_varattnos(Node *node, Index varno, Bitmapset **varattnos); +extern bool expr_contain_wholerow(Node *node, Oid reltypid); extern List *pull_vars_of_level(Node *node, int levelsup); extern bool contain_var_clause(Node *node); extern bool contain_vars_of_level(Node *node, int levelsup); diff --git a/src/test/regress/expected/constraints.out b/src/test/regress/expected/constraints.out index 83f97f684d5..211583d3769 100644 --- a/src/test/regress/expected/constraints.out +++ b/src/test/regress/expected/constraints.out @@ -254,6 +254,29 @@ ERROR: system column "ctid" reference in check constraint is invalid LINE 3: CHECK (NOT (is_capital AND ctid::text = 'sys_col_check... ^ -- +-- Drop column should also drop all check constraints that contains whole-row references +-- +-- Change column data type should fail since whole-row referenced check constraint still exists +-- +-- No need to worry about partitioned tables, since the whole-row check constraint +-- can not span multi relations +CREATE TABLE wholerow_check_tbl ( + city int, + state int, + CONSTRAINT cc0 CHECK (wholerow_check_tbl is null) NOT ENFORCED, + CONSTRAINT cc1 CHECK (wholerow_check_tbl is not null) NOT ENFORCED); +ALTER TABLE wholerow_check_tbl ALTER COLUMN city SET DATA TYPE INT8; -- error +ERROR: cannot alter table "wholerow_check_tbl" because constraint cc0 on table wholerow_check_tbl uses its row type +HINT: You might need to drop constraint cc0 on table wholerow_check_tbl first +ALTER TABLE wholerow_check_tbl DROP COLUMN city; -- ok +\d wholerow_check_tbl + Table "public.wholerow_check_tbl" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + state | integer | | | + +DROP TABLE wholerow_check_tbl; +-- -- Check inheritance of defaults and constraints -- CREATE TABLE INSERT_CHILD (cx INT default 42, diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out index d8e4cb12c3d..4e2acad9dd9 100644 --- a/src/test/regress/expected/foreign_data.out +++ b/src/test/regress/expected/foreign_data.out @@ -1427,6 +1427,20 @@ DROP TRIGGER trigtest_before_stmt ON foreign_schema.foreign_table_1; DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1; DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1; DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1; +CREATE TRIGGER trigtest_before_stmt BEFORE INSERT OR UPDATE +ON foreign_schema.foreign_table_1 +FOR EACH ROW +WHEN (new IS NOT NULL) +EXECUTE PROCEDURE dummy_trigger(); +ALTER FOREIGN TABLE foreign_schema.foreign_table_1 ALTER COLUMN c7 SET DATA TYPE bigint; -- error +ERROR: cannot alter table "foreign_table_1" because trigger trigtest_before_stmt on foreign table foreign_schema.foreign_table_1 uses its row type +HINT: You might need to drop trigger trigtest_before_stmt on foreign table foreign_schema.foreign_table_1 first +ALTER FOREIGN TABLE foreign_schema.foreign_table_1 DROP COLUMN c7; -- error +ERROR: cannot drop column c7 of foreign table foreign_schema.foreign_table_1 because other objects depend on it +DETAIL: trigger trigtest_before_stmt on foreign table foreign_schema.foreign_table_1 depends on column c7 of foreign table foreign_schema.foreign_table_1 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +ALTER FOREIGN TABLE foreign_schema.foreign_table_1 DROP COLUMN c7 CASCADE; -- ok +NOTICE: drop cascades to trigger trigtest_before_stmt on foreign table foreign_schema.foreign_table_1 DROP FUNCTION dummy_trigger(); -- Table inheritance CREATE TABLE fd_pt1 ( diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out index 4d350fbc658..4c4815344f2 100644 --- a/src/test/regress/expected/indexing.out +++ b/src/test/regress/expected/indexing.out @@ -759,6 +759,38 @@ alter table idxpart2 drop column c; b | integer | | | drop table idxpart, idxpart2; +-- Drop column should also drop all indexes that contains whole-row references +-- Change column data type should fail if whole-row referenced indexes exists +create table idxpart (a int, b int, c int) partition by range (a); +create table idxpart1 partition of idxpart for values from (2000) to (3000); +create index idxpart_idx1 on idxpart1((idxpart1 is not null)); +alter table idxpart alter column b set data type bigint; -- error +ERROR: cannot alter table "idxpart1" because index idxpart_idx1 uses its row type +HINT: You might need to drop index idxpart_idx1 first +drop index idxpart_idx1; +create index idxpart_idx2 on idxpart1(a) where idxpart1 is not null; +alter table idxpart alter column c set data type bigint; -- error +ERROR: cannot alter table "idxpart1" because index idxpart_idx2 uses its row type +HINT: You might need to drop index idxpart_idx2 first +alter table idxpart drop column c; -- ok +\d idxpart + Partitioned table "public.idxpart" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | +Partition key: RANGE (a) +Number of partitions: 1 (Use \d+ to list them.) + +\d idxpart1 + Table "public.idxpart1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | +Partition of: idxpart FOR VALUES FROM (2000) TO (3000) + +drop table idxpart; -- Verify that expression indexes inherit correctly create table idxpart (a int, b int) partition by range (a); create table idxpart1 (like idxpart); diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 3a5e82c35bd..c0f578cb813 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -2731,6 +2731,32 @@ SELECT * FROM document; 14 | 11 | 1 | regress_rls_bob | new novel | (16 rows) +-- check drop column (no CASCADE) or alter column data type will fail because of +-- whole-row referenced security policy exists. +DROP TABLE part_document; +ALTER TABLE document ADD COLUMN dummy INT4; +CREATE POLICY p7 ON document AS PERMISSIVE + USING (cid IS NOT NULL AND + (WITH cte AS (SELECT TRUE FROM uaccount + WHERE EXISTS (SELECT document FROM uaccount WHERE uaccount IS NULL)) + SELECT * FROM cte)); +ALTER TABLE uaccount ALTER COLUMN seclv SET DATA TYPE BIGINT; -- error +ERROR: cannot alter table "uaccount" because policy p7 on table document uses its row type +HINT: You might need to drop policy p7 on table document first +ALTER TABLE document ALTER COLUMN dummy SET DATA TYPE BIGINT; -- error +ERROR: cannot alter table "document" because policy p7 on table document uses its row type +HINT: You might need to drop policy p7 on table document first +ALTER TABLE document DROP COLUMN dummy; -- error +ERROR: cannot drop column dummy of table document because other objects depend on it +DETAIL: policy p7 on table document depends on column dummy of table document +HINT: Use DROP ... CASCADE to drop the dependent objects too. +ALTER TABLE uaccount DROP COLUMN seclv; -- error +ERROR: cannot drop column seclv of table uaccount because other objects depend on it +DETAIL: policy p7 on table document depends on column seclv of table uaccount +HINT: Use DROP ... CASCADE to drop the dependent objects too. +ALTER TABLE uaccount DROP COLUMN seclv CASCADE; -- ok, policy p7 will be dropped +NOTICE: drop cascades to policy p7 on table document +ALTER TABLE document DROP COLUMN dummy CASCADE; -- ok -- -- ROLE/GROUP -- @@ -5199,12 +5225,11 @@ drop table rls_t, test_t; -- RESET SESSION AUTHORIZATION; DROP SCHEMA regress_rls_schema CASCADE; -NOTICE: drop cascades to 30 other objects +NOTICE: drop cascades to 29 other objects DETAIL: drop cascades to function f_leak(text) drop cascades to table uaccount drop cascades to table category drop cascades to table document -drop cascades to table part_document drop cascades to table dependent drop cascades to table rec1 drop cascades to table rec2 diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index 8fcb33ac81a..b9ddb9ca7ba 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -227,6 +227,34 @@ ERROR: trigger "no_such_trigger" for table "main_table" does not exist COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS 'right'; COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS NULL; -- +-- test triggers with WHEN clause contain wholerow reference +-- +CREATE TABLE test_tbl1 (a int, b int) PARTITION BY RANGE (a); +CREATE TABLE test_tbl1p1 PARTITION OF test_tbl1 FOR VALUES FROM (0) TO (1000); +CREATE TRIGGER test_tbl1p1_trig + BEFORE INSERT OR UPDATE ON test_tbl1p1 FOR EACH ROW + WHEN (new = ROW (1, 1)) + EXECUTE PROCEDURE trigger_func ('test_tbl1p1'); +ALTER TABLE test_tbl1 ALTER COLUMN b SET DATA TYPE bigint; -- error +ERROR: cannot alter table "test_tbl1p1" because trigger test_tbl1p1_trig on table test_tbl1p1 uses its row type +HINT: You might need to drop trigger test_tbl1p1_trig on table test_tbl1p1 first +ALTER TABLE test_tbl1 DROP COLUMN b; -- error +ERROR: cannot drop desired object(s) because other objects depend on them +DETAIL: trigger test_tbl1p1_trig on table test_tbl1p1 depends on column b of table test_tbl1p1 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +ALTER TABLE test_tbl1 DROP COLUMN b CASCADE; -- ok +NOTICE: drop cascades to trigger test_tbl1p1_trig on table test_tbl1p1 +\d+ test_tbl1 + Partitioned table "public.test_tbl1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | +Partition key: RANGE (a) +Partitions: + test_tbl1p1 FOR VALUES FROM (0) TO (1000) + +DROP TABLE test_tbl1; +-- -- test triggers with WHEN clause -- CREATE TRIGGER modified_a BEFORE UPDATE OF a ON main_table diff --git a/src/test/regress/sql/constraints.sql b/src/test/regress/sql/constraints.sql index 9705962eb9f..3d25c85b4b1 100644 --- a/src/test/regress/sql/constraints.sql +++ b/src/test/regress/sql/constraints.sql @@ -165,6 +165,23 @@ CREATE TABLE SYS_COL_CHECK_TBL (city text, state text, is_capital bool, altitude int, CHECK (NOT (is_capital AND ctid::text = 'sys_col_check_tbl'))); +-- +-- Drop column should also drop all check constraints that contains whole-row references +-- +-- Change column data type should fail since whole-row referenced check constraint still exists +-- +-- No need to worry about partitioned tables, since the whole-row check constraint +-- can not span multi relations +CREATE TABLE wholerow_check_tbl ( + city int, + state int, + CONSTRAINT cc0 CHECK (wholerow_check_tbl is null) NOT ENFORCED, + CONSTRAINT cc1 CHECK (wholerow_check_tbl is not null) NOT ENFORCED); +ALTER TABLE wholerow_check_tbl ALTER COLUMN city SET DATA TYPE INT8; -- error +ALTER TABLE wholerow_check_tbl DROP COLUMN city; -- ok +\d wholerow_check_tbl +DROP TABLE wholerow_check_tbl; + -- -- Check inheritance of defaults and constraints -- diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql index ed78052b1e2..5f2eb0e3279 100644 --- a/src/test/regress/sql/foreign_data.sql +++ b/src/test/regress/sql/foreign_data.sql @@ -649,6 +649,16 @@ DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1; DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1; DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1; +CREATE TRIGGER trigtest_before_stmt BEFORE INSERT OR UPDATE +ON foreign_schema.foreign_table_1 +FOR EACH ROW +WHEN (new IS NOT NULL) +EXECUTE PROCEDURE dummy_trigger(); + +ALTER FOREIGN TABLE foreign_schema.foreign_table_1 ALTER COLUMN c7 SET DATA TYPE bigint; -- error +ALTER FOREIGN TABLE foreign_schema.foreign_table_1 DROP COLUMN c7; -- error +ALTER FOREIGN TABLE foreign_schema.foreign_table_1 DROP COLUMN c7 CASCADE; -- ok + DROP FUNCTION dummy_trigger(); -- Table inheritance diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql index 561403cc7f7..e15d8a9b541 100644 --- a/src/test/regress/sql/indexing.sql +++ b/src/test/regress/sql/indexing.sql @@ -354,6 +354,23 @@ alter table idxpart2 drop column c; \d idxpart2 drop table idxpart, idxpart2; +-- Drop column should also drop all indexes that contains whole-row references +-- Change column data type should fail if whole-row referenced indexes exists +create table idxpart (a int, b int, c int) partition by range (a); +create table idxpart1 partition of idxpart for values from (2000) to (3000); + +create index idxpart_idx1 on idxpart1((idxpart1 is not null)); +alter table idxpart alter column b set data type bigint; -- error +drop index idxpart_idx1; + +create index idxpart_idx2 on idxpart1(a) where idxpart1 is not null; +alter table idxpart alter column c set data type bigint; -- error +alter table idxpart drop column c; -- ok + +\d idxpart +\d idxpart1 +drop table idxpart; + -- Verify that expression indexes inherit correctly create table idxpart (a int, b int) partition by range (a); create table idxpart1 (like idxpart); diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql index 6b3566271df..4c49cceeb56 100644 --- a/src/test/regress/sql/rowsecurity.sql +++ b/src/test/regress/sql/rowsecurity.sql @@ -1219,6 +1219,23 @@ DROP POLICY p1 ON document; -- Just check everything went per plan SELECT * FROM document; +-- check drop column (no CASCADE) or alter column data type will fail because of +-- whole-row referenced security policy exists. +DROP TABLE part_document; +ALTER TABLE document ADD COLUMN dummy INT4; +CREATE POLICY p7 ON document AS PERMISSIVE + USING (cid IS NOT NULL AND + (WITH cte AS (SELECT TRUE FROM uaccount + WHERE EXISTS (SELECT document FROM uaccount WHERE uaccount IS NULL)) + SELECT * FROM cte)); +ALTER TABLE uaccount ALTER COLUMN seclv SET DATA TYPE BIGINT; -- error +ALTER TABLE document ALTER COLUMN dummy SET DATA TYPE BIGINT; -- error + +ALTER TABLE document DROP COLUMN dummy; -- error +ALTER TABLE uaccount DROP COLUMN seclv; -- error +ALTER TABLE uaccount DROP COLUMN seclv CASCADE; -- ok, policy p7 will be dropped +ALTER TABLE document DROP COLUMN dummy CASCADE; -- ok + -- -- ROLE/GROUP -- diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index 2285e90110e..1f7f5e64c50 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -158,6 +158,23 @@ COMMENT ON TRIGGER no_such_trigger ON main_table IS 'wrong'; COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS 'right'; COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS NULL; +-- +-- test triggers with WHEN clause contain wholerow reference +-- +CREATE TABLE test_tbl1 (a int, b int) PARTITION BY RANGE (a); +CREATE TABLE test_tbl1p1 PARTITION OF test_tbl1 FOR VALUES FROM (0) TO (1000); + +CREATE TRIGGER test_tbl1p1_trig + BEFORE INSERT OR UPDATE ON test_tbl1p1 FOR EACH ROW + WHEN (new = ROW (1, 1)) + EXECUTE PROCEDURE trigger_func ('test_tbl1p1'); + +ALTER TABLE test_tbl1 ALTER COLUMN b SET DATA TYPE bigint; -- error +ALTER TABLE test_tbl1 DROP COLUMN b; -- error +ALTER TABLE test_tbl1 DROP COLUMN b CASCADE; -- ok +\d+ test_tbl1 +DROP TABLE test_tbl1; + -- -- test triggers with WHEN clause -- -- 2.34.1
