summaryrefslogtreecommitdiffstats
path: root/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/tablecmds.c
diff options
context:
space:
mode:
authorvvvv <[email protected]>2025-10-24 14:59:50 +0300
committervvvv <[email protected]>2025-10-24 15:29:24 +0300
commit5b0d18921f2a509d8363c40a5ca208dfed026287 (patch)
treed1369c696d3a9e9a65b68d9208e198269a48cfbc /yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/tablecmds.c
parente7fbdb6e81ae4a296e710b133de7a2a04b31bbc4 (diff)
YQL-20567 upgrade PG up to 16.10 & fix instructions
init commit_hash:81aba13295273281d19d2d332a48ff1c44977447
Diffstat (limited to 'yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/tablecmds.c')
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/tablecmds.c1033
1 files changed, 690 insertions, 343 deletions
diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/tablecmds.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/tablecmds.c
index f84348dc6ca..0b212e623e3 100644
--- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/tablecmds.c
+++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/tablecmds.c
@@ -336,6 +336,14 @@ typedef struct ForeignTruncateInfo
List *rels;
} ForeignTruncateInfo;
+/* Partial or complete FK creation in addFkConstraint() */
+typedef enum addFkConstraintSides
+{
+ addFkReferencedSide,
+ addFkReferencingSide,
+ addFkBothSides,
+} addFkConstraintSides;
+
/*
* Partition tables are expected to be dropped when the parent partitioned
* table gets dropped. Hence for partitioning we use AUTO dependency.
@@ -388,6 +396,7 @@ static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
static void validateForeignKeyConstraint(char *conname,
Relation rel, Relation pkrel,
Oid pkindOid, Oid constraintOid);
+static void CheckAlterTableIsSafe(Relation rel);
static void ATController(AlterTableStmt *parsetree,
Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
AlterTableUtilityContext *context);
@@ -489,16 +498,25 @@ static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *
Relation rel, Constraint *fkconstraint,
bool recurse, bool recursing,
LOCKMODE lockmode);
-static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint,
- Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
- int numfks, int16 *pkattnum, int16 *fkattnum,
- Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
- int numfkdelsetcols, int16 *fkdelsetcols,
- bool old_check_ok,
- Oid parentDelTrigger, Oid parentUpdTrigger);
-static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
- int numfksetcols, const int16 *fksetcolsattnums,
+static int validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
+ int numfksetcols, int16 *fksetcolsattnums,
List *fksetcols);
+static ObjectAddress addFkConstraint(addFkConstraintSides fkside,
+ char *constraintname,
+ Constraint *fkconstraint, Relation rel,
+ Relation pkrel, Oid indexOid,
+ Oid parentConstr,
+ int numfks, int16 *pkattnum, int16 *fkattnum,
+ Oid *pfeqoperators, Oid *ppeqoperators,
+ Oid *ffeqoperators, int numfkdelsetcols,
+ int16 *fkdelsetcols, bool is_internal);
+static void addFkRecurseReferenced(Constraint *fkconstraint,
+ Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
+ int numfks, int16 *pkattnum, int16 *fkattnum,
+ Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
+ int numfkdelsetcols, int16 *fkdelsetcols,
+ bool old_check_ok,
+ Oid parentDelTrigger, Oid parentUpdTrigger);
static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
int numfks, int16 *pkattnum, int16 *fkattnum,
@@ -3324,8 +3342,15 @@ findAttrByName(const char *attributeName, List *schema)
* SetRelationHasSubclass
* Set the value of the relation's relhassubclass field in pg_class.
*
- * NOTE: caller must be holding an appropriate lock on the relation.
- * ShareUpdateExclusiveLock is sufficient.
+ * It's always safe to set this field to true, because all SQL commands are
+ * ready to see true and then find no children. On the other hand, commands
+ * generally assume zero children if this is false.
+ *
+ * Caller must hold any self-exclusive lock until end of transaction. If the
+ * new value is false, caller must have acquired that lock before reading the
+ * evidence that justified the false value. That way, it properly waits if
+ * another backend is simultaneously concluding no need to change the tuple
+ * (new and old values are true).
*
* NOTE: an important side-effect of this operation is that an SI invalidation
* message is sent out to all backends --- including me --- causing plans
@@ -3340,6 +3365,11 @@ SetRelationHasSubclass(Oid relationId, bool relhassubclass)
HeapTuple tuple;
Form_pg_class classtuple;
+ Assert(CheckRelationOidLockedByMe(relationId,
+ ShareUpdateExclusiveLock, false) ||
+ CheckRelationOidLockedByMe(relationId,
+ ShareRowExclusiveLock, true));
+
/*
* Fetch a modifiable copy of the tuple, modify it, update pg_class.
*/
@@ -3438,6 +3468,7 @@ SetRelationTableSpace(Relation rel,
{
Relation pg_class;
HeapTuple tuple;
+ ItemPointerData otid;
Form_pg_class rd_rel;
Oid reloid = RelationGetRelid(rel);
@@ -3446,9 +3477,10 @@ SetRelationTableSpace(Relation rel,
/* Get a modifiable copy of the relation's pg_class row. */
pg_class = table_open(RelationRelationId, RowExclusiveLock);
- tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
+ tuple = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(reloid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", reloid);
+ otid = tuple->t_self;
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
/* Update the pg_class row. */
@@ -3456,7 +3488,8 @@ SetRelationTableSpace(Relation rel,
InvalidOid : newTableSpaceId;
if (RelFileNumberIsValid(newRelFilenumber))
rd_rel->relfilenode = newRelFilenumber;
- CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+ CatalogTupleUpdate(pg_class, &otid, tuple);
+ UnlockTuple(pg_class, &otid, InplaceUpdateTupleLock);
/*
* Record dependency on tablespace. This is only required for relations
@@ -3950,6 +3983,7 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
{
Relation targetrelation;
Relation relrelation; /* for RELATION relation */
+ ItemPointerData otid;
HeapTuple reltup;
Form_pg_class relform;
Oid namespaceId;
@@ -3972,9 +4006,10 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
*/
relrelation = table_open(RelationRelationId, RowExclusiveLock);
- reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
+ reltup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(myrelid));
if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
elog(ERROR, "cache lookup failed for relation %u", myrelid);
+ otid = reltup->t_self;
relform = (Form_pg_class) GETSTRUCT(reltup);
if (get_relname_relid(newrelname, namespaceId) != InvalidOid)
@@ -3999,7 +4034,8 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
*/
namestrcpy(&(relform->relname), newrelname);
- CatalogTupleUpdate(relrelation, &reltup->t_self, reltup);
+ CatalogTupleUpdate(relrelation, &otid, reltup);
+ UnlockTuple(relrelation, &otid, InplaceUpdateTupleLock);
InvokeObjectPostAlterHookArg(RelationRelationId, myrelid, 0,
InvalidOid, is_internal);
@@ -4112,6 +4148,37 @@ CheckTableNotInUse(Relation rel, const char *stmt)
}
/*
+ * CheckAlterTableIsSafe
+ * Verify that it's safe to allow ALTER TABLE on this relation.
+ *
+ * This consists of CheckTableNotInUse() plus a check that the relation
+ * isn't another session's temp table. We must split out the temp-table
+ * check because there are callers of CheckTableNotInUse() that don't want
+ * that, notably DROP TABLE. (We must allow DROP or we couldn't clean out
+ * an orphaned temp schema.) Compare truncate_check_activity().
+ */
+static void
+CheckAlterTableIsSafe(Relation rel)
+{
+ /*
+ * Don't allow ALTER on temp tables of other backends. Their local buffer
+ * manager is not going to cope if we need to change the table's contents.
+ * Even if we don't, there may be optimizations that assume temp tables
+ * aren't subject to such interference.
+ */
+ if (RELATION_IS_OTHER_TEMP(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter temporary tables of other sessions")));
+
+ /*
+ * Also check for active uses of the relation in the current transaction,
+ * including open scans and pending AFTER trigger events.
+ */
+ CheckTableNotInUse(rel, "ALTER TABLE");
+}
+
+/*
* AlterTableLookupRelation
* Look up, and lock, the OID for the relation named by an alter table
* statement.
@@ -4184,7 +4251,7 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
- CheckTableNotInUse(rel, "ALTER TABLE");
+ CheckAlterTableIsSafe(rel);
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
}
@@ -5541,7 +5608,9 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
/*
* Don't allow rewrite on temp tables of other backends ... their
- * local buffer manager is not going to cope.
+ * local buffer manager is not going to cope. (This is redundant
+ * with the check in CheckAlterTableIsSafe, but for safety we'll
+ * check here too.)
*/
if (RELATION_IS_OTHER_TEMP(OldHeap))
ereport(ERROR,
@@ -6405,7 +6474,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
continue;
/* find_all_inheritors already got lock */
childrel = relation_open(childrelid, NoLock);
- CheckTableNotInUse(childrel, "ALTER TABLE");
+ CheckAlterTableIsSafe(childrel);
ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
relation_close(childrel, NoLock);
}
@@ -6414,7 +6483,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
/*
* Obtain list of partitions of the given table, locking them all at the given
- * lockmode and ensuring that they all pass CheckTableNotInUse.
+ * lockmode and ensuring that they all pass CheckAlterTableIsSafe.
*
* This function is a no-op if the given relation is not a partitioned table;
* in particular, nothing is done if it's a legacy inheritance parent.
@@ -6435,7 +6504,7 @@ ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
/* find_all_inheritors already got lock */
childrel = table_open(lfirst_oid(cell), NoLock);
- CheckTableNotInUse(childrel, "ALTER TABLE");
+ CheckAlterTableIsSafe(childrel);
table_close(childrel, NoLock);
}
list_free(inh);
@@ -6468,7 +6537,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
Relation childrel;
childrel = relation_open(childrelid, lockmode);
- CheckTableNotInUse(childrel, "ALTER TABLE");
+ CheckAlterTableIsSafe(childrel);
ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context);
relation_close(childrel, NoLock);
}
@@ -7009,14 +7078,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
rawEnt->attnum = attribute.attnum;
rawEnt->raw_default = copyObject(colDef->raw_default);
-
- /*
- * Attempt to skip a complete table rewrite by storing the specified
- * DEFAULT value outside of the heap. This may be disabled inside
- * AddRelationNewConstraints if the optimization cannot be applied.
- */
- rawEnt->missingMode = (!colDef->generated);
-
+ rawEnt->missingMode = false; /* XXX vestigial */
rawEnt->generated = colDef->generated;
/*
@@ -7028,13 +7090,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* Make the additional catalog changes visible */
CommandCounterIncrement();
-
- /*
- * Did the request for a missing value work? If not we'll have to do a
- * rewrite
- */
- if (!rawEnt->missingMode)
- tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
}
/*
@@ -7051,9 +7106,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
* rejects nulls. If there are any domain constraints then we construct
* an explicit NULL default value that will be passed through
* CoerceToDomain processing. (This is a tad inefficient, since it causes
- * rewriting the table which we really don't have to do, but the present
- * design of domain processing doesn't offer any simple way of checking
- * the constraints more directly.)
+ * rewriting the table which we really wouldn't have to do; but we do it
+ * to preserve the historical behavior that such a failure will be raised
+ * only if the table currently contains some rows.)
*
* Note: we use build_column_default, and not just the cooked default
* returned by AddRelationNewConstraints, so that the right thing happens
@@ -7072,6 +7127,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
*/
if (RELKIND_HAS_STORAGE(relkind) && attribute.attnum > 0)
{
+ bool has_domain_constraints;
+ bool has_missing = false;
+
/*
* For an identity column, we can't use build_column_default(),
* because the sequence ownership isn't set yet. So do it manually.
@@ -7084,14 +7142,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
nve->typeId = typeOid;
defval = (Expr *) nve;
-
- /* must do a rewrite for identity columns */
- tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
}
else
defval = (Expr *) build_column_default(rel, attribute.attnum);
- if (!defval && DomainHasConstraints(typeOid))
+ /* Build CoerceToDomain(NULL) expression if needed */
+ has_domain_constraints = DomainHasConstraints(typeOid);
+ if (!defval && has_domain_constraints)
{
Oid baseTypeId;
int32 baseTypeMod;
@@ -7117,18 +7174,63 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
{
NewColumnValue *newval;
+ /* Prepare defval for execution, either here or in Phase 3 */
+ defval = expression_planner(defval);
+
+ /* Add the new default to the newvals list */
newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
newval->attnum = attribute.attnum;
- newval->expr = expression_planner(defval);
+ newval->expr = defval;
newval->is_generated = (colDef->generated != '\0');
tab->newvals = lappend(tab->newvals, newval);
- }
- if (DomainHasConstraints(typeOid))
- tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
+ /*
+ * Attempt to skip a complete table rewrite by storing the
+ * specified DEFAULT value outside of the heap. This is only
+ * allowed for plain relations and non-generated columns, and the
+ * default expression can't be volatile (stable is OK). Note that
+ * contain_volatile_functions deems CoerceToDomain immutable, but
+ * here we consider that coercion to a domain with constraints is
+ * volatile; else it might fail even when the table is empty.
+ */
+ if (rel->rd_rel->relkind == RELKIND_RELATION &&
+ !colDef->generated &&
+ !has_domain_constraints &&
+ !contain_volatile_functions((Node *) defval))
+ {
+ EState *estate;
+ ExprState *exprState;
+ Datum missingval;
+ bool missingIsNull;
+
+ /* Evaluate the default expression */
+ estate = CreateExecutorState();
+ exprState = ExecPrepareExpr(defval, estate);
+ missingval = ExecEvalExpr(exprState,
+ GetPerTupleExprContext(estate),
+ &missingIsNull);
+ /* If it turns out NULL, nothing to do; else store it */
+ if (!missingIsNull)
+ {
+ StoreAttrMissingVal(rel, attribute.attnum, missingval);
+ /* Make the additional catalog change visible */
+ CommandCounterIncrement();
+ has_missing = true;
+ }
+ FreeExecutorState(estate);
+ }
+ else
+ {
+ /*
+ * Failed to use missing mode. We have to do a table rewrite
+ * to install the value.
+ */
+ tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
+ }
+ }
- if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing)
+ if (!has_missing)
{
/*
* If the new column is NOT NULL, and there is no missing value,
@@ -7180,7 +7282,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* find_inheritance_children already got lock */
childrel = table_open(childrelid, NoLock);
- CheckTableNotInUse(childrel, "ALTER TABLE");
+ CheckAlterTableIsSafe(childrel);
/* Find or create work queue entry for this table */
childtab = ATGetQueueEntry(wqueue, childrel);
@@ -8612,7 +8714,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
/* find_inheritance_children already got lock */
childrel = table_open(childrelid, NoLock);
- CheckTableNotInUse(childrel, "ALTER TABLE");
+ CheckAlterTableIsSafe(childrel);
tuple = SearchSysCacheCopyAttName(childrelid, colName);
if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
@@ -9095,7 +9197,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* find_inheritance_children already got lock */
childrel = table_open(childrelid, NoLock);
- CheckTableNotInUse(childrel, "ALTER TABLE");
+ CheckAlterTableIsSafe(childrel);
/* Find or create work queue entry for this table */
childtab = ATGetQueueEntry(wqueue, childrel);
@@ -9238,9 +9340,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
fkconstraint->fk_del_set_cols,
fkdelsetcols, NULL);
- validateFkOnDeleteSetColumns(numfks, fkattnum,
- numfkdelsetcols, fkdelsetcols,
- fkconstraint->fk_del_set_cols);
+ numfkdelsetcols = validateFkOnDeleteSetColumns(numfks, fkattnum,
+ numfkdelsetcols,
+ fkdelsetcols,
+ fkconstraint->fk_del_set_cols);
/*
* If the attribute list for the referenced table was omitted, lookup the
@@ -9503,25 +9606,37 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
ffeqoperators[i] = ffeqop;
}
- /*
- * Create all the constraint and trigger objects, recursing to partitions
- * as necessary. First handle the referenced side.
- */
- address = addFkRecurseReferenced(wqueue, fkconstraint, rel, pkrel,
- indexOid,
- InvalidOid, /* no parent constraint */
- numfks,
- pkattnum,
- fkattnum,
- pfeqoperators,
- ppeqoperators,
- ffeqoperators,
- numfkdelsetcols,
- fkdelsetcols,
- old_check_ok,
- InvalidOid, InvalidOid);
-
- /* Now handle the referencing side. */
+ /* First, create the constraint catalog entry itself. */
+ address = addFkConstraint(addFkBothSides,
+ fkconstraint->conname, fkconstraint, rel, pkrel,
+ indexOid,
+ InvalidOid, /* no parent constraint */
+ numfks,
+ pkattnum,
+ fkattnum,
+ pfeqoperators,
+ ppeqoperators,
+ ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
+ false);
+
+ /* Next process the action triggers at the referenced side and recurse */
+ addFkRecurseReferenced(fkconstraint, rel, pkrel,
+ indexOid,
+ address.objectId,
+ numfks,
+ pkattnum,
+ fkattnum,
+ pfeqoperators,
+ ppeqoperators,
+ ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
+ old_check_ok,
+ InvalidOid, InvalidOid);
+
+ /* Lastly create the check triggers at the referencing side and recurse */
addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
indexOid,
address.objectId,
@@ -9549,17 +9664,23 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
* validateFkOnDeleteSetColumns
* Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
* column lists are valid.
+ *
+ * If there are duplicates in the fksetcolsattnums[] array, this silently
+ * removes the dups. The new count of numfksetcols is returned.
*/
-void
+static int
validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
- int numfksetcols, const int16 *fksetcolsattnums,
+ int numfksetcols, int16 *fksetcolsattnums,
List *fksetcols)
{
+ int numcolsout = 0;
+
for (int i = 0; i < numfksetcols; i++)
{
int16 setcol_attnum = fksetcolsattnums[i];
bool seen = false;
+ /* Make sure it's in fkattnums[] */
for (int j = 0; j < numfks; j++)
{
if (fkattnums[j] == setcol_attnum)
@@ -9577,50 +9698,59 @@ validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
}
+
+ /* Now check for dups */
+ seen = false;
+ for (int j = 0; j < numcolsout; j++)
+ {
+ if (fksetcolsattnums[j] == setcol_attnum)
+ {
+ seen = true;
+ break;
+ }
+ }
+ if (!seen)
+ fksetcolsattnums[numcolsout++] = setcol_attnum;
}
+ return numcolsout;
}
/*
- * addFkRecurseReferenced
- * subroutine for ATAddForeignKeyConstraint; recurses on the referenced
- * side of the constraint
+ * addFkConstraint
+ * Install pg_constraint entries to implement a foreign key constraint.
+ * Caller must separately invoke addFkRecurseReferenced and
+ * addFkRecurseReferencing, as appropriate, to install pg_trigger entries
+ * and (for partitioned tables) recurse to partitions.
*
- * Create pg_constraint rows for the referenced side of the constraint,
- * referencing the parent of the referencing side; also create action triggers
- * on leaf partitions. If the table is partitioned, recurse to handle each
- * partition.
- *
- * wqueue is the ALTER TABLE work queue; can be NULL when not running as part
- * of an ALTER TABLE sequence.
- * fkconstraint is the constraint being added.
- * rel is the root referencing relation.
- * pkrel is the referenced relation; might be a partition, if recursing.
- * indexOid is the OID of the index (on pkrel) implementing this constraint.
- * parentConstr is the OID of a parent constraint; InvalidOid if this is a
- * top-level constraint.
- * numfks is the number of columns in the foreign key
- * pkattnum is the attnum array of referenced attributes.
- * fkattnum is the attnum array of referencing attributes.
- * numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DEFAULT
+ * fkside: the side of the FK (or both) to create. Caller should
+ * call addFkRecurseReferenced if this is addFkReferencedSide,
+ * addFkRecurseReferencing if it's addFkReferencingSide, or both if it's
+ * addFkBothSides.
+ * constraintname: the base name for the constraint being added,
+ * copied to fkconstraint->conname if the latter is not set
+ * fkconstraint: the constraint being added
+ * rel: the root referencing relation
+ * pkrel: the referenced relation; might be a partition, if recursing
+ * indexOid: the OID of the index (on pkrel) implementing this constraint
+ * parentConstr: the OID of a parent constraint; InvalidOid if this is a
+ * top-level constraint
+ * numfks: the number of columns in the foreign key
+ * pkattnum: the attnum array of referenced attributes
+ * fkattnum: the attnum array of referencing attributes
+ * pf/pp/ffeqoperators: OID array of operators between columns
+ * numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
* (...) clause
- * fkdelsetcols is the attnum array of the columns in the ON DELETE SET
+ * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
* NULL/DEFAULT clause
- * pf/pp/ffeqoperators are OID array of operators between columns.
- * old_check_ok signals that this constraint replaces an existing one that
- * was already validated (thus this one doesn't need validation).
- * parentDelTrigger and parentUpdTrigger, when being recursively called on
- * a partition, are the OIDs of the parent action triggers for DELETE and
- * UPDATE respectively.
*/
static ObjectAddress
-addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
- Relation pkrel, Oid indexOid, Oid parentConstr,
- int numfks,
- int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
- Oid *ppeqoperators, Oid *ffeqoperators,
- int numfkdelsetcols, int16 *fkdelsetcols,
- bool old_check_ok,
- Oid parentDelTrigger, Oid parentUpdTrigger)
+addFkConstraint(addFkConstraintSides fkside,
+ char *constraintname, Constraint *fkconstraint,
+ Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
+ int numfks, int16 *pkattnum,
+ int16 *fkattnum, Oid *pfeqoperators, Oid *ppeqoperators,
+ Oid *ffeqoperators, int numfkdelsetcols, int16 *fkdelsetcols,
+ bool is_internal)
{
ObjectAddress address;
Oid constrOid;
@@ -9628,8 +9758,6 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
bool conislocal;
int coninhcount;
bool connoinherit;
- Oid deleteTriggerOid,
- updateTriggerOid;
/*
* Verify relkind for each referenced partition. At the top level, this
@@ -9648,13 +9776,16 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
*/
if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
RelationGetRelid(rel),
- fkconstraint->conname))
+ constraintname))
conname = ChooseConstraintName(RelationGetRelationName(rel),
ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs),
"fkey",
RelationGetNamespace(rel), NIL);
else
- conname = fkconstraint->conname;
+ conname = constraintname;
+
+ if (fkconstraint->conname == NULL)
+ fkconstraint->conname = pstrdup(conname);
if (OidIsValid(parentConstr))
{
@@ -9706,33 +9837,107 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
conislocal, /* islocal */
coninhcount, /* inhcount */
connoinherit, /* conNoInherit */
- false); /* is_internal */
+ is_internal); /* is_internal */
ObjectAddressSet(address, ConstraintRelationId, constrOid);
/*
- * Mark the child constraint as part of the parent constraint; it must not
- * be dropped on its own. (This constraint is deleted when the partition
- * is detached, but a special check needs to occur that the partition
- * contains no referenced values.)
+ * In partitioning cases, create the dependency entries for this
+ * constraint. (For non-partitioned cases, relevant entries were created
+ * by CreateConstraintEntry.)
+ *
+ * On the referenced side, we need the constraint to have an internal
+ * dependency on its parent constraint; this means that this constraint
+ * cannot be dropped on its own -- only through the parent constraint. It
+ * also means the containing partition cannot be dropped on its own, but
+ * it can be detached, at which point this dependency is removed (after
+ * verifying that no rows are referenced via this FK.)
+ *
+ * When processing the referencing side, we link the constraint via the
+ * special partitioning dependencies: the parent constraint is the primary
+ * dependent, and the partition on which the foreign key exists is the
+ * secondary dependency. That way, this constraint is dropped if either
+ * of these objects is.
+ *
+ * Note that this is only necessary for the subsidiary pg_constraint rows
+ * in partitions; the topmost row doesn't need any of this.
*/
if (OidIsValid(parentConstr))
{
ObjectAddress referenced;
ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
- recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);
+
+ Assert(fkside != addFkBothSides);
+ if (fkside == addFkReferencedSide)
+ recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);
+ else
+ {
+ recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
+ ObjectAddressSet(referenced, RelationRelationId, RelationGetRelid(rel));
+ recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
+ }
}
/* make new constraint visible, in case we add more */
CommandCounterIncrement();
+ return address;
+}
+
+/*
+ * addFkRecurseReferenced
+ * Recursive helper for the referenced side of foreign key creation,
+ * which creates the action triggers and recurses
+ *
+ * If the referenced relation is a plain relation, create the necessary action
+ * triggers that implement the constraint. If the referenced relation is a
+ * partitioned table, then we create a pg_constraint row referencing the parent
+ * of the referencing side for it and recurse on this routine for each
+ * partition.
+ *
+ * fkconstraint: the constraint being added
+ * rel: the root referencing relation
+ * pkrel: the referenced relation; might be a partition, if recursing
+ * indexOid: the OID of the index (on pkrel) implementing this constraint
+ * parentConstr: the OID of a parent constraint; InvalidOid if this is a
+ * top-level constraint
+ * numfks: the number of columns in the foreign key
+ * pkattnum: the attnum array of referenced attributes
+ * fkattnum: the attnum array of referencing attributes
+ * numfkdelsetcols: the number of columns in the ON DELETE SET
+ * NULL/DEFAULT (...) clause
+ * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
+ * NULL/DEFAULT clause
+ * pf/pp/ffeqoperators: OID array of operators between columns
+ * old_check_ok: true if this constraint replaces an existing one that
+ * was already validated (thus this one doesn't need validation)
+ * parentDelTrigger and parentUpdTrigger: when recursively called on a
+ * partition, the OIDs of the parent action triggers for DELETE and
+ * UPDATE respectively.
+ */
+static void
+addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
+ Relation pkrel, Oid indexOid, Oid parentConstr,
+ int numfks,
+ int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
+ Oid *ppeqoperators, Oid *ffeqoperators,
+ int numfkdelsetcols, int16 *fkdelsetcols,
+ bool old_check_ok,
+ Oid parentDelTrigger, Oid parentUpdTrigger)
+{
+ Oid deleteTriggerOid,
+ updateTriggerOid;
+
+ Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
+ Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
+
/*
* Create the action triggers that enforce the constraint.
*/
createForeignKeyActionTriggers(rel, RelationGetRelid(pkrel),
fkconstraint,
- constrOid, indexOid,
+ parentConstr, indexOid,
parentDelTrigger, parentUpdTrigger,
&deleteTriggerOid, &updateTriggerOid);
@@ -9751,7 +9956,9 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
AttrMap *map;
AttrNumber *mapped_pkattnum;
Oid partIndexId;
+ ObjectAddress address;
+ /* XXX would it be better to acquire these locks beforehand? */
partRel = table_open(pd->oids[i], ShareRowExclusiveLock);
/*
@@ -9770,13 +9977,23 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
else
mapped_pkattnum = pkattnum;
- /* do the deed */
+ /* Determine the index to use at this level */
partIndexId = index_get_partition(partRel, indexOid);
if (!OidIsValid(partIndexId))
elog(ERROR, "index for %u not found in partition %s",
indexOid, RelationGetRelationName(partRel));
- addFkRecurseReferenced(wqueue, fkconstraint, rel, partRel,
- partIndexId, constrOid, numfks,
+
+ /* Create entry at this level ... */
+ address = addFkConstraint(addFkReferencedSide,
+ fkconstraint->conname, fkconstraint, rel,
+ partRel, partIndexId, parentConstr,
+ numfks, mapped_pkattnum,
+ fkattnum, pfeqoperators, ppeqoperators,
+ ffeqoperators, numfkdelsetcols,
+ fkdelsetcols, true);
+ /* ... and recurse to our children */
+ addFkRecurseReferenced(fkconstraint, rel, partRel,
+ partIndexId, address.objectId, numfks,
mapped_pkattnum, fkattnum,
pfeqoperators, ppeqoperators, ffeqoperators,
numfkdelsetcols, fkdelsetcols,
@@ -9792,13 +10009,12 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
}
}
}
-
- return address;
}
/*
* addFkRecurseReferencing
- * subroutine for ATAddForeignKeyConstraint and CloneFkReferencing
+ * Recursive helper for the referencing side of foreign key creation,
+ * which creates the check triggers and recurses
*
* If the referencing relation is a plain relation, create the necessary check
* triggers that implement the constraint, and set up for Phase 3 constraint
@@ -9810,27 +10026,27 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
* deletions. If it's a partitioned relation, every partition must be so
* locked.
*
- * wqueue is the ALTER TABLE work queue; can be NULL when not running as part
- * of an ALTER TABLE sequence.
- * fkconstraint is the constraint being added.
- * rel is the referencing relation; might be a partition, if recursing.
- * pkrel is the root referenced relation.
- * indexOid is the OID of the index (on pkrel) implementing this constraint.
- * parentConstr is the OID of the parent constraint (there is always one).
- * numfks is the number of columns in the foreign key
- * pkattnum is the attnum array of referenced attributes.
- * fkattnum is the attnum array of referencing attributes.
- * pf/pp/ffeqoperators are OID array of operators between columns.
- * numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DEFAULT
+ * wqueue: the ALTER TABLE work queue; NULL when not running as part
+ * of an ALTER TABLE sequence.
+ * fkconstraint: the constraint being added
+ * rel: the referencing relation; might be a partition, if recursing
+ * pkrel: the root referenced relation
+ * indexOid: the OID of the index (on pkrel) implementing this constraint
+ * parentConstr: the OID of the parent constraint (there is always one)
+ * numfks: the number of columns in the foreign key
+ * pkattnum: the attnum array of referenced attributes
+ * fkattnum: the attnum array of referencing attributes
+ * pf/pp/ffeqoperators: OID array of operators between columns
+ * numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
* (...) clause
- * fkdelsetcols is the attnum array of the columns in the ON DELETE SET
+ * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
* NULL/DEFAULT clause
- * old_check_ok signals that this constraint replaces an existing one that
- * was already validated (thus this one doesn't need validation).
- * lockmode is the lockmode to acquire on partitions when recursing.
- * parentInsTrigger and parentUpdTrigger, when being recursively called on
- * a partition, are the OIDs of the parent check triggers for INSERT and
- * UPDATE respectively.
+ * old_check_ok: true if this constraint replaces an existing one that
+ * was already validated (thus this one doesn't need validation)
+ * lockmode: the lockmode to acquire on partitions when recursing
+ * parentInsTrigger and parentUpdTrigger: when being recursively called on
+ * a partition, the OIDs of the parent check triggers for INSERT and
+ * UPDATE respectively.
*/
static void
addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
@@ -9845,6 +10061,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
updateTriggerOid;
Assert(OidIsValid(parentConstr));
+ Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
+ Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -9918,13 +10136,10 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
AttrMap *attmap;
AttrNumber mapped_fkattnum[INDEX_MAX_KEYS];
bool attached;
- char *conname;
- Oid constrOid;
- ObjectAddress address,
- referenced;
+ ObjectAddress address;
ListCell *cell;
- CheckTableNotInUse(partition, "ALTER TABLE");
+ CheckAlterTableIsSafe(partition);
attmap = build_attrmap_by_name(RelationGetDescr(partition),
RelationGetDescr(rel),
@@ -9964,65 +10179,18 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
/*
* No luck finding a good constraint to reuse; create our own.
*/
- if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
- RelationGetRelid(partition),
- fkconstraint->conname))
- conname = ChooseConstraintName(RelationGetRelationName(partition),
- ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs),
- "fkey",
- RelationGetNamespace(partition), NIL);
- else
- conname = fkconstraint->conname;
- constrOid =
- CreateConstraintEntry(conname,
- RelationGetNamespace(partition),
- CONSTRAINT_FOREIGN,
- fkconstraint->deferrable,
- fkconstraint->initdeferred,
- fkconstraint->initially_valid,
- parentConstr,
- partitionId,
- mapped_fkattnum,
- numfks,
- numfks,
- InvalidOid,
- indexOid,
- RelationGetRelid(pkrel),
- pkattnum,
- pfeqoperators,
- ppeqoperators,
- ffeqoperators,
- numfks,
- fkconstraint->fk_upd_action,
- fkconstraint->fk_del_action,
- fkdelsetcols,
- numfkdelsetcols,
- fkconstraint->fk_matchtype,
- NULL,
- NULL,
- NULL,
- false,
- 1,
- false,
- false);
-
- /*
- * Give this constraint partition-type dependencies on the parent
- * constraint as well as the table.
- */
- ObjectAddressSet(address, ConstraintRelationId, constrOid);
- ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
- recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
- ObjectAddressSet(referenced, RelationRelationId, partitionId);
- recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
-
- /* Make all this visible before recursing */
- CommandCounterIncrement();
+ address = addFkConstraint(addFkReferencingSide,
+ fkconstraint->conname, fkconstraint,
+ partition, pkrel, indexOid, parentConstr,
+ numfks, pkattnum,
+ mapped_fkattnum, pfeqoperators,
+ ppeqoperators, ffeqoperators,
+ numfkdelsetcols, fkdelsetcols, true);
/* call ourselves to finalize the creation and we're done */
addFkRecurseReferencing(wqueue, fkconstraint, partition, pkrel,
indexOid,
- constrOid,
+ address.objectId,
numfks,
pkattnum,
mapped_fkattnum,
@@ -10064,14 +10232,14 @@ CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
Assert(parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
/*
- * Clone constraints for which the parent is on the referenced side.
+ * First, clone constraints where the parent is on the referencing side.
*/
- CloneFkReferenced(parentRel, partitionRel);
+ CloneFkReferencing(wqueue, parentRel, partitionRel);
/*
- * Now clone constraints where the parent is on the referencing side.
+ * Clone constraints for which the parent is on the referenced side.
*/
- CloneFkReferencing(wqueue, parentRel, partitionRel);
+ CloneFkReferenced(parentRel, partitionRel);
}
/*
@@ -10082,8 +10250,6 @@ CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
* clone those constraints to the given partition. This is to be called
* when the partition is being created or attached.
*
- * This ignores self-referencing FKs; those are handled by CloneFkReferencing.
- *
* This recurses to partitions, if the relation being attached is partitioned.
* Recursion is done by calling addFkRecurseReferenced.
*/
@@ -10155,6 +10321,7 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
int numfkdelsetcols;
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
Constraint *fkconstraint;
+ ObjectAddress address;
Oid deleteTriggerOid,
updateTriggerOid;
@@ -10173,24 +10340,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
continue;
}
- /*
- * Don't clone self-referencing foreign keys, which can be in the
- * partitioned table or in the partition-to-be.
- */
- if (constrForm->conrelid == RelationGetRelid(parentRel) ||
- constrForm->conrelid == RelationGetRelid(partitionRel))
- {
- ReleaseSysCache(tuple);
- continue;
- }
-
- /*
- * Because we're only expanding the key space at the referenced side,
- * we don't need to prevent any operation in the referencing table, so
- * AccessShareLock suffices (assumes that dropping the constraint
- * acquires AEL).
- */
- fkRel = table_open(constrForm->conrelid, AccessShareLock);
+ /* We need the same lock level that CreateTrigger will acquire */
+ fkRel = table_open(constrForm->conrelid, ShareRowExclusiveLock);
indexOid = constrForm->conindid;
DeconstructFkConstraintRow(tuple,
@@ -10254,12 +10405,19 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
constrForm->confrelid, constrForm->conrelid,
&deleteTriggerOid, &updateTriggerOid);
- addFkRecurseReferenced(NULL,
- fkconstraint,
+ /* Add this constraint ... */
+ address = addFkConstraint(addFkReferencedSide,
+ fkconstraint->conname, fkconstraint, fkRel,
+ partitionRel, partIndexId, constrOid,
+ numfks, mapped_confkey,
+ conkey, conpfeqop, conppeqop, conffeqop,
+ numfkdelsetcols, confdelsetcols, false);
+ /* ... and recurse */
+ addFkRecurseReferenced(fkconstraint,
fkRel,
partitionRel,
partIndexId,
- constrOid,
+ address.objectId,
numfks,
mapped_confkey,
conkey,
@@ -10289,8 +10447,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
* child.
*
* If wqueue is given, it is used to set up phase-3 verification for each
- * cloned constraint; if omitted, we assume that such verification is not
- * needed (example: the partition is being created anew).
+ * cloned constraint; omit it if such verification is not needed
+ * (example: the partition is being created anew).
*/
static void
CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
@@ -10306,6 +10464,23 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
{
ForeignKeyCacheInfo *fk = lfirst(cell);
+ /*
+ * Refuse to attach a table as partition that this partitioned table
+ * already has a foreign key to. This isn't useful schema, which is
+ * proven by the fact that there have been no user complaints that
+ * it's already impossible to achieve this in the opposite direction,
+ * i.e., creating a foreign key that references a partition. This
+ * restriction allows us to dodge some complexities around
+ * pg_constraint and pg_trigger row creations that would be needed
+ * during ATTACH/DETACH for this kind of relationship.
+ */
+ if (fk->confrelid == RelationGetRelid(partRel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"",
+ RelationGetRelationName(partRel),
+ get_constraint_name(fk->conoid))));
+
clone = lappend_oid(clone, fk->conoid);
}
@@ -10357,9 +10532,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
Constraint *fkconstraint;
bool attached;
Oid indexOid;
- Oid constrOid;
- ObjectAddress address,
- referenced;
+ ObjectAddress address;
ListCell *lc;
Oid insertTriggerOid,
updateTriggerOid;
@@ -10456,7 +10629,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
fkconstraint->old_conpfeqop = NIL;
fkconstraint->old_pktable_oid = InvalidOid;
fkconstraint->skip_validation = false;
- fkconstraint->initially_valid = true;
+ fkconstraint->initially_valid = constrForm->convalidated;
for (int i = 0; i < numfks; i++)
{
Form_pg_attribute att;
@@ -10466,71 +10639,29 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
makeString(NameStr(att->attname)));
}
- if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
- RelationGetRelid(partRel),
- NameStr(constrForm->conname)))
- fkconstraint->conname =
- ChooseConstraintName(RelationGetRelationName(partRel),
- ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs),
- "fkey",
- RelationGetNamespace(partRel), NIL);
- else
- fkconstraint->conname = pstrdup(NameStr(constrForm->conname));
indexOid = constrForm->conindid;
- constrOid =
- CreateConstraintEntry(fkconstraint->conname,
- constrForm->connamespace,
- CONSTRAINT_FOREIGN,
- fkconstraint->deferrable,
- fkconstraint->initdeferred,
- constrForm->convalidated,
- parentConstrOid,
- RelationGetRelid(partRel),
- mapped_conkey,
- numfks,
- numfks,
- InvalidOid, /* not a domain constraint */
- indexOid,
- constrForm->confrelid, /* same foreign rel */
- confkey,
- conpfeqop,
- conppeqop,
- conffeqop,
- numfks,
- fkconstraint->fk_upd_action,
- fkconstraint->fk_del_action,
- confdelsetcols,
- numfkdelsetcols,
- fkconstraint->fk_matchtype,
- NULL,
- NULL,
- NULL,
- false, /* islocal */
- 1, /* inhcount */
- false, /* conNoInherit */
- true);
-
- /* Set up partition dependencies for the new constraint */
- ObjectAddressSet(address, ConstraintRelationId, constrOid);
- ObjectAddressSet(referenced, ConstraintRelationId, parentConstrOid);
- recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
- ObjectAddressSet(referenced, RelationRelationId,
- RelationGetRelid(partRel));
- recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
+
+ /* Create the pg_constraint entry at this level */
+ address = addFkConstraint(addFkReferencingSide,
+ NameStr(constrForm->conname), fkconstraint,
+ partRel, pkrel, indexOid, parentConstrOid,
+ numfks, confkey,
+ mapped_conkey, conpfeqop,
+ conppeqop, conffeqop,
+ numfkdelsetcols, confdelsetcols,
+ false);
/* Done with the cloned constraint's tuple */
ReleaseSysCache(tuple);
- /* Make all this visible before recursing */
- CommandCounterIncrement();
-
+ /* Create the check triggers, and recurse to partitions, if any */
addFkRecurseReferencing(wqueue,
fkconstraint,
partRel,
pkrel,
indexOid,
- constrOid,
+ address.objectId,
numfks,
confkey,
mapped_conkey,
@@ -10694,6 +10825,81 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
partRelid);
+ /*
+ * If the referenced table is partitioned, then the partition we're
+ * attaching now has extra pg_constraint rows and action triggers that are
+ * no longer needed. Remove those.
+ */
+ if (get_rel_relkind(fk->confrelid) == RELKIND_PARTITIONED_TABLE)
+ {
+ Relation pg_constraint = table_open(ConstraintRelationId, RowShareLock);
+ ObjectAddresses *objs;
+ HeapTuple consttup;
+
+ ScanKeyInit(&key,
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(fk->conrelid));
+
+ scan = systable_beginscan(pg_constraint,
+ ConstraintRelidTypidNameIndexId,
+ true, NULL, 1, &key);
+ objs = new_object_addresses();
+ while ((consttup = systable_getnext(scan)) != NULL)
+ {
+ Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(consttup);
+
+ if (conform->conparentid != fk->conoid)
+ continue;
+ else
+ {
+ ObjectAddress addr;
+ SysScanDesc scan2;
+ ScanKeyData key2;
+ int n PG_USED_FOR_ASSERTS_ONLY;
+
+ ObjectAddressSet(addr, ConstraintRelationId, conform->oid);
+ add_exact_object_address(&addr, objs);
+
+ /*
+ * First we must delete the dependency record that binds the
+ * constraint records together.
+ */
+ n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
+ conform->oid,
+ DEPENDENCY_INTERNAL,
+ ConstraintRelationId,
+ fk->conoid);
+ Assert(n == 1); /* actually only one is expected */
+
+ /*
+ * Now search for the triggers for this constraint and set
+ * them up for deletion too
+ */
+ ScanKeyInit(&key2,
+ Anum_pg_trigger_tgconstraint,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(conform->oid));
+ scan2 = systable_beginscan(trigrel, TriggerConstraintIndexId,
+ true, NULL, 1, &key2);
+ while ((trigtup = systable_getnext(scan2)) != NULL)
+ {
+ ObjectAddressSet(addr, TriggerRelationId,
+ ((Form_pg_trigger) GETSTRUCT(trigtup))->oid);
+ add_exact_object_address(&addr, objs);
+ }
+ systable_endscan(scan2);
+ }
+ }
+ /* make the dependency deletions visible */
+ CommandCounterIncrement();
+ performMultipleDeletions(objs, DROP_RESTRICT,
+ PERFORM_DELETION_INTERNAL);
+ systable_endscan(scan);
+
+ table_close(pg_constraint, RowShareLock);
+ }
+
CommandCounterIncrement();
return true;
}
@@ -12045,7 +12251,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/* Must match lock taken by RemoveTriggerById: */
frel = table_open(con->confrelid, AccessExclusiveLock);
- CheckTableNotInUse(frel, "ALTER TABLE");
+ CheckAlterTableIsSafe(frel);
table_close(frel, NoLock);
}
@@ -12123,7 +12329,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/* find_inheritance_children already got lock */
childrel = table_open(childrelid, NoLock);
- CheckTableNotInUse(childrel, "ALTER TABLE");
+ CheckAlterTableIsSafe(childrel);
ScanKeyInit(&skey[0],
Anum_pg_constraint_conrelid,
@@ -12271,6 +12477,16 @@ ATPrepAlterColumnType(List **wqueue,
colName)));
/*
+ * Cannot specify USING when altering type of a generated column, because
+ * that would violate the generation expression.
+ */
+ if (attTup->attgenerated && def->cooked_default)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+ errmsg("cannot specify USING when altering type of generated column"),
+ errdetail("Column \"%s\" is a generated column.", colName)));
+
+ /*
* Don't alter inherited columns. At outer level, there had better not be
* any inherited definition; when recursing, we assume this was checked at
* the parent level (see below).
@@ -12346,11 +12562,12 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" cannot be cast automatically to type %s",
colName, format_type_be(targettype)),
+ !attTup->attgenerated ?
/* translator: USING is SQL, don't translate it */
errhint("You might need to specify \"USING %s::%s\".",
quote_identifier(colName),
format_type_with_typemod(targettype,
- targettypmod))));
+ targettypmod)) : 0));
}
/* Fix collations after all else */
@@ -12426,7 +12643,7 @@ ATPrepAlterColumnType(List **wqueue,
/* find_all_inheritors already got lock */
childrel = relation_open(childrelid, NoLock);
- CheckTableNotInUse(childrel, "ALTER TABLE");
+ CheckAlterTableIsSafe(childrel);
/*
* Verify that the child doesn't have any inherited definitions of
@@ -13272,9 +13489,12 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
/*
* Re-parse the index and constraint definitions, and attach them to the
* appropriate work queue entries. We do this before dropping because in
- * the case of a FOREIGN KEY constraint, we might not yet have exclusive
- * lock on the table the constraint is attached to, and we need to get
- * that before reparsing/dropping.
+ * the case of a constraint on another table, we might not yet have
+ * exclusive lock on the table the constraint is attached to, and we need
+ * to get that before reparsing/dropping. (That's possible at least for
+ * FOREIGN KEY, CHECK, and EXCLUSION constraints; in non-FK cases it
+ * requires a dependency on the target table's composite type in the other
+ * table's constraint expressions.)
*
* We can't rely on the output of deparsing to tell us which relation to
* operate on, because concurrent activity might have made the name
@@ -13290,7 +13510,6 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
Form_pg_constraint con;
Oid relid;
Oid confrelid;
- char contype;
bool conislocal;
tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
@@ -13307,7 +13526,6 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
elog(ERROR, "could not identify relation associated with constraint %u", oldId);
}
confrelid = con->confrelid;
- contype = con->contype;
conislocal = con->conislocal;
ReleaseSysCache(tup);
@@ -13324,12 +13542,12 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
continue;
/*
- * When rebuilding an FK constraint that references the table we're
- * modifying, we might not yet have any lock on the FK's table, so get
- * one now. We'll need AccessExclusiveLock for the DROP CONSTRAINT
- * step, so there's no value in asking for anything weaker.
+ * When rebuilding another table's constraint that references the
+ * table we're modifying, we might not yet have any lock on the other
+ * table, so get one now. We'll need AccessExclusiveLock for the DROP
+ * CONSTRAINT step, so there's no value in asking for anything weaker.
*/
- if (relid != tab->relid && contype == CONSTRAINT_FOREIGN)
+ if (relid != tab->relid)
LockRelationOid(relid, AccessExclusiveLock);
ATPostAlterTypeParse(oldId, relid, confrelid,
@@ -13343,6 +13561,14 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
Oid relid;
relid = IndexGetRelation(oldId, false);
+
+ /*
+ * As above, make sure we have lock on the index's table if it's not
+ * the same table.
+ */
+ if (relid != tab->relid)
+ LockRelationOid(relid, AccessExclusiveLock);
+
ATPostAlterTypeParse(oldId, relid, InvalidOid,
(char *) lfirst(def_item),
wqueue, lockmode, tab->rewrite);
@@ -13359,6 +13585,20 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
Oid relid;
relid = StatisticsGetRelation(oldId, false);
+
+ /*
+ * As above, make sure we have lock on the statistics object's table
+ * if it's not the same table. However, we take
+ * ShareUpdateExclusiveLock here, aligning with the lock level used in
+ * CreateStatistics and RemoveStatisticsById.
+ *
+ * CAUTION: this should be done after all cases that grab
+ * AccessExclusiveLock, else we risk causing deadlock due to needing
+ * to promote our table lock.
+ */
+ if (relid != tab->relid)
+ LockRelationOid(relid, ShareUpdateExclusiveLock);
+
ATPostAlterTypeParse(oldId, relid, InvalidOid,
(char *) lfirst(def_item),
wqueue, lockmode, tab->rewrite);
@@ -14358,7 +14598,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
/* Fetch heap tuple */
relid = RelationGetRelid(rel);
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ tuple = SearchSysCacheLocked1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
@@ -14462,6 +14702,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
repl_val, repl_null, repl_repl);
CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);
+ UnlockTuple(pgclass, &tuple->t_self, InplaceUpdateTupleLock);
InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
@@ -16618,7 +16859,8 @@ AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
ObjectAddress thisobj;
bool already_done = false;
- classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid));
+ /* no rel lock for relkind=c so use LOCKTAG_TUPLE */
+ classTup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(classTup))
elog(ERROR, "cache lookup failed for relation %u", relOid);
classForm = (Form_pg_class) GETSTRUCT(classTup);
@@ -16637,6 +16879,8 @@ AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
already_done = object_address_present(&thisobj, objsMoved);
if (!already_done && oldNspOid != newNspOid)
{
+ ItemPointerData otid = classTup->t_self;
+
/* check for duplicate name (more friendly than unique-index failure) */
if (get_relname_relid(NameStr(classForm->relname),
newNspOid) != InvalidOid)
@@ -16649,7 +16893,9 @@ AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
/* classTup is a copy, so OK to scribble on */
classForm->relnamespace = newNspOid;
- CatalogTupleUpdate(classRel, &classTup->t_self, classTup);
+ CatalogTupleUpdate(classRel, &otid, classTup);
+ UnlockTuple(classRel, &otid, InplaceUpdateTupleLock);
+
/* Update dependency on schema if caller said so */
if (hasDependEntry &&
@@ -16661,6 +16907,8 @@ AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
elog(ERROR, "failed to change schema dependency for relation \"%s\"",
NameStr(classForm->relname));
}
+ else
+ UnlockTuple(classRel, &classTup->t_self, InplaceUpdateTupleLock);
if (!already_done)
{
add_exact_object_address(&thisobj, objsMoved);
@@ -18564,9 +18812,17 @@ ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
tab->rel = rel;
}
+ /*
+ * Detaching the partition might involve TOAST table access, so ensure we
+ * have a valid snapshot.
+ */
+ PushActiveSnapshot(GetTransactionSnapshot());
+
/* Do the final part of detaching */
DetachPartitionFinalize(rel, partRel, concurrent, defaultPartOid);
+ PopActiveSnapshot();
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
/* keep our lock until commit */
@@ -18595,6 +18851,7 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
HeapTuple tuple,
newtuple;
Relation trigrel = NULL;
+ List *fkoids = NIL;
if (concurrent)
{
@@ -18615,12 +18872,32 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
fks = copyObject(RelationGetFKeyList(partRel));
if (fks != NIL)
trigrel = table_open(TriggerRelationId, RowExclusiveLock);
+
+ /*
+ * It's possible that the partition being detached has a foreign key that
+ * references a partitioned table. When that happens, there are multiple
+ * pg_constraint rows for the partition: one points to the partitioned
+ * table itself, while the others point to each of its partitions. Only
+ * the topmost one is to be considered here; the child constraints must be
+ * left alone, because conceptually those aren't coming from our parent
+ * partitioned table, but from this partition itself.
+ *
+ * We implement this by collecting all the constraint OIDs in a first scan
+ * of the FK array, and skipping in the loop below those constraints whose
+ * parents are listed here.
+ */
+ foreach(cell, fks)
+ {
+ ForeignKeyCacheInfo *fk = (ForeignKeyCacheInfo *) lfirst(cell);
+
+ fkoids = lappend_oid(fkoids, fk->conoid);
+ }
+
foreach(cell, fks)
{
ForeignKeyCacheInfo *fk = lfirst(cell);
HeapTuple contup;
Form_pg_constraint conform;
- Constraint *fkconstraint;
Oid insertTriggerOid,
updateTriggerOid;
@@ -18629,15 +18906,22 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
conform = (Form_pg_constraint) GETSTRUCT(contup);
- /* consider only the inherited foreign keys */
+ /*
+ * Consider only inherited foreign keys, and only if their parents
+ * aren't in the list.
+ */
if (conform->contype != CONSTRAINT_FOREIGN ||
- !OidIsValid(conform->conparentid))
+ !OidIsValid(conform->conparentid) ||
+ list_member_oid(fkoids, conform->conparentid))
{
ReleaseSysCache(contup);
continue;
}
- /* unset conparentid and adjust conislocal, coninhcount, etc. */
+ /*
+ * The constraint on this table must be marked no longer a child of
+ * the parent's constraint, as do its check triggers.
+ */
ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
/*
@@ -18655,33 +18939,87 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
RelationGetRelid(partRel));
/*
- * Make the action triggers on the referenced relation. When this was
- * a partition the action triggers pointed to the parent rel (they
- * still do), but now we need separate ones of our own.
+ * Lastly, create the action triggers on the referenced table, using
+ * addFkRecurseReferenced, which requires some elaborate setup (so put
+ * it in a separate block). While at it, if the table is partitioned,
+ * that function will recurse to create the pg_constraint rows and
+ * action triggers for each partition.
+ *
+ * Note there's no need to do addFkConstraint() here, because the
+ * pg_constraint row already exists.
*/
- fkconstraint = makeNode(Constraint);
- fkconstraint->contype = CONSTRAINT_FOREIGN;
- fkconstraint->conname = pstrdup(NameStr(conform->conname));
- fkconstraint->deferrable = conform->condeferrable;
- fkconstraint->initdeferred = conform->condeferred;
- fkconstraint->location = -1;
- fkconstraint->pktable = NULL;
- fkconstraint->fk_attrs = NIL;
- fkconstraint->pk_attrs = NIL;
- fkconstraint->fk_matchtype = conform->confmatchtype;
- fkconstraint->fk_upd_action = conform->confupdtype;
- fkconstraint->fk_del_action = conform->confdeltype;
- fkconstraint->fk_del_set_cols = NIL;
- fkconstraint->old_conpfeqop = NIL;
- fkconstraint->old_pktable_oid = InvalidOid;
- fkconstraint->skip_validation = false;
- fkconstraint->initially_valid = true;
+ {
+ Constraint *fkconstraint;
+ int numfks;
+ AttrNumber conkey[INDEX_MAX_KEYS];
+ AttrNumber confkey[INDEX_MAX_KEYS];
+ Oid conpfeqop[INDEX_MAX_KEYS];
+ Oid conppeqop[INDEX_MAX_KEYS];
+ Oid conffeqop[INDEX_MAX_KEYS];
+ int numfkdelsetcols;
+ AttrNumber confdelsetcols[INDEX_MAX_KEYS];
+ Relation refdRel;
+
+ DeconstructFkConstraintRow(contup,
+ &numfks,
+ conkey,
+ confkey,
+ conpfeqop,
+ conppeqop,
+ conffeqop,
+ &numfkdelsetcols,
+ confdelsetcols);
+
+ /* Create a synthetic node we'll use throughout */
+ fkconstraint = makeNode(Constraint);
+ fkconstraint->contype = CONSTRAINT_FOREIGN;
+ fkconstraint->conname = pstrdup(NameStr(conform->conname));
+ fkconstraint->deferrable = conform->condeferrable;
+ fkconstraint->initdeferred = conform->condeferred;
+ fkconstraint->skip_validation = true;
+ fkconstraint->initially_valid = true;
+ /* a few irrelevant fields omitted here */
+ fkconstraint->pktable = NULL;
+ fkconstraint->fk_attrs = NIL;
+ fkconstraint->pk_attrs = NIL;
+ fkconstraint->fk_matchtype = conform->confmatchtype;
+ fkconstraint->fk_upd_action = conform->confupdtype;
+ fkconstraint->fk_del_action = conform->confdeltype;
+ fkconstraint->fk_del_set_cols = NIL;
+ fkconstraint->old_conpfeqop = NIL;
+ fkconstraint->old_pktable_oid = InvalidOid;
+ fkconstraint->location = -1;
+
+ /* set up colnames, used to generate the constraint name */
+ for (int i = 0; i < numfks; i++)
+ {
+ Form_pg_attribute att;
+
+ att = TupleDescAttr(RelationGetDescr(partRel),
+ conkey[i] - 1);
+
+ fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
+ makeString(NameStr(att->attname)));
+ }
+
+ refdRel = table_open(fk->confrelid, ShareRowExclusiveLock);
- createForeignKeyActionTriggers(partRel, conform->confrelid,
- fkconstraint, fk->conoid,
- conform->conindid,
- InvalidOid, InvalidOid,
- NULL, NULL);
+ addFkRecurseReferenced(fkconstraint, partRel,
+ refdRel,
+ conform->conindid,
+ fk->conoid,
+ numfks,
+ confkey,
+ conkey,
+ conpfeqop,
+ conppeqop,
+ conffeqop,
+ numfkdelsetcols,
+ confdelsetcols,
+ true,
+ InvalidOid, InvalidOid);
+ table_close(refdRel, NoLock); /* keep lock till end of xact */
+ }
ReleaseSysCache(contup);
}
@@ -18715,22 +19053,31 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
foreach(cell, indexes)
{
Oid idxid = lfirst_oid(cell);
+ Oid parentidx;
Relation idx;
Oid constrOid;
+ Oid parentConstrOid;
if (!has_superclass(idxid))
continue;
- Assert((IndexGetRelation(get_partition_parent(idxid, false), false) ==
- RelationGetRelid(rel)));
+ parentidx = get_partition_parent(idxid, false);
+ Assert((IndexGetRelation(parentidx, false) == RelationGetRelid(rel)));
idx = index_open(idxid, AccessExclusiveLock);
IndexSetParentIndex(idx, InvalidOid);
- /* If there's a constraint associated with the index, detach it too */
+ /*
+ * If there's a constraint associated with the index, detach it too.
+ * Careful: it is possible for a constraint index in a partition to be
+ * the child of a non-constraint index, so verify whether the parent
+ * index does actually have a constraint.
+ */
constrOid = get_relation_idx_constraint_oid(RelationGetRelid(partRel),
idxid);
- if (OidIsValid(constrOid))
+ parentConstrOid = get_relation_idx_constraint_oid(RelationGetRelid(rel),
+ parentidx);
+ if (OidIsValid(parentConstrOid) && OidIsValid(constrOid))
ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
index_close(idx, NoLock);