summaryrefslogtreecommitdiffstats
path: root/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands
diff options
context:
space:
mode:
authorvvvv <[email protected]>2025-10-24 14:59:50 +0300
committervvvv <[email protected]>2025-10-24 15:29:24 +0300
commit5b0d18921f2a509d8363c40a5ca208dfed026287 (patch)
treed1369c696d3a9e9a65b68d9208e198269a48cfbc /yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands
parente7fbdb6e81ae4a296e710b133de7a2a04b31bbc4 (diff)
YQL-20567 upgrade PG up to 16.10 & fix instructions
init commit_hash:81aba13295273281d19d2d332a48ff1c44977447
Diffstat (limited to 'yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands')
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/alter.c9
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/analyze.c7
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/async.c11
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/copyto.c8
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/dbcommands.c44
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/foreigncmds.c15
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/functioncmds.c28
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/indexcmds.c71
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/opclasscmds.c98
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/sequence.c43
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/subscriptioncmds.c15
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/tablecmds.c1033
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/trigger.c68
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c6
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/vacuum.c54
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/variable.c109
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(&params_copy, params, sizeof(VacuumParams));
+
+ if (!vacuum_rel(vrel->oid, vrel->relation, &params_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;