diff options
| author | vvvv <[email protected]> | 2025-10-24 14:59:50 +0300 |
|---|---|---|
| committer | vvvv <[email protected]> | 2025-10-24 15:29:24 +0300 |
| commit | 5b0d18921f2a509d8363c40a5ca208dfed026287 (patch) | |
| tree | d1369c696d3a9e9a65b68d9208e198269a48cfbc /yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands | |
| parent | e7fbdb6e81ae4a296e710b133de7a2a04b31bbc4 (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')
16 files changed, 1166 insertions, 453 deletions
diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/alter.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/alter.c index cbe02853b04..85f11a1ec61 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/alter.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/alter.c @@ -62,6 +62,7 @@ #include "parser/parse_func.h" #include "replication/logicalworker.h" #include "rewrite/rewriteDefine.h" +#include "storage/lmgr.h" #include "tcop/utility.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -975,7 +976,9 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId) Oid old_ownerId; Oid namespaceId = InvalidOid; - oldtup = get_catalog_object_by_oid(rel, Anum_oid, objectId); + /* Search tuple and lock it. */ + oldtup = + get_catalog_object_by_oid_extended(rel, Anum_oid, objectId, true); if (oldtup == NULL) elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"", objectId, RelationGetRelationName(rel)); @@ -1074,6 +1077,8 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId) /* Perform actual update */ CatalogTupleUpdate(rel, &newtup->t_self, newtup); + UnlockTuple(rel, &oldtup->t_self, InplaceUpdateTupleLock); + /* * Update owner dependency reference. When working on a large object, * we have to translate back to the OID conventionally used for LOs' @@ -1091,6 +1096,8 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId) } else { + UnlockTuple(rel, &oldtup->t_self, InplaceUpdateTupleLock); + /* * No need to change anything. But when working on a large object, we * have to translate back to the OID conventionally used for LOs' diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/analyze.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/analyze.c index 54fcbd1aa16..1b8fb838afb 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/analyze.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/analyze.c @@ -636,7 +636,11 @@ do_analyze_rel(Relation onerel, VacuumParams *params, visibilitymap_count(onerel, &relallvisible, NULL); - /* Update pg_class for table relation */ + /* + * Update pg_class for table relation. CCI first, in case acquirefunc + * updated pg_class. + */ + CommandCounterIncrement(); vac_update_relstats(onerel, relpages, totalrows, @@ -671,6 +675,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params, * Partitioned tables don't have storage, so we don't set any fields * in their pg_class entries except for reltuples and relhasindex. */ + CommandCounterIncrement(); vac_update_relstats(onerel, -1, totalrows, 0, hasindex, InvalidTransactionId, InvalidMultiXactId, diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/async.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/async.c index 6ce892fb0ed..6ed633c7b7e 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/async.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/async.c @@ -2236,6 +2236,8 @@ asyncQueueAdvanceTail(void) static void ProcessIncomingNotify(bool flush) { + MemoryContext oldcontext; + /* We *must* reset the flag */ notifyInterruptPending = false; @@ -2250,14 +2252,21 @@ ProcessIncomingNotify(bool flush) /* * We must run asyncQueueReadAllNotifications inside a transaction, else - * bad things happen if it gets an error. + * bad things happen if it gets an error. However, we need to preserve + * the caller's memory context (typically MessageContext). */ + oldcontext = CurrentMemoryContext; + StartTransactionCommand(); asyncQueueReadAllNotifications(); CommitTransactionCommand(); + /* Caller's context had better not have been transaction-local */ + Assert(MemoryContextIsValid(oldcontext)); + MemoryContextSwitchTo(oldcontext); + /* * If this isn't an end-of-command case, we must flush the notify messages * to ensure frontend gets them promptly. diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/copyto.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/copyto.c index 9e4b2437a57..4e1cb2597f0 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/copyto.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/copyto.c @@ -482,7 +482,7 @@ BeginCopyTo(ParseState *pstate, if (q->querySource == QSRC_NON_INSTEAD_RULE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("DO ALSO rules are not supported for the COPY"))); + errmsg("DO ALSO rules are not supported for COPY"))); } ereport(ERROR, @@ -499,7 +499,11 @@ BeginCopyTo(ParseState *pstate, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("COPY (SELECT INTO) is not supported"))); - Assert(query->utilityStmt == NULL); + /* The only other utility command we could see is NOTIFY */ + if (query->utilityStmt != NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("COPY query must not be a utility command"))); /* * Similarly the grammar doesn't enforce the presence of a RETURNING diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/dbcommands.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/dbcommands.c index 7295800a7d8..a100d5b37e0 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/dbcommands.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/dbcommands.c @@ -1586,6 +1586,8 @@ dropdb(const char *dbname, bool missing_ok, bool force) bool db_istemplate; Relation pgdbrel; HeapTuple tup; + ScanKeyData scankey; + void *inplace_state; Form_pg_database datform; int notherbackends; int npreparedxacts; @@ -1723,11 +1725,6 @@ dropdb(const char *dbname, bool missing_ok, bool force) */ pgstat_drop_database(db_id); - tup = SearchSysCacheCopy1(DATABASEOID, ObjectIdGetDatum(db_id)); - if (!HeapTupleIsValid(tup)) - elog(ERROR, "cache lookup failed for database %u", db_id); - datform = (Form_pg_database) GETSTRUCT(tup); - /* * Except for the deletion of the catalog row, subsequent actions are not * transactional (consider DropDatabaseBuffers() discarding modified @@ -1739,8 +1736,17 @@ dropdb(const char *dbname, bool missing_ok, bool force) * modification is durable before performing irreversible filesystem * operations. */ + ScanKeyInit(&scankey, + Anum_pg_database_datname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(dbname)); + systable_inplace_update_begin(pgdbrel, DatabaseNameIndexId, true, + NULL, 1, &scankey, &tup, &inplace_state); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for database %u", db_id); + datform = (Form_pg_database) GETSTRUCT(tup); datform->datconnlimit = DATCONNLIMIT_INVALID_DB; - heap_inplace_update(pgdbrel, tup); + systable_inplace_update_finish(inplace_state, tup); XLogFlush(XactLastRecEnd); /* @@ -1748,6 +1754,7 @@ dropdb(const char *dbname, bool missing_ok, bool force) * the row will be gone, but if we fail, dropdb() can be invoked again. */ CatalogTupleDelete(pgdbrel, &tup->t_self); + heap_freetuple(tup); /* * Drop db-specific replication slots. @@ -1806,6 +1813,7 @@ RenameDatabase(const char *oldname, const char *newname) { Oid db_id; HeapTuple newtup; + ItemPointerData otid; Relation rel; int notherbackends; int npreparedxacts; @@ -1877,11 +1885,13 @@ RenameDatabase(const char *oldname, const char *newname) errdetail_busy_db(notherbackends, npreparedxacts))); /* rename */ - newtup = SearchSysCacheCopy1(DATABASEOID, ObjectIdGetDatum(db_id)); + newtup = SearchSysCacheLockedCopy1(DATABASEOID, ObjectIdGetDatum(db_id)); if (!HeapTupleIsValid(newtup)) elog(ERROR, "cache lookup failed for database %u", db_id); + otid = newtup->t_self; namestrcpy(&(((Form_pg_database) GETSTRUCT(newtup))->datname), newname); - CatalogTupleUpdate(rel, &newtup->t_self, newtup); + CatalogTupleUpdate(rel, &otid, newtup); + UnlockTuple(rel, &otid, InplaceUpdateTupleLock); InvokeObjectPostAlterHook(DatabaseRelationId, db_id, 0); @@ -2130,6 +2140,7 @@ movedb(const char *dbname, const char *tblspcname) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", dbname))); + LockTuple(pgdbrel, &oldtuple->t_self, InplaceUpdateTupleLock); new_record[Anum_pg_database_dattablespace - 1] = ObjectIdGetDatum(dst_tblspcoid); new_record_repl[Anum_pg_database_dattablespace - 1] = true; @@ -2138,6 +2149,7 @@ movedb(const char *dbname, const char *tblspcname) new_record, new_record_nulls, new_record_repl); CatalogTupleUpdate(pgdbrel, &oldtuple->t_self, newtuple); + UnlockTuple(pgdbrel, &oldtuple->t_self, InplaceUpdateTupleLock); InvokeObjectPostAlterHook(DatabaseRelationId, db_id, 0); @@ -2368,6 +2380,7 @@ AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", stmt->dbname))); + LockTuple(rel, &tuple->t_self, InplaceUpdateTupleLock); datform = (Form_pg_database) GETSTRUCT(tuple); dboid = datform->oid; @@ -2417,6 +2430,7 @@ AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel) newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), new_record, new_record_nulls, new_record_repl); CatalogTupleUpdate(rel, &tuple->t_self, newtuple); + UnlockTuple(rel, &tuple->t_self, InplaceUpdateTupleLock); InvokeObjectPostAlterHook(DatabaseRelationId, dboid, 0); @@ -2466,6 +2480,7 @@ AlterDatabaseRefreshColl(AlterDatabaseRefreshCollStmt *stmt) if (!object_ownercheck(DatabaseRelationId, db_id, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE, stmt->dbname); + LockTuple(rel, &tuple->t_self, InplaceUpdateTupleLock); datum = heap_getattr(tuple, Anum_pg_database_datcollversion, RelationGetDescr(rel), &isnull); oldversion = isnull ? NULL : TextDatumGetCString(datum); @@ -2483,6 +2498,7 @@ AlterDatabaseRefreshColl(AlterDatabaseRefreshCollStmt *stmt) bool nulls[Natts_pg_database] = {0}; bool replaces[Natts_pg_database] = {0}; Datum values[Natts_pg_database] = {0}; + HeapTuple newtuple; ereport(NOTICE, (errmsg("changing version from %s to %s", @@ -2491,14 +2507,15 @@ AlterDatabaseRefreshColl(AlterDatabaseRefreshCollStmt *stmt) values[Anum_pg_database_datcollversion - 1] = CStringGetTextDatum(newversion); replaces[Anum_pg_database_datcollversion - 1] = true; - tuple = heap_modify_tuple(tuple, RelationGetDescr(rel), - values, nulls, replaces); - CatalogTupleUpdate(rel, &tuple->t_self, tuple); - heap_freetuple(tuple); + newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), + values, nulls, replaces); + CatalogTupleUpdate(rel, &tuple->t_self, newtuple); + heap_freetuple(newtuple); } else ereport(NOTICE, (errmsg("version has not changed"))); + UnlockTuple(rel, &tuple->t_self, InplaceUpdateTupleLock); InvokeObjectPostAlterHook(DatabaseRelationId, db_id, 0); @@ -2610,6 +2627,8 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId) (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to change owner of database"))); + LockTuple(rel, &tuple->t_self, InplaceUpdateTupleLock); + repl_repl[Anum_pg_database_datdba - 1] = true; repl_val[Anum_pg_database_datdba - 1] = ObjectIdGetDatum(newOwnerId); @@ -2631,6 +2650,7 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId) newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl); CatalogTupleUpdate(rel, &newtuple->t_self, newtuple); + UnlockTuple(rel, &tuple->t_self, InplaceUpdateTupleLock); heap_freetuple(newtuple); diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/foreigncmds.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/foreigncmds.c index 0ecff545a9e..6baf80cf09c 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/foreigncmds.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/foreigncmds.c @@ -71,15 +71,26 @@ optionListToArray(List *options) foreach(cell, options) { DefElem *def = lfirst(cell); + const char *name; const char *value; Size len; text *t; + name = def->defname; value = defGetString(def); - len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value); + + /* Insist that name not contain "=", else "a=b=c" is ambiguous */ + if (strchr(name, '=') != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid option name \"%s\": must not contain \"=\"", + name))); + + len = VARHDRSZ + strlen(name) + 1 + strlen(value); + /* +1 leaves room for sprintf's trailing null */ t = palloc(len + 1); SET_VARSIZE(t, len); - sprintf(VARDATA(t), "%s=%s", def->defname, value); + sprintf(VARDATA(t), "%s=%s", name, value); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/functioncmds.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/functioncmds.c index 49c7864c7cf..f63b5ef420b 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/functioncmds.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/functioncmds.c @@ -56,6 +56,7 @@ #include "executor/functions.h" #include "funcapi.h" #include "miscadmin.h" +#include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" #include "parser/analyze.h" #include "parser/parse_coerce.h" @@ -2366,5 +2367,32 @@ CallStmtResultDesc(CallStmt *stmt) ReleaseSysCache(tuple); + /* + * The result of build_function_result_tupdesc_t has the right column + * names, but it just has the declared output argument types, which is the + * wrong thing in polymorphic cases. Get the correct types by examining + * stmt->outargs. We intentionally keep the atttypmod as -1 and the + * attcollation as the type's default, since that's always the appropriate + * thing for function outputs; there's no point in considering any + * additional info available from outargs. Note that tupdesc is null if + * there are no outargs. + */ + if (tupdesc) + { + Assert(tupdesc->natts == list_length(stmt->outargs)); + for (int i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + Node *outarg = (Node *) list_nth(stmt->outargs, i); + + TupleDescInitEntry(tupdesc, + i + 1, + NameStr(att->attname), + exprType(outarg), + -1, + 0); + } + } + return tupdesc; } diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/indexcmds.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/indexcmds.c index e4c792228fc..80da56d9743 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/indexcmds.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/indexcmds.c @@ -26,6 +26,7 @@ #include "catalog/index.h" #include "catalog/indexing.h" #include "catalog/pg_am.h" +#include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_database.h" #include "catalog/pg_inherits.h" @@ -352,10 +353,12 @@ static bool CompareOpclassOptions(Datum *opts1, Datum *opts2, int natts) { int i; + FmgrInfo fm; if (!opts1 && !opts2) return true; + fmgr_info(F_ARRAY_EQ, &fm); for (i = 0; i < natts; i++) { Datum opt1 = opts1 ? opts1[i] : (Datum) 0; @@ -371,8 +374,12 @@ CompareOpclassOptions(Datum *opts1, Datum *opts2, int natts) else if (opt2 == (Datum) 0) return false; - /* Compare non-NULL text[] datums. */ - if (!DatumGetBool(DirectFunctionCall2(array_eq, opt1, opt2))) + /* + * Compare non-NULL text[] datums. Use C collation to enforce binary + * equivalence of texts, because we don't know anything about the + * semantics of opclass options. + */ + if (!DatumGetBool(FunctionCall2Coll(&fm, C_COLLATION_OID, opt1, opt2))) return false; } @@ -2455,7 +2462,9 @@ makeObjectName(const char *name1, const char *name2, const char *label) * constraint names.) * * Note: it is theoretically possible to get a collision anyway, if someone - * else chooses the same name concurrently. This is fairly unlikely to be + * else chooses the same name concurrently. We shorten the race condition + * window by checking for conflicting relations using SnapshotDirty, but + * that doesn't close the window entirely. This is fairly unlikely to be * a problem in practice, especially if one is holding an exclusive lock on * the relation identified by name1. However, if choosing multiple names * within a single command, you'd better create the new object and do @@ -2471,15 +2480,45 @@ ChooseRelationName(const char *name1, const char *name2, int pass = 0; char *relname = NULL; char modlabel[NAMEDATALEN]; + SnapshotData SnapshotDirty; + Relation pgclassrel; + + /* prepare to search pg_class with a dirty snapshot */ + InitDirtySnapshot(SnapshotDirty); + pgclassrel = table_open(RelationRelationId, AccessShareLock); /* try the unmodified label first */ strlcpy(modlabel, label, sizeof(modlabel)); for (;;) { + ScanKeyData key[2]; + SysScanDesc scan; + bool collides; + relname = makeObjectName(name1, name2, modlabel); - if (!OidIsValid(get_relname_relid(relname, namespaceid))) + /* is there any conflicting relation name? */ + ScanKeyInit(&key[0], + Anum_pg_class_relname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(relname)); + ScanKeyInit(&key[1], + Anum_pg_class_relnamespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(namespaceid)); + + scan = systable_beginscan(pgclassrel, ClassNameNspIndexId, + true /* indexOK */ , + &SnapshotDirty, + 2, key); + + collides = HeapTupleIsValid(systable_getnext(scan)); + + systable_endscan(scan); + + /* break out of loop if no conflict */ + if (!collides) { if (!isconstraint || !ConstraintNameExists(relname, namespaceid)) @@ -2491,6 +2530,8 @@ ChooseRelationName(const char *name1, const char *name2, snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass); } + table_close(pgclassrel, AccessShareLock); + return relname; } @@ -3739,8 +3780,8 @@ ReindexRelationConcurrently(Oid relationOid, ReindexParams *params) save_nestlevel = NewGUCNestLevel(); /* determine safety of this index for set_indexsafe_procflags */ - idx->safe = (indexRel->rd_indexprs == NIL && - indexRel->rd_indpred == NIL); + idx->safe = (RelationGetIndexExpressions(indexRel) == NIL && + RelationGetIndexPredicate(indexRel) == NIL); idx->tableId = RelationGetRelid(heapRel); idx->amId = indexRel->rd_rel->relam; @@ -4058,11 +4099,19 @@ ReindexRelationConcurrently(Oid relationOid, ReindexParams *params) false); /* + * Swapping the indexes might involve TOAST table access, so ensure we + * have a valid snapshot. + */ + PushActiveSnapshot(GetTransactionSnapshot()); + + /* * Swap old index with the new one. This also marks the new one as * valid and the old one as not valid. */ index_concurrently_swap(newidx->indexId, oldidx->indexId, oldName); + PopActiveSnapshot(); + /* * Invalidate the relcache for the table, so that after this commit * all sessions will refresh any cached plans that might reference the @@ -4304,7 +4353,10 @@ IndexSetParentIndex(Relation partitionIdx, Oid parentOid) /* set relhassubclass if an index partition has been added to the parent */ if (OidIsValid(parentOid)) + { + LockRelationOid(parentOid, ShareUpdateExclusiveLock); SetRelationHasSubclass(parentOid, true); + } /* set relispartition correctly on the partition */ update_relispartition(partRelid, OidIsValid(parentOid)); @@ -4355,14 +4407,17 @@ update_relispartition(Oid relationId, bool newval) { HeapTuple tup; Relation classRel; + ItemPointerData otid; classRel = table_open(RelationRelationId, RowExclusiveLock); - tup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId)); + tup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(relationId)); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for relation %u", relationId); + otid = tup->t_self; Assert(((Form_pg_class) GETSTRUCT(tup))->relispartition != newval); ((Form_pg_class) GETSTRUCT(tup))->relispartition = newval; - CatalogTupleUpdate(classRel, &tup->t_self, tup); + CatalogTupleUpdate(classRel, &otid, tup); + UnlockTuple(classRel, &otid, InplaceUpdateTupleLock); heap_freetuple(tup); table_close(classRel, RowExclusiveLock); } diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/opclasscmds.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/opclasscmds.c index 5f7ee238863..3c0acfafbd2 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/opclasscmds.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/opclasscmds.c @@ -66,6 +66,7 @@ static void storeOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid, List *operators, bool isAdd); static void storeProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid, List *procedures, bool isAdd); +static bool typeDepNeeded(Oid typid, OpFamilyMember *member); static void dropOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid, List *operators); static void dropProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid, @@ -1508,6 +1509,29 @@ storeOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid, recordDependencyOn(&myself, &referenced, op->ref_is_hard ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO); + if (typeDepNeeded(op->lefttype, op)) + { + referenced.classId = TypeRelationId; + referenced.objectId = op->lefttype; + referenced.objectSubId = 0; + + /* see comments in amapi.h about dependency strength */ + recordDependencyOn(&myself, &referenced, + op->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO); + } + + if (op->lefttype != op->righttype && + typeDepNeeded(op->righttype, op)) + { + referenced.classId = TypeRelationId; + referenced.objectId = op->righttype; + referenced.objectSubId = 0; + + /* see comments in amapi.h about dependency strength */ + recordDependencyOn(&myself, &referenced, + op->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO); + } + /* A search operator also needs a dep on the referenced opfamily */ if (OidIsValid(op->sortfamily)) { @@ -1609,6 +1633,29 @@ storeProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid, recordDependencyOn(&myself, &referenced, proc->ref_is_hard ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO); + if (typeDepNeeded(proc->lefttype, proc)) + { + referenced.classId = TypeRelationId; + referenced.objectId = proc->lefttype; + referenced.objectSubId = 0; + + /* see comments in amapi.h about dependency strength */ + recordDependencyOn(&myself, &referenced, + proc->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO); + } + + if (proc->lefttype != proc->righttype && + typeDepNeeded(proc->righttype, proc)) + { + referenced.classId = TypeRelationId; + referenced.objectId = proc->righttype; + referenced.objectSubId = 0; + + /* see comments in amapi.h about dependency strength */ + recordDependencyOn(&myself, &referenced, + proc->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO); + } + /* Post create hook of access method procedure */ InvokeObjectPostCreateHook(AccessMethodProcedureRelationId, entryoid, 0); @@ -1617,6 +1664,57 @@ storeProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid, table_close(rel, RowExclusiveLock); } +/* + * Detect whether a pg_amop or pg_amproc entry needs an explicit dependency + * on its lefttype or righttype. + * + * We make such a dependency unless the entry has an indirect dependency + * via its referenced operator or function. That's nearly always true + * for operators, but might well not be true for support functions. + */ +static bool +typeDepNeeded(Oid typid, OpFamilyMember *member) +{ + bool result = true; + + /* + * If the type is pinned, we don't need a dependency. This is a bit of a + * layering violation perhaps (recordDependencyOn would ignore the request + * anyway), but it's a cheap test and will frequently save a syscache + * lookup here. + */ + if (IsPinnedObject(TypeRelationId, typid)) + return false; + + /* Nope, so check the input types of the function or operator. */ + if (member->is_func) + { + Oid *argtypes; + int nargs; + + (void) get_func_signature(member->object, &argtypes, &nargs); + for (int i = 0; i < nargs; i++) + { + if (typid == argtypes[i]) + { + result = false; /* match, no dependency needed */ + break; + } + } + pfree(argtypes); + } + else + { + Oid lefttype, + righttype; + + op_input_types(member->object, &lefttype, &righttype); + if (typid == lefttype || typid == righttype) + result = false; /* match, no dependency needed */ + } + return result; +} + /* * Remove operator entries from an opfamily. diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/sequence.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/sequence.c index a8c77bc0f8d..4d3472419f6 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/sequence.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/sequence.c @@ -557,6 +557,13 @@ SequenceChangePersistence(Oid relid, char newrelpersistence) Buffer buf; HeapTupleData seqdatatuple; + /* + * ALTER SEQUENCE acquires this lock earlier. If we're processing an + * owned sequence for ALTER TABLE, lock now. Without the lock, we'd + * discard increments from nextval() calls (in other sessions) between + * this function's buffer unlock and this transaction's commit. + */ + LockRelationOid(relid, AccessExclusiveLock); init_sequence(relid, &elm, &seqrel); /* check the comment above nextval_internal()'s equivalent call. */ @@ -1358,7 +1365,10 @@ init_params(ParseState *pstate, List *options, bool for_identity, /* * The parser allows this, but it is only for identity columns, in * which case it is filtered out in parse_utilcmd.c. We only get - * here if someone puts it into a CREATE SEQUENCE. + * here if someone puts it into a CREATE SEQUENCE, where it'd be + * redundant. (The same is true for the equally-nonstandard + * LOGGED and UNLOGGED options, but for those, the default error + * below seems sufficient.) */ ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -1795,11 +1805,8 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) Oid relid = PG_GETARG_OID(0); SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; - bool is_called; - int64 result; + bool is_called = false; + int64 result = 0; /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -1810,12 +1817,28 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); - seq = read_seq_tuple(seqrel, &buf, &seqtuple); + /* + * We return NULL for other sessions' temporary sequences. The + * pg_sequences system view already filters those out, but this offers a + * defense against ERRORs in case someone invokes this function directly. + * + * Also, for the benefit of the pg_sequences view, we return NULL for + * unlogged sequences on standbys instead of throwing an error. + */ + if (!RELATION_IS_OTHER_TEMP(seqrel) && + (RelationIsPermanent(seqrel) || !RecoveryInProgress())) + { + Buffer buf; + HeapTupleData seqtuple; + Form_pg_sequence_data seq; + + seq = read_seq_tuple(seqrel, &buf, &seqtuple); - is_called = seq->is_called; - result = seq->last_value; + is_called = seq->is_called; + result = seq->last_value; - UnlockReleaseBuffer(buf); + UnlockReleaseBuffer(buf); + } relation_close(seqrel, NoLock); if (is_called) diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/subscriptioncmds.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/subscriptioncmds.c index 6fe111e98d3..e1260fc0e90 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/subscriptioncmds.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/subscriptioncmds.c @@ -1931,11 +1931,12 @@ AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId) } /* - * Check and log a warning if the publisher has subscribed to the same table - * from some other publisher. This check is required only if "copy_data = true" - * and "origin = none" for CREATE SUBSCRIPTION and - * ALTER SUBSCRIPTION ... REFRESH statements to notify the user that data - * having origin might have been copied. + * Check and log a warning if the publisher has subscribed to the same table, + * its partition ancestors (if it's a partition), or its partition children (if + * it's a partitioned table), from some other publishers. This check is + * required only if "copy_data = true" and "origin = none" for CREATE + * SUBSCRIPTION and ALTER SUBSCRIPTION ... REFRESH statements to notify the + * user that data having origin might have been copied. * * This check need not be performed on the tables that are already added * because incremental sync for those tables will happen through WAL and the @@ -1965,7 +1966,9 @@ check_publications_origin(WalReceiverConn *wrconn, List *publications, "SELECT DISTINCT P.pubname AS pubname\n" "FROM pg_publication P,\n" " LATERAL pg_get_publication_tables(P.pubname) GPT\n" - " JOIN pg_subscription_rel PS ON (GPT.relid = PS.srrelid),\n" + " JOIN pg_subscription_rel PS ON (GPT.relid = PS.srrelid OR" + " GPT.relid IN (SELECT relid FROM pg_partition_ancestors(PS.srrelid) UNION" + " SELECT relid FROM pg_partition_tree(PS.srrelid))),\n" " pg_class C JOIN pg_namespace N ON (N.oid = C.relnamespace)\n" "WHERE C.oid = GPT.relid AND P.pubname IN ("); get_publications_str(publications, &cmd, true); 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); diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/trigger.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/trigger.c index 021d7796bf2..878ca5f23e4 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/trigger.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/trigger.c @@ -2285,6 +2285,8 @@ FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc) { Trigger *trigger = &trigdesc->triggers[i]; + if (!TRIGGER_FOR_ROW(trigger->tgtype)) + continue; if (trigger->tgoldtable != NULL || trigger->tgnewtable != NULL) return trigger->tgname; } @@ -2543,6 +2545,15 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + if (relinfo->ri_FdwRoutine && transition_capture && + transition_capture->tcs_insert_new_table) + { + Assert(relinfo->ri_RootResultRelInfo); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot collect transition tuples from child foreign tables"))); + } + if ((trigdesc && trigdesc->trig_insert_after_row) || (transition_capture && transition_capture->tcs_insert_new_table)) AfterTriggerSaveEvent(estate, relinfo, NULL, NULL, @@ -2786,6 +2797,15 @@ ExecARDeleteTriggers(EState *estate, { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + if (relinfo->ri_FdwRoutine && transition_capture && + transition_capture->tcs_delete_old_table) + { + Assert(relinfo->ri_RootResultRelInfo); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot collect transition tuples from child foreign tables"))); + } + if ((trigdesc && trigdesc->trig_delete_after_row) || (transition_capture && transition_capture->tcs_delete_old_table)) { @@ -3112,6 +3132,16 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + if (relinfo->ri_FdwRoutine && transition_capture && + (transition_capture->tcs_update_old_table || + transition_capture->tcs_update_new_table)) + { + Assert(relinfo->ri_RootResultRelInfo); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot collect transition tuples from child foreign tables"))); + } + if ((trigdesc && trigdesc->trig_update_after_row) || (transition_capture && (transition_capture->tcs_update_old_table || @@ -4010,13 +4040,6 @@ afterTriggerCopyBitmap(Bitmapset *src) if (src == NULL) return NULL; - /* Create event context if we didn't already */ - if (afterTriggers.event_cxt == NULL) - afterTriggers.event_cxt = - AllocSetContextCreate(TopTransactionContext, - "AfterTriggerEvents", - ALLOCSET_DEFAULT_SIZES); - oldcxt = MemoryContextSwitchTo(afterTriggers.event_cxt); dst = bms_copy(src); @@ -4118,16 +4141,21 @@ afterTriggerAddEvent(AfterTriggerEventList *events, (char *) newshared >= chunk->endfree; newshared--) { + /* compare fields roughly by probability of them being different */ if (newshared->ats_tgoid == evtshared->ats_tgoid && - newshared->ats_relid == evtshared->ats_relid && newshared->ats_event == evtshared->ats_event && + newshared->ats_firing_id == 0 && newshared->ats_table == evtshared->ats_table && - newshared->ats_firing_id == 0) + newshared->ats_relid == evtshared->ats_relid && + bms_equal(newshared->ats_modifiedcols, + evtshared->ats_modifiedcols)) break; } if ((char *) newshared < chunk->endfree) { *newshared = *evtshared; + /* now we must make a suitably-long-lived copy of the bitmap */ + newshared->ats_modifiedcols = afterTriggerCopyBitmap(evtshared->ats_modifiedcols); newshared->ats_firing_id = 0; /* just to be sure */ chunk->endfree = (char *) newshared; } @@ -4296,8 +4324,12 @@ AfterTriggerExecute(EState *estate, bool should_free_new = false; /* - * Locate trigger in trigdesc. + * Locate trigger in trigdesc. It might not be present, and in fact the + * trigdesc could be NULL, if the trigger was dropped since the event was + * queued. In that case, silently do nothing. */ + if (trigdesc == NULL) + return; for (tgindx = 0; tgindx < trigdesc->numtriggers; tgindx++) { if (trigdesc->triggers[tgindx].tgoid == tgoid) @@ -4307,7 +4339,7 @@ AfterTriggerExecute(EState *estate, } } if (LocTriggerData.tg_trigger == NULL) - elog(ERROR, "could not find trigger %u", tgoid); + return; /* * If doing EXPLAIN ANALYZE, start charging time to this trigger. We want @@ -4688,6 +4720,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, /* Catch calls with insufficient relcache refcounting */ Assert(!RelationHasReferenceCountZero(rel)); trigdesc = rInfo->ri_TrigDesc; + /* caution: trigdesc could be NULL here */ finfo = rInfo->ri_TrigFunctions; instr = rInfo->ri_TrigInstrument; if (slot1 != NULL) @@ -4703,9 +4736,6 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, slot2 = MakeSingleTupleTableSlot(rel->rd_att, &TTSOpsMinimalTuple); } - if (trigdesc == NULL) /* should not happen */ - elog(ERROR, "relation %u has no triggers", - evtshared->ats_relid); } /* @@ -4971,10 +5001,10 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType) /* Now build the TransitionCaptureState struct, in caller's context */ state = (TransitionCaptureState *) palloc0(sizeof(TransitionCaptureState)); - state->tcs_delete_old_table = trigdesc->trig_delete_old_table; - state->tcs_update_old_table = trigdesc->trig_update_old_table; - state->tcs_update_new_table = trigdesc->trig_update_new_table; - state->tcs_insert_new_table = trigdesc->trig_insert_new_table; + state->tcs_delete_old_table = need_old_del; + state->tcs_update_old_table = need_old_upd; + state->tcs_update_new_table = need_new_upd; + state->tcs_insert_new_table = need_new_ins; state->tcs_private = table; return state; @@ -6436,7 +6466,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, new_shared.ats_table = transition_capture->tcs_private; else new_shared.ats_table = NULL; - new_shared.ats_modifiedcols = afterTriggerCopyBitmap(modifiedCols); + new_shared.ats_modifiedcols = modifiedCols; afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth].events, &new_event, &new_shared); diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c index a42c46a775b..dfa8c18e95a 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c @@ -818,12 +818,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt) "BYPASSRLS", "BYPASSRLS"))); } - /* To add members to a role, you need ADMIN OPTION. */ + /* To add or drop members, you need ADMIN OPTION. */ if (drolemembers && !is_admin_of_role(currentUserId, roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to alter role"), - errdetail("Only roles with the %s option on role \"%s\" may add members.", + errdetail("Only roles with the %s option on role \"%s\" may add or drop members.", "ADMIN", rolename))); /* Convert validuntil to internal form */ @@ -2554,6 +2554,8 @@ check_createrole_self_grant(char **newval, void **extra, GucSource source) list_free(elemlist); result = (unsigned *) guc_malloc(LOG, sizeof(unsigned)); + if (!result) + return false; *result = options; *extra = result; diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/vacuum.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/vacuum.c index adb58850b6b..aa4fd5db159 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/vacuum.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/vacuum.c @@ -619,7 +619,15 @@ vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy, if (params->options & VACOPT_VACUUM) { - if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy)) + VacuumParams params_copy; + + /* + * vacuum_rel() scribbles on the parameters, so give it a copy + * to avoid affecting other relations. + */ + memcpy(¶ms_copy, params, sizeof(VacuumParams)); + + if (!vacuum_rel(vrel->oid, vrel->relation, ¶ms_copy, bstrategy)) continue; } @@ -642,6 +650,8 @@ vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy, if (use_own_xacts) { PopActiveSnapshot(); + /* standard_ProcessUtility() does CCI if !use_own_xacts */ + CommandCounterIncrement(); CommitTransactionCommand(); } else @@ -1427,7 +1437,9 @@ vac_update_relstats(Relation relation, { Oid relid = RelationGetRelid(relation); Relation rd; + ScanKeyData key[1]; HeapTuple ctup; + void *inplace_state; Form_pg_class pgcform; bool dirty, futurexid, @@ -1438,7 +1450,12 @@ vac_update_relstats(Relation relation, rd = table_open(RelationRelationId, RowExclusiveLock); /* Fetch a copy of the tuple to scribble on */ - ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); + ScanKeyInit(&key[0], + Anum_pg_class_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + systable_inplace_update_begin(rd, ClassOidIndexId, true, + NULL, 1, key, &ctup, &inplace_state); if (!HeapTupleIsValid(ctup)) elog(ERROR, "pg_class entry for relid %u vanished during vacuuming", relid); @@ -1546,7 +1563,9 @@ vac_update_relstats(Relation relation, /* If anything changed, write out the tuple. */ if (dirty) - heap_inplace_update(rd, ctup); + systable_inplace_update_finish(inplace_state, ctup); + else + systable_inplace_update_cancel(inplace_state); table_close(rd, RowExclusiveLock); @@ -1598,6 +1617,7 @@ vac_update_datfrozenxid(void) bool bogus = false; bool dirty = false; ScanKeyData key[1]; + void *inplace_state; /* * Restrict this task to one backend per database. This avoids race @@ -1721,20 +1741,18 @@ vac_update_datfrozenxid(void) relation = table_open(DatabaseRelationId, RowExclusiveLock); /* - * Get the pg_database tuple to scribble on. Note that this does not - * directly rely on the syscache to avoid issues with flattened toast - * values for the in-place update. + * Fetch a copy of the tuple to scribble on. We could check the syscache + * tuple first. If that concluded !dirty, we'd avoid waiting on + * concurrent heap_update() and would avoid exclusive-locking the buffer. + * For now, don't optimize that. */ ScanKeyInit(&key[0], Anum_pg_database_oid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(MyDatabaseId)); - scan = systable_beginscan(relation, DatabaseOidIndexId, true, - NULL, 1, key); - tuple = systable_getnext(scan); - tuple = heap_copytuple(tuple); - systable_endscan(scan); + systable_inplace_update_begin(relation, DatabaseOidIndexId, true, + NULL, 1, key, &tuple, &inplace_state); if (!HeapTupleIsValid(tuple)) elog(ERROR, "could not find tuple for database %u", MyDatabaseId); @@ -1768,7 +1786,9 @@ vac_update_datfrozenxid(void) newMinMulti = dbform->datminmxid; if (dirty) - heap_inplace_update(relation, tuple); + systable_inplace_update_finish(inplace_state, tuple); + else + systable_inplace_update_cancel(inplace_state); heap_freetuple(tuple); table_close(relation, RowExclusiveLock); @@ -1981,9 +2001,16 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, Oid save_userid; int save_sec_context; int save_nestlevel; + VacuumParams toast_vacuum_params; Assert(params != NULL); + /* + * This function scribbles on the parameters, so make a copy early to + * avoid affecting the TOAST table (if we do end up recursing to it). + */ + memcpy(&toast_vacuum_params, params, sizeof(VacuumParams)); + /* Begin a transaction for vacuuming this relation */ StartTransactionCommand(); @@ -2246,10 +2273,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, */ if (toast_relid != InvalidOid) { - VacuumParams toast_vacuum_params; - /* force VACOPT_PROCESS_MAIN so vacuum_rel() processes it */ - memcpy(&toast_vacuum_params, params, sizeof(VacuumParams)); toast_vacuum_params.options |= VACOPT_PROCESS_MAIN; vacuum_rel(toast_relid, NULL, &toast_vacuum_params, bstrategy); diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/variable.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/variable.c index d413e7f1cca..023da107c0d 100644 --- a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/variable.c +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/variable.c @@ -479,7 +479,7 @@ show_log_timezone(void) */ /* - * GUC check_hook for assign_timezone_abbreviations + * GUC check_hook for timezone_abbreviations */ bool check_timezone_abbreviations(char **newval, void **extra, GucSource source) @@ -511,7 +511,7 @@ check_timezone_abbreviations(char **newval, void **extra, GucSource source) } /* - * GUC assign_hook for assign_timezone_abbreviations + * GUC assign_hook for timezone_abbreviations */ void assign_timezone_abbreviations(const char *newval, void *extra) @@ -811,40 +811,78 @@ check_session_authorization(char **newval, void **extra, GucSource source) if (*newval == NULL) return true; - if (!IsTransactionState()) + if (InitializingParallelWorker) { /* - * Can't do catalog lookups, so fail. The result of this is that - * session_authorization cannot be set in postgresql.conf, which seems - * like a good thing anyway, so we don't work hard to avoid it. + * In parallel worker initialization, we want to copy the leader's + * state even if it no longer matches the catalogs. ParallelWorkerMain + * already installed the correct role OID and superuser state. */ - return false; + roleid = GetSessionUserId(); + is_superuser = GetSessionUserIsSuperuser(); } - - /* Look up the username */ - roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval)); - if (!HeapTupleIsValid(roleTup)) + else { + if (!IsTransactionState()) + { + /* + * Can't do catalog lookups, so fail. The result of this is that + * session_authorization cannot be set in postgresql.conf, which + * seems like a good thing anyway, so we don't work hard to avoid + * it. + */ + return false; + } + /* * When source == PGC_S_TEST, we don't throw a hard error for a - * nonexistent user name, only a NOTICE. See comments in guc.h. + * nonexistent user name or insufficient privileges, only a NOTICE. + * See comments in guc.h. */ - if (source == PGC_S_TEST) + + /* Look up the username */ + roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval)); + if (!HeapTupleIsValid(roleTup)) { - ereport(NOTICE, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("role \"%s\" does not exist", *newval))); - return true; + if (source == PGC_S_TEST) + { + ereport(NOTICE, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role \"%s\" does not exist", *newval))); + return true; + } + GUC_check_errmsg("role \"%s\" does not exist", *newval); + return false; } - GUC_check_errmsg("role \"%s\" does not exist", *newval); - return false; - } - roleform = (Form_pg_authid) GETSTRUCT(roleTup); - roleid = roleform->oid; - is_superuser = roleform->rolsuper; + roleform = (Form_pg_authid) GETSTRUCT(roleTup); + roleid = roleform->oid; + is_superuser = roleform->rolsuper; + + ReleaseSysCache(roleTup); - ReleaseSysCache(roleTup); + /* + * Only superusers may SET SESSION AUTHORIZATION a role other than + * itself. Note that in case of multiple SETs in a single session, the + * original authenticated user's superuserness is what matters. + */ + if (roleid != GetAuthenticatedUserId() && + !GetAuthenticatedUserIsSuperuser()) + { + if (source == PGC_S_TEST) + { + ereport(NOTICE, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission will be denied to set session authorization \"%s\"", + *newval))); + return true; + } + GUC_check_errcode(ERRCODE_INSUFFICIENT_PRIVILEGE); + GUC_check_errmsg("permission denied to set session authorization \"%s\"", + *newval); + return false; + } + } /* Set up "extra" struct for assign_session_authorization to use */ myextra = (role_auth_extra *) guc_malloc(LOG, sizeof(role_auth_extra)); @@ -894,6 +932,16 @@ check_role(char **newval, void **extra, GucSource source) roleid = InvalidOid; is_superuser = false; } + else if (InitializingParallelWorker) + { + /* + * In parallel worker initialization, we want to copy the leader's + * state even if it no longer matches the catalogs. ParallelWorkerMain + * already installed the correct role OID and superuser state. + */ + roleid = GetCurrentRoleId(); + is_superuser = session_auth_is_superuser; + } else { if (!IsTransactionState()) @@ -933,13 +981,8 @@ check_role(char **newval, void **extra, GucSource source) ReleaseSysCache(roleTup); - /* - * Verify that session user is allowed to become this role, but skip - * this in parallel mode, where we must blindly recreate the parallel - * leader's state. - */ - if (!InitializingParallelWorker && - !member_can_set_role(GetSessionUserId(), roleid)) + /* Verify that session user is allowed to become this role */ + if (!member_can_set_role(GetSessionUserId(), roleid)) { if (source == PGC_S_TEST) { @@ -1039,6 +1082,8 @@ check_application_name(char **newval, void **extra, GucSource source) return false; } + guc_free(*newval); + pfree(clean); *newval = ret; return true; @@ -1075,6 +1120,8 @@ check_cluster_name(char **newval, void **extra, GucSource source) return false; } + guc_free(*newval); + pfree(clean); *newval = ret; return true; |
