typedefenum AlterTablePass
{
AT_PASS_UNSET = -1, /* UNSET will cause ERROR */
AT_PASS_DROP, /* DROP (all flavors) */
AT_PASS_ALTER_TYPE, /* ALTER COLUMN TYPE */
AT_PASS_ADD_COL, /* ADD COLUMN */
AT_PASS_SET_EXPRESSION, /* ALTER SET EXPRESSION */
AT_PASS_OLD_INDEX, /* re-add existing indexes */
AT_PASS_OLD_CONSTR, /* re-add existing constraints */ /* We could support a RENAME COLUMN pass here, but not currently used */
AT_PASS_ADD_CONSTR, /* ADD constraints (initial examination) */
AT_PASS_COL_ATTRS, /* set column attributes, eg NOT NULL */
AT_PASS_ADD_INDEXCONSTR, /* ADD index-based constraints */
AT_PASS_ADD_INDEX, /* ADD indexes */
AT_PASS_ADD_OTHERCONSTR, /* ADD other constraints, defaults */
AT_PASS_MISC, /* other stuff */
} AlterTablePass;
#define AT_NUM_PASSES (AT_PASS_MISC + 1)
typedefstruct AlteredTableInfo
{ /* Information saved before any work commences: */
Oid relid; /* Relation to work on */ char relkind; /* Its relkind */
TupleDesc oldDesc; /* Pre-modification tuple descriptor */
/* Information saved by Phase 1 for Phase 2: */
List *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */ /* Information saved by Phases 1/2 for Phase 3: */
List *constraints; /* List of NewConstraint */
List *newvals; /* List of NewColumnValue */
List *afterStmts; /* List of utility command parsetrees */ bool verify_new_notnull; /* T if we should recheck NOT NULL */ int rewrite; /* Reason for forced rewrite, if any */ bool chgAccessMethod; /* T if SET ACCESS METHOD is used */
Oid newAccessMethod; /* new access method; 0 means no change,
* if above is true */
Oid newTableSpace; /* new tablespace; 0 means no change */ bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */ char newrelpersistence; /* if above is true */
Expr *partition_constraint; /* for attach partition validation */ /* true, if validating default due to some other attach/detach */ bool validate_default; /* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
List *changedIndexOids; /* OIDs of indexes to rebuild */
List *changedIndexDefs; /* string definitions of same */ char *replicaIdentityIndex; /* index to reset as REPLICA IDENTITY */ char *clusterOnIndex; /* index to use for CLUSTER */
List *changedStatisticsOids; /* OIDs of statistics to rebuild */
List *changedStatisticsDefs; /* string definitions of same */
} AlteredTableInfo;
/* Struct describing one new constraint to check in Phase 3 scan */ /* Note: new not-null constraints are handled elsewhere */ typedefstruct NewConstraint
{ char *name; /* Constraint name, or NULL if none */
ConstrType contype; /* CHECK or FOREIGN */
Oid refrelid; /* PK rel, if FOREIGN */
Oid refindid; /* OID of PK's index, if FOREIGN */ bool conwithperiod; /* Whether the new FOREIGN KEY uses PERIOD */
Oid conid; /* OID of pg_constraint entry, if FOREIGN */
Node *qual; /* Check expr or CONSTR_FOREIGN Constraint */
ExprState *qualstate; /* Execution state for CHECK expr */
} NewConstraint;
/* *Structdescribingonenewcolumnvaluethatneedstobecomputedduring *Phase3copy(thiscouldbeeitheranewcolumnwithanon-nulldefault,or *acolumnthatwe'rechangingthetypeof).Columnswithoutsuchanentry *arejustcopiedfromtheoldtableduringATRewriteTable.Notethatthe *exprisanexpressionover*old*tablevalues,exceptwhenis_generated *istrue;thenitisanexpressionovercolumnsofthe*new*tuple.
*/ typedefstruct NewColumnValue
{
AttrNumber attnum; /* which column */
Expr *expr; /* expression to compute */
ExprState *exprstate; /* execution state */ bool is_generated; /* is it a GENERATED expression? */
} NewColumnValue;
staticconststruct dropmsgstrings dropmsgstringarray[] = {
{RELKIND_RELATION,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("table \"%s\" does not exist"),
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
{RELKIND_SEQUENCE,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("sequence \"%s\" does not exist"),
gettext_noop("sequence \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a sequence"),
gettext_noop("Use DROP SEQUENCE to remove a sequence.")},
{RELKIND_VIEW,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("view \"%s\" does not exist"),
gettext_noop("view \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a view"),
gettext_noop("Use DROP VIEW to remove a view.")},
{RELKIND_MATVIEW,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("materialized view \"%s\" does not exist"),
gettext_noop("materialized view \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a materialized view"),
gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view.")},
{RELKIND_INDEX,
ERRCODE_UNDEFINED_OBJECT,
gettext_noop("index \"%s\" does not exist"),
gettext_noop("index \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not an index"),
gettext_noop("Use DROP INDEX to remove an index.")},
{RELKIND_COMPOSITE_TYPE,
ERRCODE_UNDEFINED_OBJECT,
gettext_noop("type \"%s\" does not exist"),
gettext_noop("type \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a type"),
gettext_noop("Use DROP TYPE to remove a type.")},
{RELKIND_FOREIGN_TABLE,
ERRCODE_UNDEFINED_OBJECT,
gettext_noop("foreign table \"%s\" does not exist"),
gettext_noop("foreign table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a foreign table"),
gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
{RELKIND_PARTITIONED_TABLE,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("table \"%s\" does not exist"),
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
{RELKIND_PARTITIONED_INDEX,
ERRCODE_UNDEFINED_OBJECT,
gettext_noop("index \"%s\" does not exist"),
gettext_noop("index \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not an index"),
gettext_noop("Use DROP INDEX to remove an index.")},
{'\0', 0, NULL, NULL, NULL, NULL}
};
/* communication between RemoveRelations and RangeVarCallbackForDropRelation */ struct DropRelationCallbackState
{ /* These fields are set by RemoveRelations: */ char expected_relkind;
LOCKMODE heap_lockmode; /* These fields are state to track which subsidiary locks are held: */
Oid heapOid;
Oid partParentOid; /* These fields are passed back by RangeVarCallbackForDropRelation: */ char actual_relkind; char actual_relpersistence;
};
/* *Checkconsistencyofarguments
*/ if (stmt->oncommit != ONCOMMIT_NOOP
&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
if (stmt->partspec != NULL)
{ if (relkind != RELKIND_RELATION)
elog(ERROR, "unexpected relkind: %d", (int) relkind);
/* Determine the list of OIDs of the parents. */
inheritOids = NIL;
foreach(listptr, stmt->inhRelations)
{
RangeVar *rv = (RangeVar *) lfirst(listptr);
Oid parentOid;
/* *Rejectduplicationsinthelistofparents.
*/ if (list_member_oid(inheritOids, parentOid))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" would be inherited from more than once",
get_rel_name(parentOid))));
/* In all cases disallow placing user relations in pg_global */ if (tablespaceId == GLOBALTABLESPACE_OID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("only shared relations can be placed in pg_global tablespace")));
/* Identify user ID that will own the table */ if (!OidIsValid(ownerId))
ownerId = GetUserId();
/* Process and store partition bound, if any. */ if (stmt->partbound)
{
PartitionBoundSpec *bound;
ParseState *pstate;
Oid parentId = linitial_oid(inheritOids),
defaultPartOid;
Relation parent,
defaultRel = NULL;
ParseNamespaceItem *nsitem;
/* Already have strong enough lock on the parent */
parent = table_open(parentId, NoLock);
/* *Wearegoingtotrytovalidatethepartitionboundspecification *againstthepartitionkeyofparentRel,soitbetterhaveone.
*/ if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("\"%s\" is not partitioned",
RelationGetRelationName(parent))));
/* *Ifthedefaultpartitionexists,itspartitionconstraintswill *changeaftertheadditionofthisnewpartitionsuchthatitwon't *allowanyrowthatqualifiesforthisnewpartition.So,checkthat *theexistingdatainthedefaultpartitionsatisfiestheconstraint *asitwillexistafteraddingthispartition.
*/ if (OidIsValid(defaultPartOid))
{
check_default_partition_contents(parent, defaultRel, bound); /* Keep the lock until commit. */
table_close(defaultRel, NoLock);
}
/* Update the pg_class entry. */
StorePartitionBound(rel, parent, bound);
table_close(parent, NoLock);
}
/* Store inheritance information for new rel. */
StoreCatalogInheritance(relationId, inheritOids, stmt->partbound != NULL);
/* *Processthepartitioningspecification(ifany)andstorethepartition *keyinformationintothecatalog.
*/ if (partitioned)
{
ParseState *pstate; int partnatts;
AttrNumber partattrs[PARTITION_MAX_KEYS];
Oid partopclass[PARTITION_MAX_KEYS];
Oid partcollation[PARTITION_MAX_KEYS];
List *partexprs = NIL;
/* Protect fixed-size arrays here and in executor */ if (partnatts > PARTITION_MAX_KEYS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("cannot partition using more than %d columns",
PARTITION_MAX_KEYS)));
/* *NowaddanynewlyspecifiedCHECKconstraintstothenewrelation.Same *asfordefaultsabove,buttheseneedtocomeafterpartitioningisset *up.Wesavetheconstraintnamesthatwereused,toavoiddupesbelow.
*/ if (stmt->constraints)
{
List *conlist;
for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++) if (rentry->kind == rightkind) break;
Assert(rentry->kind != '\0');
for (wentry = dropmsgstringarray; wentry->kind != '\0'; wentry++) if (wentry->kind == wrongkind) break; /* wrongkind could be something we don't have in our table... */
/* DROP CONCURRENTLY uses a weaker lock, and has some restrictions */ if (drop->concurrent)
{ /* *Notethatfortemporaryrelationsthislockmaygetupgradedlater *on,butasnoothersessioncanaccessatemporaryrelation,this *isactuallyfine.
*/
lockmode = ShareUpdateExclusiveLock;
Assert(drop->removeType == OBJECT_INDEX); if (list_length(drop->objects) != 1)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DROP INDEX CONCURRENTLY does not support dropping multiple objects"))); if (drop->behavior == DROP_CASCADE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DROP INDEX CONCURRENTLY does not support CASCADE")));
}
/* Look up the appropriate relation using namespace search. */
state.expected_relkind = relkind;
state.heap_lockmode = drop->concurrent ?
ShareUpdateExclusiveLock : AccessExclusiveLock; /* We must initialize these fields to show that no locks are held: */
state.heapOid = InvalidOid;
state.partParentOid = InvalidOid;
/* Didn't find a relation, so no need for locking or permission checks. */ if (!OidIsValid(relOid)) return;
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid)); if (!HeapTupleIsValid(tuple)) return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
is_partition = classform->relispartition;
/* Pass back some data to save lookups in RemoveRelations */
state->actual_relkind = classform->relkind;
state->actual_relpersistence = classform->relpersistence;
if (state->expected_relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind,
state->expected_relkind);
/* Allow DROP to either table owner or schema owner */ if (!object_ownercheck(RelationRelationId, relOid, GetUserId()) &&
!object_ownercheck(NamespaceRelationId, classform->relnamespace, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER,
get_relkind_objtype(classform->relkind),
rel->relname);
/* Mark object as being an invalid index of system catalogs */ if (!indisvalid)
invalid_system_index = true;
}
/* In the case of an invalid index, it is fine to bypass this check */ if (!invalid_system_index && !allowSystemTableMods && IsSystemClass(relOid, classform))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
rel->relname)));
/* Log this relation only if needed for logical decoding */ if (RelationIsLogicallyLogged(rel))
relids_logged = lappend_oid(relids_logged, childrelid);
}
} elseif (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot truncate only a partitioned table"),
errhint("Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.")));
}
/* Log this relation only if needed for logical decoding */ if (RelationIsLogicallyLogged(rel))
relids_logged = lappend_oid(relids_logged, relid);
}
}
}
/* This check must match AlterSequence! */ if (!object_ownercheck(RelationRelationId, seq_relid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
RelationGetRelationName(seq_rel));
seq_relids = lappend_oid(seq_relids, seq_relid);
relation_close(seq_rel, NoLock);
}
}
}
/* Prepare to catch AFTER triggers. */
AfterTriggerBeginQuery();
/* *Checkforandrejecttableswithtoomanycolumns.Weperformthis *checkrelativelyearlyfortworeasons:(a)wedon'truntheriskof *overflowinganAttrNumberinsubsequentcode(b)anO(n^2)algorithmis *okayifwe'reprocessing<=1600columns,butcouldtakeminutesto *executeiftheuserattemptstocreateatablewithhundredsof *thousandsofcolumns. * *Notethatwealsoneedtocheckthatwedonotexceedthisfigureafter *includingcolumnsfrominheritedrelations.
*/ if (list_length(columns) > MaxHeapAttributeNumber)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("tables can have at most %d columns",
MaxHeapAttributeNumber)));
/* *Wedonotallowpartitionedtablesandpartitionstoparticipatein *regularinheritance.
*/ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
RelationGetRelationName(relation)))); if (relation->rd_rel->relispartition && !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partition \"%s\"",
RelationGetRelationName(relation))));
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
RelationGetRelationName(relation))));
/* *Iftheparentispermanent,somustbeallofitspartitions.Note *thatinheritanceallowsthatcase.
*/ if (is_partition &&
relation->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
relpersistence == RELPERSISTENCE_TEMP)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
RelationGetRelationName(relation))));
/* Permanent rels cannot inherit from temporary ones */ if (relpersistence != RELPERSISTENCE_TEMP &&
relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg(!is_partition
? "cannot inherit from temporary relation \"%s\""
: "cannot create a permanent relation as partition of temporary relation \"%s\"",
RelationGetRelationName(relation))));
/* If existing rel is temp, it must belong to this session */ if (relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
!relation->rd_islocaltemp)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg(!is_partition
? "cannot inherit from temporary relation of another session"
: "cannot create as partition of temporary relation of another session")));
/* *markattnotnullifparenthasit
*/ if (bms_is_member(parent_attno, nncols))
mergeddef->is_not_null = true;
/* *Locatedefault/generationexpressionifany
*/ if (attribute->atthasdef)
{
Node *this_default;
this_default = TupleDescGetDefault(tupleDesc, parent_attno); if (this_default == NULL)
elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
parent_attno, RelationGetRelationName(relation));
/* Adjust Vars to match new table's column numbering */
this_default = map_variable_attnos(this_default, 1, 0,
newattmap,
InvalidOid, &found_whole_row);
/* *Forthemomentwehavetorejectwhole-rowvariables.Wecould *convertthem,ifweknewthenewtable'srowtypeOID,butthat *hasn'tbeenassignedyet.(Avariablecouldonlyappearina *generationexpression,sotheerrormessageiscorrect.)
*/ if (found_whole_row)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert whole-row table reference"),
errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".",
def->colname,
RelationGetRelationName(relation))));
for (int i = 0; i < constr->num_check; i++)
{ char *name = check[i].ccname;
Node *expr; bool found_whole_row;
/* ignore if the constraint is non-inheritable */ if (check[i].ccnoinherit) continue;
/* Adjust Vars to match new table's column numbering */
expr = map_variable_attnos(stringToNode(check[i].ccbin), 1, 0,
newattmap,
InvalidOid, &found_whole_row);
/* *Forthemomentwehavetorejectwhole-rowvariables.We *couldconvertthem,ifweknewthenewtable'srowtypeOID, *butthathasn'tbeenassignedyet.
*/ if (found_whole_row)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert whole-row table reference"),
errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".",
name,
RelationGetRelationName(relation))));
/* *Checkthatwehaven'texceededthelegal#ofcolumnsaftermerging *ininheritedcolumns.
*/ if (list_length(columns) > MaxHeapAttributeNumber)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("tables can have at most %d columns",
MaxHeapAttributeNumber)));
}
/* complain for constraints on columns not in parent */ if (!found)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" does not exist",
restdef->colname)));
}
}
/* Non-matching names never conflict */ if (strcmp(ccon->name, name) != 0) continue;
if (equal(expr, ccon->expr))
{ /* OK to merge constraint with existing */ if (pg_add_s16_overflow(ccon->inhcount, 1,
&ccon->inhcount))
ereport(ERROR,
errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many inheritance parents"));
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("check constraint name \"%s\" appears multiple times but with different expressions",
name)));
}
if (exist_attno == newcol_attno)
ereport(NOTICE,
(errmsg("merging column \"%s\" with inherited definition",
attributeName))); else
ereport(NOTICE,
(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
errdetail("User-specified column moved to the position of the inherited column.")));
if (pg_add_s16_overflow(prevdef->inhcount, 1,
&prevdef->inhcount))
ereport(ERROR,
errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many inheritance parents"));
if (classtuple->relhassubclass != relhassubclass)
{
classtuple->relhassubclass = relhassubclass;
CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple);
} else
{ /* no need to change tuple, but force relcache rebuild anyway */
CacheInvalidateRelcacheByTuple(tuple);
}
/* *Wecannotsupportmovingmappedrelationsintodifferenttablespaces. *(Inparticularthiseliminatesallsharedcatalogs.)
*/ if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move system relation \"%s\"",
RelationGetRelationName(rel))));
/* Cannot move a non-shared relation into pg_global */ if (newTableSpaceId == GLOBALTABLESPACE_OID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("only shared relations can be placed in pg_global tablespace")));
/* *Donotallowmovingtemptablesofotherbackends...theirlocal *buffermanagerisnotgoingtocope.
*/ if (RELATION_IS_OTHER_TEMP(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move temporary tables of other sessions")));
/* *find_all_inheritorsdoestherecursivesearchoftheinheritance *hierarchy,soallwehavetodoisprocessalloftherelidsinthe *listthatitreturns.
*/
forboth(lo, child_oids, li, child_numparents)
{
Oid childrelid = lfirst_oid(lo); int numparents = lfirst_int(li);
if (childrelid == myrelid) continue; /* note we need not recurse again */
renameatt_internal(childrelid, oldattname, newattname, false, true, numparents, behavior);
}
} else
{ /* *Ifwearetoldnottorecurse,therehadbetternotbeanychild *tables;elsetherenamewouldputthemoutofstep. * *expected_parentswillonlybe0ifwearenotalreadyrecursing.
*/ if (expected_parents == 0 &&
find_inheritance_children(myrelid, NoLock) != NIL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("inherited column \"%s\" must be renamed in child tables too",
oldattname)));
}
/* rename attributes in typed tables of composite type */ if (targetrelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
{
List *child_oids;
ListCell *lo;
/* lock level taken here should match renameatt_internal */
relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
stmt->missing_ok ? RVR_MISSING_OK : 0,
RangeVarCallbackForRenameAttribute,
NULL);
if (!OidIsValid(relid))
{
ereport(NOTICE,
(errmsg("relation \"%s\" does not exist, skipping",
stmt->relation->relname))); return InvalidObjectAddress;
}
attnum =
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
stmt->relation->inh, /* recursive? */ false, /* recursing? */ 0, /* expected inhcount */
stmt->behavior);
if (!OidIsValid(relid))
{
ereport(NOTICE,
(errmsg("relation \"%s\" does not exist, skipping",
stmt->relation->relname))); return InvalidObjectAddress;
}
expected_refcnt = rel->rd_isnailed ? 2 : 1; if (rel->rd_refcnt != expected_refcnt)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE), /* translator: first %s is a SQL command, eg ALTER TABLE */
errmsg("cannot %s \"%s\" because it is being used by active queries in this session",
stmt, RelationGetRelationName(rel))));
if (rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
AfterTriggerPendingOnRel(RelationGetRelid(rel)))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE), /* translator: first %s is a SQL command, eg ALTER TABLE */
errmsg("cannot %s \"%s\" because it has pending trigger events",
stmt, RelationGetRelationName(rel))));
}
/* *CheckAlterTableIsSafe *Verifythatit'ssafetoallowALTERTABLEonthisrelation. * *ThisconsistsofCheckTableNotInUse()plusacheckthattherelation *isn'tanothersession'stemptable.Wemustsplitoutthetemp-table *checkbecausetherearecallersofCheckTableNotInUse()thatdon'twant *that,notablyDROPTABLE.(WemustallowDROPorwecouldn'tcleanout *anorphanedtempschema.)Comparetruncate_check_activity().
*/ staticvoid
CheckAlterTableIsSafe(Relation rel)
{ /* *Don'tallowALTERontemptablesofotherbackends.Theirlocalbuffer *managerisnotgoingtocopeifweneedtochangethetable'scontents. *Evenifwedon't,theremaybeoptimizationsthatassumetemptables *aren'tsubjecttosuchinterference.
*/ if (RELATION_IS_OTHER_TEMP(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter temporary tables of other sessions")));
switch (cmd->subtype)
{ /* *Thesesubcommandsrewritetheheap,sorequirefulllocks.
*/ case AT_AddColumn: /* may rewrite heap, in some cases and visible
* to SELECT */ case AT_SetAccessMethod: /* must rewrite heap */ case AT_SetTableSpace: /* must rewrite heap */ case AT_AlterColumnType: /* must rewrite heap */
cmd_lockmode = AccessExclusiveLock; break;
/* *Thesesubcommandsmayrequireadditionoftoasttables.If *weaddatoasttabletoatablecurrentlybeingscanned,we *mightmissdataaddedtothenewtoasttablebyconcurrent *inserttransactions.
*/ case AT_SetStorage: /* may add toast tables, see
* ATRewriteCatalogs() */
cmd_lockmode = AccessExclusiveLock; break;
/* *RemovingconstraintscanaffectSELECTsthathavebeen *optimizedassumingtheconstraintholdstrue.Seealso *CloneFkReferenced.
*/ case AT_DropConstraint: /* as DROP INDEX */ case AT_DropNotNull: /* may change some SQL plans */
cmd_lockmode = AccessExclusiveLock; break;
/* *SubcommandsthatmaybevisibletoconcurrentSELECTs
*/ case AT_DropColumn: /* change visible to SELECT */ case AT_AddColumnToView: /* CREATE VIEW */ case AT_DropOids: /* used to equiv to DropColumn */ case AT_EnableAlwaysRule: /* may change SELECT rules */ case AT_EnableReplicaRule: /* may change SELECT rules */ case AT_EnableRule: /* may change SELECT rules */ case AT_DisableRule: /* may change SELECT rules */
cmd_lockmode = AccessExclusiveLock; break;
/* *ChangingownermayremoveimplicitSELECTprivileges
*/ case AT_ChangeOwner: /* change visible to SELECT */
cmd_lockmode = AccessExclusiveLock; break;
/* *Changingforeigntableoptionsmayaffectoptimization.
*/ case AT_GenericOptions: case AT_AlterColumnGenericOptions:
cmd_lockmode = AccessExclusiveLock; break;
/* *Thesesubcommandsaffectwriteoperationsonly.
*/ case AT_EnableTrig: case AT_EnableAlwaysTrig: case AT_EnableReplicaTrig: case AT_EnableTrigAll: case AT_EnableTrigUser: case AT_DisableTrig: case AT_DisableTrigAll: case AT_DisableTrigUser:
cmd_lockmode = ShareRowExclusiveLock; break;
/* *Thesesubcommandsaffectwriteoperationsonly.XXX *Theoretically,thesecouldbeShareRowExclusiveLock.
*/ case AT_ColumnDefault: case AT_CookedColumnDefault: case AT_AlterConstraint: case AT_AddIndex: /* from ADD CONSTRAINT */ case AT_AddIndexConstraint: case AT_ReplicaIdentity: case AT_SetNotNull: case AT_EnableRowSecurity: case AT_DisableRowSecurity: case AT_ForceRowSecurity: case AT_NoForceRowSecurity: case AT_AddIdentity: case AT_DropIdentity: case AT_SetIdentity: case AT_SetExpression: case AT_DropExpression: case AT_SetCompression:
cmd_lockmode = AccessExclusiveLock; break;
case AT_AddConstraint: case AT_ReAddConstraint: /* becomes AT_AddConstraint */ case AT_ReAddDomainConstraint: /* becomes AT_AddConstraint */ if (IsA(cmd->def, Constraint))
{
Constraint *con = (Constraint *) cmd->def;
switch (con->contype)
{ case CONSTR_EXCLUSION: case CONSTR_PRIMARY: case CONSTR_UNIQUE:
/* *WeallowdefaultsonviewssothatINSERTintoaviewcanhave *default-ishbehavior.Thisworksbecausetherewriter *substitutesdefaultvaluesintoINSERTsbeforeitexpands *rules.
*/
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); /* No command-specific prep needed */
pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP; break; case AT_CookedColumnDefault: /* add a pre-cooked default */ /* This is currently used only in CREATE TABLE */ /* (so the permission check really isn't necessary) */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE); /* This command never recurses */
pass = AT_PASS_ADD_OTHERCONSTR; break; case AT_AddIdentity:
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
ATT_FOREIGN_TABLE); /* Set up recursion for phase 2; no other prep needed */ if (recurse)
cmd->recurse = true;
pass = AT_PASS_ADD_OTHERCONSTR; break; case AT_SetIdentity:
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
ATT_FOREIGN_TABLE); /* Set up recursion for phase 2; no other prep needed */ if (recurse)
cmd->recurse = true; /* This should run after AddIdentity, so do it in MISC pass */
pass = AT_PASS_MISC; break; case AT_DropIdentity:
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
ATT_FOREIGN_TABLE); /* Set up recursion for phase 2; no other prep needed */ if (recurse)
cmd->recurse = true;
pass = AT_PASS_DROP; break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE); /* Set up recursion for phase 2; no other prep needed */ if (recurse)
cmd->recurse = true;
pass = AT_PASS_DROP; break; case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE); /* Set up recursion for phase 2; no other prep needed */ if (recurse)
cmd->recurse = true;
pass = AT_PASS_COL_ATTRS; break; case AT_SetExpression: /* ALTER COLUMN SET EXPRESSION */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
pass = AT_PASS_SET_EXPRESSION; break; case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
ATPrepDropExpression(rel, cmd, recurse, recursing, lockmode);
pass = AT_PASS_DROP; break; case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW |
ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_SetOptions: /* ALTER COLUMN SET ( options ) */ case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE |
ATT_MATVIEW | ATT_FOREIGN_TABLE); /* This command never recurses */
pass = AT_PASS_MISC; break; case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE |
ATT_MATVIEW | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW); /* This command never recurses */ /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_DropColumn: /* DROP COLUMN */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE |
ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd,
lockmode, context); /* Recursion occurs during execution phase */
pass = AT_PASS_DROP; break; case AT_AddIndex: /* ADD INDEX */
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE); /* This command never recurses */ /* No command-specific prep needed */
pass = AT_PASS_ADD_INDEX; break; case AT_AddConstraint: /* ADD CONSTRAINT */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
ATPrepAddPrimaryKey(wqueue, rel, cmd, recurse, lockmode, context); if (recurse)
{ /* recurses at exec time; lock descendants and set flag */
(void) find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
cmd->recurse = true;
}
pass = AT_PASS_ADD_CONSTR; break; case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE); /* This command never recurses */ /* No command-specific prep needed */
pass = AT_PASS_ADD_INDEXCONSTR; break; case AT_DropConstraint: /* DROP CONSTRAINT */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
ATCheckPartitionsNotInUse(rel, lockmode); /* Other recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse)
cmd->recurse = true;
pass = AT_PASS_DROP; break; case AT_AlterColumnType: /* ALTER COLUMN TYPE */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE |
ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE); /* See comments for ATPrepAlterColumnType */
cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
AT_PASS_UNSET, context);
Assert(cmd != NULL); /* Performs own recursion */
ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
lockmode, context);
pass = AT_PASS_ALTER_TYPE; break; case AT_AlterColumnGenericOptions:
ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE); /* This command never recurses */ /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_ChangeOwner: /* ALTER OWNER */ /* This command never recurses */ /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_ClusterOn: /* CLUSTER ON */ case AT_DropCluster: /* SET WITHOUT CLUSTER */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW); /* These commands never recurse */ /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_SetLogged: /* SET LOGGED */ case AT_SetUnLogged: /* SET UNLOGGED */
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_SEQUENCE); if (tab->chgPersistence)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot change persistence setting twice")));
ATPrepChangePersistence(tab, rel, cmd->subtype == AT_SetLogged);
pass = AT_PASS_MISC; break; case AT_DropOids: /* SET WITHOUT OIDS */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
pass = AT_PASS_DROP; break; case AT_SetAccessMethod: /* SET ACCESS METHOD */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
/* check if another access method change was already requested */ if (tab->chgAccessMethod)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot have multiple SET ACCESS METHOD subcommands")));
ATPrepSetAccessMethod(tab, rel, cmd->name);
pass = AT_PASS_MISC; /* does not matter; no work in Phase 2 */ break; case AT_SetTableSpace: /* SET TABLESPACE */
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE |
ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX); /* This command never recurses */
ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
pass = AT_PASS_MISC; /* doesn't actually matter */ break; case AT_SetRelOptions: /* SET (...) */ case AT_ResetRelOptions: /* RESET (...) */ case AT_ReplaceRelOptions: /* reset them all, then set just these */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
ATT_MATVIEW | ATT_INDEX); /* This command never recurses */ /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_AddInherit: /* INHERIT */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE); /* This command never recurses */
ATPrepAddInherit(rel);
pass = AT_PASS_MISC; break; case AT_DropInherit: /* NO INHERIT */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE); /* This command never recurses */ /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_AlterConstraint: /* ALTER CONSTRAINT */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE); /* Recursion occurs during execution phase */ if (recurse)
cmd->recurse = true;
pass = AT_PASS_MISC; break; case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse)
cmd->recurse = true;
pass = AT_PASS_MISC; break; case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
pass = AT_PASS_MISC; /* This command never recurses */ /* No command-specific prep needed */ break; case AT_EnableTrig: /* ENABLE TRIGGER variants */ case AT_EnableAlwaysTrig: case AT_EnableReplicaTrig: case AT_EnableTrigAll: case AT_EnableTrigUser: case AT_DisableTrig: /* DISABLE TRIGGER variants */ case AT_DisableTrigAll: case AT_DisableTrigUser:
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE); /* Set up recursion for phase 2; no other prep needed */ if (recurse)
cmd->recurse = true;
pass = AT_PASS_MISC; break; case AT_EnableRule: /* ENABLE/DISABLE RULE variants */ case AT_EnableAlwaysRule: case AT_EnableReplicaRule: case AT_DisableRule: case AT_AddOf: /* OF */ case AT_DropOf: /* NOT OF */ case AT_EnableRowSecurity: case AT_DisableRowSecurity: case AT_ForceRowSecurity: case AT_NoForceRowSecurity:
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE); /* These commands never recurse */ /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_GenericOptions:
ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE); /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_AttachPartition:
ATSimplePermissions(cmd->subtype, rel,
ATT_PARTITIONED_TABLE | ATT_PARTITIONED_INDEX); /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_DetachPartition:
ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE); /* No command-specific prep needed */
pass = AT_PASS_MISC; break; case AT_DetachPartitionFinalize:
ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE); /* No command-specific prep needed */
pass = AT_PASS_MISC; break; default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
pass = AT_PASS_UNSET; /* keep compiler quiet */ break;
}
Assert(pass > AT_PASS_UNSET);
/* Add the subcommand to the appropriate list for phase 2 */
tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd);
}
/* Gin up an AlterTableStmt with just this subcommand and this table */
atstmt->relation =
makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
pstrdup(RelationGetRelationName(rel)),
-1);
atstmt->relation->inh = recurse;
atstmt->cmds = list_make1(cmd);
atstmt->objtype = OBJECT_TABLE; /* needn't be picky here */
atstmt->missing_ok = false;
/* *Weonlyneedtorewritethetableifatleastonecolumnneedsto *berecomputed,orwearechangingitspersistenceoraccessmethod. * *Therearetworeasonsforrequiringarewritewhenchanging *persistence:ononehand,weneedtoensurethatthebuffers *belongingtoeachofthetworelationsaremarkedwithorwithout *BM_PERMANENTproperly.Ontheotherhand,sincerewritingcreates *andassignsanewrelfilenumber,weautomaticallycreateordropan *initforkfortherelationasappropriate.
*/ if (tab->rewrite > 0 && tab->relkind != RELKIND_SEQUENCE)
{ /* Build a temporary relation and copy data */
Relation OldHeap;
Oid OIDNewHeap;
Oid NewAccessMethod;
Oid NewTableSpace; char persistence;
OldHeap = table_open(tab->relid, NoLock);
/* *Wedon'tsupportrewritingofsystemcatalogs;therearetoo *manycornercasesandtoolittlebenefit.Inparticularthis *iscertainlynotgoingtoworkformappedcatalogs.
*/ if (IsSystemRelation(OldHeap))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite system relation \"%s\"",
RelationGetRelationName(OldHeap))));
if (RelationIsUsedAsCatalogTable(OldHeap))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite table \"%s\" used as a catalog table",
RelationGetRelationName(OldHeap))));
/* *Don'tallowrewriteontemptablesofotherbackends...their *localbuffermanagerisnotgoingtocope.(Thisisredundant *withthecheckinCheckAlterTableIsSafe,butforsafetywe'll *checkheretoo.)
*/ if (RELATION_IS_OTHER_TEMP(OldHeap))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite temporary tables of other sessions")));
/* Finally, run any afterStmts that were queued up */
foreach(ltab, *wqueue)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
ListCell *lc;
switch (con->contype)
{ case CONSTR_CHECK: if (!ExecCheck(con->qualstate, econtext))
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" of relation \"%s\" is violated by some row",
con->name,
RelationGetRelationName(oldrel)),
errtableconstraint(oldrel, con->name))); break; case CONSTR_NOTNULL: case CONSTR_FOREIGN: /* Nothing to do here */ break; default:
elog(ERROR, "unrecognized constraint type: %d",
(int) con->contype);
}
}
if (partqualstate && !ExecCheck(partqualstate, econtext))
{ if (tab->validate_default)
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
RelationGetRelationName(oldrel)),
errtable(oldrel))); else
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("partition constraint of relation \"%s\" is violated by some row",
RelationGetRelationName(oldrel)),
errtable(oldrel)));
}
/* Write the tuple out to the new relation */ if (newrel)
table_tuple_insert(newrel, insertslot, mycid,
ti_options, bistate);
staticconstchar *
alter_table_type_to_string(AlterTableType cmdtype)
{ switch (cmdtype)
{ case AT_AddColumn: case AT_AddColumnToView: return"ADD COLUMN"; case AT_ColumnDefault: case AT_CookedColumnDefault: return"ALTER COLUMN ... SET DEFAULT"; case AT_DropNotNull: return"ALTER COLUMN ... DROP NOT NULL"; case AT_SetNotNull: return"ALTER COLUMN ... SET NOT NULL"; case AT_SetExpression: return"ALTER COLUMN ... SET EXPRESSION"; case AT_DropExpression: return"ALTER COLUMN ... DROP EXPRESSION"; case AT_SetStatistics: return"ALTER COLUMN ... SET STATISTICS"; case AT_SetOptions: return"ALTER COLUMN ... SET"; case AT_ResetOptions: return"ALTER COLUMN ... RESET"; case AT_SetStorage: return"ALTER COLUMN ... SET STORAGE"; case AT_SetCompression: return"ALTER COLUMN ... SET COMPRESSION"; case AT_DropColumn: return"DROP COLUMN"; case AT_AddIndex: case AT_ReAddIndex: return NULL; /* not real grammar */ case AT_AddConstraint: case AT_ReAddConstraint: case AT_ReAddDomainConstraint: case AT_AddIndexConstraint: return"ADD CONSTRAINT"; case AT_AlterConstraint: return"ALTER CONSTRAINT"; case AT_ValidateConstraint: return"VALIDATE CONSTRAINT"; case AT_DropConstraint: return"DROP CONSTRAINT"; case AT_ReAddComment: return NULL; /* not real grammar */ case AT_AlterColumnType: return"ALTER COLUMN ... SET DATA TYPE"; case AT_AlterColumnGenericOptions: return"ALTER COLUMN ... OPTIONS"; case AT_ChangeOwner: return"OWNER TO"; case AT_ClusterOn: return"CLUSTER ON"; case AT_DropCluster: return"SET WITHOUT CLUSTER"; case AT_SetAccessMethod: return"SET ACCESS METHOD"; case AT_SetLogged: return"SET LOGGED"; case AT_SetUnLogged: return"SET UNLOGGED"; case AT_DropOids: return"SET WITHOUT OIDS"; case AT_SetTableSpace: return"SET TABLESPACE"; case AT_SetRelOptions: return"SET"; case AT_ResetRelOptions: return"RESET"; case AT_ReplaceRelOptions: return NULL; /* not real grammar */ case AT_EnableTrig: return"ENABLE TRIGGER"; case AT_EnableAlwaysTrig: return"ENABLE ALWAYS TRIGGER"; case AT_EnableReplicaTrig: return"ENABLE REPLICA TRIGGER"; case AT_DisableTrig: return"DISABLE TRIGGER"; case AT_EnableTrigAll: return"ENABLE TRIGGER ALL"; case AT_DisableTrigAll: return"DISABLE TRIGGER ALL"; case AT_EnableTrigUser: return"ENABLE TRIGGER USER"; case AT_DisableTrigUser: return"DISABLE TRIGGER USER"; case AT_EnableRule: return"ENABLE RULE"; case AT_EnableAlwaysRule: return"ENABLE ALWAYS RULE"; case AT_EnableReplicaRule: return"ENABLE REPLICA RULE"; case AT_DisableRule: return"DISABLE RULE"; case AT_AddInherit: return"INHERIT"; case AT_DropInherit: return"NO INHERIT"; case AT_AddOf: return"OF"; case AT_DropOf: return"NOT OF"; case AT_ReplicaIdentity: return"REPLICA IDENTITY"; case AT_EnableRowSecurity: return"ENABLE ROW SECURITY"; case AT_DisableRowSecurity: return"DISABLE ROW SECURITY"; case AT_ForceRowSecurity: return"FORCE ROW SECURITY"; case AT_NoForceRowSecurity: return"NO FORCE ROW SECURITY"; case AT_GenericOptions: return"OPTIONS"; case AT_AttachPartition: return"ATTACH PARTITION"; case AT_DetachPartition: return"DETACH PARTITION"; case AT_DetachPartitionFinalize: return"DETACH PARTITION ... FINALIZE"; case AT_AddIdentity: return"ALTER COLUMN ... ADD IDENTITY"; case AT_SetIdentity: return"ALTER COLUMN ... SET"; case AT_DropIdentity: return"ALTER COLUMN ... DROP IDENTITY"; case AT_ReAddStatistics: return NULL; /* not real grammar */
}
return NULL;
}
/* *ATSimplePermissions * *-Ensurethatitisarelation(orpossiblyaview) *-Ensurethisuseristheowner *-Ensurethatitisnotasystemtable
*/ staticvoid
ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets)
{ int actual_target;
switch (rel->rd_rel->relkind)
{ case RELKIND_RELATION:
actual_target = ATT_TABLE; break; case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_PARTITIONED_TABLE; break; case RELKIND_VIEW:
actual_target = ATT_VIEW; break; case RELKIND_MATVIEW:
actual_target = ATT_MATVIEW; break; case RELKIND_INDEX:
actual_target = ATT_INDEX; break; case RELKIND_PARTITIONED_INDEX:
actual_target = ATT_PARTITIONED_INDEX; break; case RELKIND_COMPOSITE_TYPE:
actual_target = ATT_COMPOSITE_TYPE; break; case RELKIND_FOREIGN_TABLE:
actual_target = ATT_FOREIGN_TABLE; break; case RELKIND_SEQUENCE:
actual_target = ATT_SEQUENCE; break; default:
actual_target = 0; break;
}
if (action_str)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), /* translator: %s is a group of some SQL keywords */
errmsg("ALTER action %s cannot be performed on relation \"%s\"",
action_str, RelationGetRelationName(rel)),
errdetail_relkind_not_supported(rel->rd_rel->relkind))); else /* internal error? */
elog(ERROR, "invalid ALTER action attempted on relation \"%s\"",
RelationGetRelationName(rel));
}
if (!allowSystemTableMods && IsSystemRelation(rel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel))));
}
inh = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL); /* first element is the parent rel; must ignore it */
for_each_from(cell, inh, 1)
{
Relation childrel;
if (behavior == DROP_RESTRICT)
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot alter type \"%s\" because it is the type of a typed table", typeName),
errhint("Use ALTER ... CASCADE to alter the typed tables too."))); else
result = lappend_oid(result, classform->oid);
}
if (!typeOk)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("type %s is the row type of another table",
format_type_be(typ->oid)),
errdetail("A typed table must use a stand-alone composite type created with CREATE TYPE.")));
} else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("type %s is not a composite type",
format_type_be(typ->oid))));
}
/* since this function recurses, it could be driven to stack overflow */
check_stack_depth();
/* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing)
ATSimplePermissions((*cmd)->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
if (rel->rd_rel->relispartition && !recursing)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot add column to a partition")));
/* Does child already have a column by this name? */
tuple = SearchSysCacheCopyAttName(myrelid, colDef->colname); if (HeapTupleIsValid(tuple))
{
Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
Oid ctypeId;
int32 ctypmod;
Oid ccollid;
/* Child column must match on type, typmod, and collation */
typenameTypeIdAndMod(NULL, colDef->typeName, &ctypeId, &ctypmod); if (ctypeId != childatt->atttypid ||
ctypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(rel), colDef->colname)));
ccollid = GetColumnDefCollation(NULL, colDef, ctypeId); if (ccollid != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
errmsg("child table \"%s\" has different collation for column \"%s\"",
RelationGetRelationName(rel), colDef->colname),
errdetail("\"%s\" versus \"%s\"",
get_collation_name(ccollid),
get_collation_name(childatt->attcollation))));
/* Bump the existing child att's inhcount */ if (pg_add_s16_overflow(childatt->attinhcount, 1,
&childatt->attinhcount))
ereport(ERROR,
errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many inheritance parents"));
CatalogTupleUpdate(attrdesc, &tuple->t_self, tuple);
heap_freetuple(tuple);
/* Inform the user about the merge */
ereport(NOTICE,
(errmsg("merging definition of column \"%s\" for child \"%s\"",
colDef->colname, RelationGetRelationName(rel))));
table_close(attrdesc, RowExclusiveLock);
/* Make the child column change visible */
CommandCounterIncrement();
return InvalidObjectAddress;
}
}
/* skip if the name already exists and if_not_exists is true */ if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
{
table_close(attrdesc, RowExclusiveLock); return InvalidObjectAddress;
}
/* Determine the new attribute's number */
newattnum = relform->relnatts + 1; if (newattnum > MaxHeapAttributeNumber)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("tables can have at most %d columns",
MaxHeapAttributeNumber)));
/* *Propagatetochildrenasappropriate.UnlikemostotherALTER *routines,wehavetodothisonelevelofrecursionatatime;wecan't *usefind_all_inheritorstodoitinonepass.
*/
children =
find_inheritance_children(RelationGetRelid(rel), lockmode);
/* *Ifwearetoldnottorecurse,therehadbetternotbeanychild *tables;elsetheadditionwouldputthemoutofstep.
*/ if (children && !recurse)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column must be added to child tables too")));
/* Children should see column as singly inherited */ if (!recursing)
{
childcmd = copyObject(*cmd);
colDef = castNode(ColumnDef, childcmd->def);
colDef->inhcount = 1;
colDef->is_local = false;
} else
childcmd = *cmd; /* no need to copy again */
tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = attTup->attnum;
ObjectAddressSubSet(address, RelationRelationId,
RelationGetRelid(rel), attnum);
/* If the column is already nullable there's nothing to do. */ if (!attTup->attnotnull)
{
table_close(attr_rel, RowExclusiveLock); return InvalidObjectAddress;
}
/* Prevent them from altering a system attribute */ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
if (attTup->attidentity)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column \"%s\" of relation \"%s\" is an identity column",
colName, RelationGetRelationName(rel))));
parent_attnum = get_attnum(parentId, colName); if (TupleDescAttr(tupDesc, parent_attnum - 1)->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" is marked NOT NULL in parent table",
colName)));
table_close(parent, AccessShareLock);
}
/* *FindtheconstraintthatmakesthiscolumnNOTNULL,anddropit. *dropconstraint_internal()resetsattnotnull.
*/
conTup = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum); if (conTup == NULL)
elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
colName, RelationGetRelationName(rel));
/* The normal case: we have a pg_constraint row, remove it */
dropconstraint_internal(rel, conTup, DROP_RESTRICT, recurse, false, false, lockmode);
heap_freetuple(conTup);
/* Guard against stack overflow due to overly deep inheritance tree. */
check_stack_depth();
/* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing)
{
ATSimplePermissions(AT_AddConstraint, rel,
ATT_PARTITIONED_TABLE | ATT_TABLE | ATT_FOREIGN_TABLE);
Assert(conName != NULL);
}
attnum = get_attnum(RelationGetRelid(rel), colName); if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
/* Prevent them from altering a system attribute */ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
/* See if there's already a constraint */
tuple = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum); if (HeapTupleIsValid(tuple))
{
Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple); bool changed = false;
/* *Don'tletaNOINHERITconstraintbechangedintoinherit.
*/ if (conForm->connoinherit && recurse)
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
NameStr(conForm->conname),
RelationGetRelationName(rel)));
if (changed) return address; else return InvalidObjectAddress;
}
/* *Ifwe'reaskednottorecurse,andchildrenexist,raiseanerrorfor *partitionedtables.Forinheritance,weactasifNOINHERIThadbeen *specified.
*/ if (!recurse &&
find_inheritance_children(RelationGetRelid(rel),
NoLock) != NIL)
{ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraint must be added to child tables too"),
errhint("Do not specify the ONLY keyword.")); else
is_no_inherit = true;
}
if (ConstraintImpliedByRelConstraint(rel, list_make1(nnulltest), NIL))
{
ereport(DEBUG1,
(errmsg_internal("existing constraints on column \"%s.%s\" are sufficient to prove that it does not contain nulls",
RelationGetRelationName(rel), NameStr(attr->attname)))); returntrue;
}
/* *getthenumberoftheattribute
*/
attnum = get_attnum(RelationGetRelid(rel), colName); if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
/* Prevent them from altering a system attribute */ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
if (TupleDescAttr(tupdesc, attnum - 1)->attidentity)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column \"%s\" of relation \"%s\" is an identity column",
colName, RelationGetRelationName(rel)), /* translator: %s is an SQL ALTER command */
newDefault ? 0 : errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY")));
if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column \"%s\" of relation \"%s\" is a generated column",
colName, RelationGetRelationName(rel)),
newDefault ? /* translator: %s is an SQL ALTER command */
errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... SET EXPRESSION") :
(TupleDescAttr(tupdesc, attnum - 1)->attgenerated == ATTRIBUTE_GENERATED_STORED ?
errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION") : 0)));
ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); if (ispartitioned && !recurse)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add identity to a column of only the partitioned table"),
errhint("Do not specify the ONLY keyword.")));
if (rel->rd_rel->relispartition && !recursing)
ereport(ERROR,
errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add identity to a column of a partition"));
tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = attTup->attnum;
/* Can't alter a system attribute */ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
/* *CreatingacolumnasidentityimpliesNOTNULL,soaddingtheidentity *toanexistingcolumnthatisnotNOTNULLwouldcreateastatethat *cannotbereproducedwithoutcontortions.
*/ if (!attTup->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added",
colName, RelationGetRelationName(rel))));
contup = findNotNullConstraintAttnum(RelationGetRelid(rel),
attnum); if (!HeapTupleIsValid(contup))
elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
colName, RelationGetRelationName(rel));
conForm = (Form_pg_constraint) GETSTRUCT(contup); if (!conForm->convalidated)
ereport(ERROR,
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("incompatible NOT VALID constraint \"%s\" on relation \"%s\"",
NameStr(conForm->conname), RelationGetRelationName(rel)),
errhint("You might need to validate it using %s.", "ALTER TABLE ... VALIDATE CONSTRAINT"));
}
if (attTup->attidentity)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("column \"%s\" of relation \"%s\" is already an identity column",
colName, RelationGetRelationName(rel))));
if (attTup->atthasdef)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("column \"%s\" of relation \"%s\" already has a default value",
colName, RelationGetRelationName(rel))));
ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); if (ispartitioned && !recurse)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change identity column of only the partitioned table"),
errhint("Do not specify the ONLY keyword.")));
if (rel->rd_rel->relispartition && !recursing)
ereport(ERROR,
errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change identity column of a partition"));
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
if (!attTup->attidentity)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("column \"%s\" of relation \"%s\" is not an identity column",
colName, RelationGetRelationName(rel))));
if (generatedEl)
{
attTup->attidentity = defGetInt32(generatedEl);
CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); if (ispartitioned && !recurse)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot drop identity from a column of only the partitioned table"),
errhint("Do not specify the ONLY keyword.")));
if (rel->rd_rel->relispartition && !recursing)
ereport(ERROR,
errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot drop identity from a column of a partition"));
attrelation = table_open(AttributeRelationId, RowExclusiveLock);
tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
if (!attTup->attidentity)
{ if (!missing_ok)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("column \"%s\" of relation \"%s\" is not an identity column",
colName, RelationGetRelationName(rel)))); else
{
ereport(NOTICE,
(errmsg("column \"%s\" of relation \"%s\" is not an identity column, skipping",
colName, RelationGetRelationName(rel))));
heap_freetuple(tuple);
table_close(attrelation, RowExclusiveLock); return InvalidObjectAddress;
}
}
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = attTup->attnum; if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
attgenerated = attTup->attgenerated; if (!attgenerated)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("column \"%s\" of relation \"%s\" is not a generated column",
colName, RelationGetRelationName(rel))));
/* *TODO:Thiscouldbedone,justneedtorecheckanyconstraints *afterwards.
*/ if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
rel->rd_att->constr && rel->rd_att->constr->num_check > 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns in tables with check constraints"),
errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
colName, RelationGetRelationName(rel))));
if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL && attTup->attnotnull)
tab->verify_new_notnull = true;
/* *Weneedtopreventthisbecauseachangeofexpressioncouldaffecta *rowfilterandinjectexpressionsthatarenotpermittedinarow *filter.XXXWecouldtrytohaveamoreprecisechecktocatchonly *publicationswithrowfilters,orevenre-verifytherowfilter *expressions.
*/ if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
GetRelationPublications(RelationGetRelid(rel)) != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns in tables that are part of a publication"),
errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
colName, RelationGetRelationName(rel))));
/* Prepare to store the new expression, in the catalogs */
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
rawEnt->attnum = attnum;
rawEnt->raw_default = newExpr;
rawEnt->generated = attgenerated;
/* Store the generated expression */
AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true, false, NULL);
/* Make above new expression visible */
CommandCounterIncrement();
if (rewrite)
{ /* Prepare for table rewrite */
defval = (Expr *) build_column_default(rel, attnum);
/* *ALTERTABLEALTERCOLUMNDROPEXPRESSION
*/ staticvoid
ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode)
{ /* *RejectONLYiftherearechildtables.Wecouldimplementthis,butit *isabitcomplicated.GENERATEDclausesmustbeattachedtothecolumn *definitionandcannotbeaddedlaterlikeDEFAULT,soifachildtable *hasagenerationexpressionthattheparentdoesnothave,thechild *columnwillnecessarilybeanattislocalcolumn.SotoimplementONLY *here,we'dneedextracodetoupdateattislocalofthedirectchild *tables,somewhatsimilartohowDROPCOLUMNdoesit,sothatthe *resultingstatecanbeproperlydumpedandrestored.
*/ if (!recurse &&
find_inheritance_children(RelationGetRelid(rel), lockmode))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ALTER TABLE / DROP EXPRESSION must be applied to child tables too")));
/* *Cannotdropgenerationexpressionfrominheritedcolumns.
*/ if (!recursing)
{
HeapTuple tuple;
Form_pg_attribute attTup;
tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), cmd->name); if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
cmd->name, RelationGetRelationName(rel))));
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
if (attTup->attinhcount > 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot drop generation expression from inherited column")));
}
}
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
/* *TODO:Thiscouldbedone,butitwouldneedatablerewriteto *materializethegeneratedvalues.Notethatforthetimebeing,we *stillerrorwithmissing_ok,sothatwedon'tsilentlyleavethecolumn *asgenerated.
*/ if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ALTER TABLE / DROP EXPRESSION is not supported for virtual generated columns"),
errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
colName, RelationGetRelationName(rel))));
if (!attTup->attgenerated)
{ if (!missing_ok)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("column \"%s\" of relation \"%s\" is not a generated column",
colName, RelationGetRelationName(rel)))); else
{
ereport(NOTICE,
(errmsg("column \"%s\" of relation \"%s\" is not a generated column, skipping",
colName, RelationGetRelationName(rel))));
heap_freetuple(tuple);
table_close(attrelation, RowExclusiveLock); return InvalidObjectAddress;
}
}
/* *Weallowreferencingcolumnsbynumbersonlyforindexes,sincetable *columnnumberscouldcontaingapsifcolumnsarelaterdropped.
*/ if (rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
!colName)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot refer to non-index column by number")));
/* -1 was used in previous versions for the default setting */ if (newValue && intVal(newValue) != -1)
{
newtarget = intVal(newValue);
newtarget_default = false;
} else
newtarget_default = true;
if (!newtarget_default)
{ /* *Limittargettoasanerange
*/ if (newtarget < 0)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("statistics target %d is too low",
newtarget)));
} elseif (newtarget > MAX_STATISTICS_TARGET)
{
newtarget = MAX_STATISTICS_TARGET;
ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("lowering statistics target to %d",
newtarget)));
}
}
if (colName)
{
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
} else
{
tuple = SearchSysCacheAttNum(RelationGetRelid(rel), colNum);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column number %d of relation \"%s\" does not exist",
colNum, RelationGetRelationName(rel))));
}
attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = attrtuple->attnum; if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
/* *PreventthisaslongastheANALYZEcodeskipsvirtualgenerated *columns.
*/ if (attrtuple->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter statistics on virtual generated column \"%s\"",
colName)));
if (rel->rd_rel->relkind == RELKIND_INDEX ||
rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
{ if (attnum > rel->rd_index->indnkeyatts)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter statistics on included column \"%s\" of index \"%s\"",
NameStr(attrtuple->attname), RelationGetRelationName(rel)))); elseif (rel->rd_index->indkey.values[attnum - 1] != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter statistics on non-expression column \"%s\" of index \"%s\"",
NameStr(attrtuple->attname), RelationGetRelationName(rel)),
errhint("Alter statistics on table column instead.")));
}
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = attrtuple->attnum; if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = attrtuple->attnum; if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
/* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing)
ATSimplePermissions(AT_DropColumn, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
/* Initialize addrs on the first invocation */
Assert(!recursing || addrs != NULL);
/* since this function recurses, it could be driven to stack overflow */
check_stack_depth();
if (!recursing)
addrs = new_object_addresses();
/* *getthenumberoftheattribute
*/
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple))
{ if (!missing_ok)
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
} else
{
ereport(NOTICE,
(errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
colName, RelationGetRelationName(rel)))); return InvalidObjectAddress;
}
}
targetatt = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = targetatt->attnum;
/* Can't drop a system attribute */ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot drop system column \"%s\"",
colName)));
/* *Don'tdropinheritedcolumns,unlessrecursing(presumablyfromadrop *oftheparentcolumn)
*/ if (targetatt->attinhcount > 0 && !recursing)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot drop inherited column \"%s\"",
colName)));
/* *Don'tdropcolumnsusedinthepartitionkey,either.(Ifweletthis *gothrough,thekeycolumn'sdependencieswouldcauseacascadeddrop *ofthewholetable,whichissurelynotwhattheuserexpected.)
*/ if (has_partition_attrs(rel,
bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
&is_expr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot drop column \"%s\" because it is part of the partition key of relation \"%s\"",
colName, RelationGetRelationName(rel))));
ReleaseSysCache(tuple);
/* *Propagatetochildrenasappropriate.UnlikemostotherALTER *routines,wehavetodothisonelevelofrecursionatatime;wecan't *usefind_all_inheritorstodoitinonepass.
*/
children =
find_inheritance_children(RelationGetRelid(rel), lockmode);
if (children)
{
Relation attr_rel;
ListCell *child;
/* *Incaseofapartitionedtable,thecolumnmustbedroppedfromthe *partitionsaswell.
*/ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot drop column from only the partitioned table when partitions exist"),
errhint("Do not specify the ONLY keyword.")));
if (!recursing)
{ /* Recursion has ended, drop everything that was collected */
performMultipleDeletions(addrs, behavior, 0);
free_object_addresses(addrs);
}
pkconstr = castNode(Constraint, cmd->def); if (pkconstr->contype != CONSTR_PRIMARY) return;
/* Verify that columns are not-null, or request that they be made so */
foreach_node(String, column, pkconstr->keys)
{
AlterTableCmd *newcmd;
Constraint *nnconstr;
HeapTuple tuple;
/* All good with this one; don't request another */
heap_freetuple(tuple); continue;
} elseif (!recurse)
{ /* *Noconstraintonthiscolumn.Askednottorecurse,wewon't *createonehere,butverifythatallchildrenhaveone.
*/ if (!got_children)
{
children = find_inheritance_children(RelationGetRelid(rel),
lockmode); /* only search for children on the first time through */
got_children = true;
}
tup = findNotNullConstraint(childrelid, strVal(column)); if (!tup)
ereport(ERROR,
errmsg("column \"%s\" of table \"%s\" is not marked NOT NULL",
strVal(column), get_rel_name(childrelid))); /* verify it's good enough */
verifyNotNullPKCompatible(tup, strVal(column));
}
}
/* This column is not already not-null, so add it to the queue */
nnconstr = makeNotNullConstraint(column);
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_AddConstraint; /* note we force recurse=true here; see above */
newcmd->recurse = true;
newcmd->def = (Node *) nnconstr;
if (conForm->contype != CONSTRAINT_NOTNULL)
elog(ERROR, "constraint %u is not a not-null constraint", conForm->oid);
/* a NO INHERIT constraint is no good */ if (conForm->connoinherit)
ereport(ERROR,
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot create primary key on column \"%s\"", colname), /*- translator: fourth %s is a constraint characteristic such as NOT VALID */
errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
NameStr(conForm->conname), colname,
get_rel_name(conForm->conrelid), "NO INHERIT"),
errhint("You might need to make the existing constraint inheritable using %s.", "ALTER TABLE ... ALTER CONSTRAINT ... INHERIT"));
/* an unvalidated constraint is no good */ if (!conForm->convalidated)
ereport(ERROR,
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot create primary key on column \"%s\"", colname), /*- translator: fourth %s is a constraint characteristic such as NOT VALID */
errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
NameStr(conForm->conname), colname,
get_rel_name(conForm->conrelid), "NOT VALID"),
errhint("You might need to validate it using %s.", "ALTER TABLE ... VALIDATE CONSTRAINT"));
}
/* The IndexStmt has already been through transformIndexStmt */
Assert(stmt->transformed);
/* suppress schema rights check when rebuilding existing index */
check_rights = !is_rebuild; /* skip index build if phase 3 will do it or we're reusing an old one */
skip_build = tab->rewrite > 0 || RelFileNumberIsValid(stmt->oldNumber); /* suppress notices when rebuilding existing index */
quiet = is_rebuild;
address = DefineIndex(RelationGetRelid(rel),
stmt,
InvalidOid, /* no predefined OID */
InvalidOid, /* no parent index */
InvalidOid, /* no parent constraint */
-1, /* total_parts unknown */ true, /* is_alter_table */
check_rights, false, /* check_not_in_use - we did it already */
skip_build,
quiet);
/* *Doingthisonpartitionedtablesisnotasimplefeaturetoimplement, *solet'spuntfornow.
*/ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables")));
/* this should have been checked at parse time */ if (!indexInfo->ii_Unique)
elog(ERROR, "index \"%s\" is not unique", indexName);
/* *Determinenametoassigntoconstraint.Werequireaconstraintto *havethesamenameastheunderlyingindex;therefore,usetheindex's *existingnameasthedefaultconstraintname,andiftheuser *explicitlygivessomeothernamefortheconstraint,renametheindex *tomatch.
*/
constraintName = stmt->idxname; if (constraintName == NULL)
constraintName = indexName; elseif (strcmp(constraintName, indexName) != 0)
{
ereport(NOTICE,
(errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
indexName, constraintName)));
RenameRelationInternal(index_oid, constraintName, false, true);
}
/* Extra checks needed if making primary key */ if (stmt->primary)
index_check_primary_key(rel, indexInfo, true, stmt);
/* Note we currently don't support EXCLUSION constraints here */ if (stmt->primary)
constraintType = CONSTRAINT_PRIMARY; else
constraintType = CONSTRAINT_UNIQUE;
/* Guard against stack overflow due to overly deep inheritance tree. */
check_stack_depth();
/* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing)
ATSimplePermissions(AT_AddConstraint, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
/* *IfaddingaNOINHERITconstraint,noneedtofindourchildren.
*/ if (constr->is_no_inherit) return address;
/* *Propagatetochildrenasappropriate.UnlikemostotherALTER *routines,wehavetodothisonelevelofrecursionatatime;wecan't *usefind_all_inheritorstodoitinonepass.
*/
children =
find_inheritance_children(RelationGetRelid(rel), lockmode);
/* *CheckifONLYwasspecifiedwithALTERTABLE.Ifso,allowthe *constraintcreationonlyiftherearenochildrencurrently.Errorout *otherwise.
*/ if (!recurse && children != NIL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraint must be added to child tables too")));
/* *Validitychecks(permissioncheckswaittillwehavethecolumn *numbers)
*/ if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot use ONLY for foreign key on partitioned table \"%s\" referencing relation \"%s\"",
RelationGetRelationName(rel),
RelationGetRelationName(pkrel)));
if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("referenced relation \"%s\" is not a table",
RelationGetRelationName(pkrel))));
if (!allowSystemTableMods && IsSystemRelation(pkrel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(pkrel))));
/* *Referencesfrompermanentorunloggedtablestotemptables,andfrom *permanenttablestounloggedtables,aredisallowedbecausethe *referenceddatacanvanishoutfromunderus.Referencesfromtemp *tablestoanyothertabletypearealsodisallowed,becauseother *backendsmightneedtoruntheRItriggersonthepermtable,butthey *can'treliablyseetuplesinthelocalbuffersofotherbackends.
*/ switch (rel->rd_rel->relpersistence)
{ case RELPERSISTENCE_PERMANENT: if (!RelationIsPermanent(pkrel))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraints on permanent tables may reference only permanent tables"))); break; case RELPERSISTENCE_UNLOGGED: if (!RelationIsPermanent(pkrel)
&& pkrel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraints on unlogged tables may reference only permanent or unlogged tables"))); break; case RELPERSISTENCE_TEMP: if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraints on temporary tables may reference only temporary tables"))); if (!pkrel->rd_islocaltemp || !rel->rd_islocaltemp)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraints on temporary tables must involve temporary tables of this session"))); break;
}
/* *Lookupthereferencingattributestomakesuretheyexist,andrecord *theirattnumsandtypeandcollationOIDs.
*/
numfks = transformColumnNameList(RelationGetRelid(rel),
fkconstraint->fk_attrs,
fkattnum, fktypoid, fkcolloid);
with_period = fkconstraint->fk_with_period || fkconstraint->pk_with_period; if (with_period && !fkconstraint->fk_with_period)
ereport(ERROR,
errcode(ERRCODE_INVALID_FOREIGN_KEY),
errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));
/* If the primary key uses WITHOUT OVERLAPS, the fk must use PERIOD */ if (pk_has_without_overlaps && !fkconstraint->fk_with_period)
ereport(ERROR,
errcode(ERRCODE_INVALID_FOREIGN_KEY),
errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));
} else
{
numpks = transformColumnNameList(RelationGetRelid(pkrel),
fkconstraint->pk_attrs,
pkattnum, pktypoid, pkcolloid);
/* Since we got pk_attrs, one should be a period. */ if (with_period && !fkconstraint->pk_with_period)
ereport(ERROR,
errcode(ERRCODE_INVALID_FOREIGN_KEY),
errmsg("foreign key uses PERIOD on the referencing table but not the referenced table"));
/* Look for an index matching the column list */
indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
with_period, opclasses, &pk_has_without_overlaps);
}
/* *IfthereferencedprimarykeyhasWITHOUTOVERLAPS,theforeignkey *mustusePERIOD.
*/ if (pk_has_without_overlaps && !with_period)
ereport(ERROR,
errcode(ERRCODE_INVALID_FOREIGN_KEY),
errmsg("foreign key must use PERIOD when referencing a primary key using WITHOUT OVERLAPS"));
for (i = 0; i < numpks; i++)
{
Oid pktype = pktypoid[i];
Oid fktype = fktypoid[i];
Oid fktyped;
Oid pkcoll = pkcolloid[i];
Oid fkcoll = fkcolloid[i];
HeapTuple cla_ht;
Form_pg_opclass cla_tup;
Oid amid;
Oid opfamily;
Oid opcintype; bool for_overlaps;
CompareType cmptype;
Oid pfeqop;
Oid ppeqop;
Oid ffeqop;
int16 eqstrategy;
Oid pfeqop_right;
/* We need several fields out of the pg_opclass entry */
cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i])); if (!HeapTupleIsValid(cla_ht))
elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
amid = cla_tup->opcmethod;
opfamily = cla_tup->opcfamily;
opcintype = cla_tup->opcintype;
ReleaseSysCache(cla_ht);
/* *GetstrategynumberfromindexAM. * *Foranormalforeign-keyconstraint,thisshouldnotfail,sincewe *alreadycheckedthattheindexisuniqueandshouldthereforehave *appropriateequaloperators.Foraperiodforeignkey,thiscould *failifweselectedanon-matchingexclusionconstraintearlier. *(XXXMaybeweshoulddotheselookupsearliersowedon'tendup *doingthat.)
*/
for_overlaps = with_period && i == numpks - 1;
cmptype = for_overlaps ? COMPARE_OVERLAP : COMPARE_EQ;
eqstrategy = IndexAmTranslateCompareType(cmptype, amid, opfamily, true); if (eqstrategy == InvalidStrategy)
ereport(ERROR,
errcode(ERRCODE_UNDEFINED_OBJECT),
for_overlaps
? errmsg("could not identify an overlaps operator for foreign key")
: errmsg("could not identify an equality operator for foreign key"),
errdetail("Could not translate compare type %d for operator family \"%s\" of access method \"%s\".",
cmptype, get_opfamily_name(opfamily, false), get_am_name(amid)));
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("foreign key constraint \"%s\" cannot be implemented",
fkconstraint->conname),
errdetail("Key columns \"%s\" of the referencing table and \"%s\" of the referenced table " "are of incompatible types: %s and %s.",
strVal(list_nth(fkconstraint->fk_attrs, i)),
strVal(list_nth(fkconstraint->pk_attrs, i)),
format_type_be(fktype),
format_type_be(pktype))));
/* *Thisshouldn'tbepossible,butbetterchecktomakesurewehavea *consistentstateforthecheckbelow.
*/ if ((OidIsValid(pkcoll) && !OidIsValid(fkcoll)) || (!OidIsValid(pkcoll) && OidIsValid(fkcoll)))
elog(ERROR, "key columns are not both collatable");
if (OidIsValid(pkcoll) && OidIsValid(fkcoll))
{ bool pkcolldet; bool fkcolldet;
/* *SQLrequiresthatbothcollationsarethesame.Thisis *becauseweneedaconsistentnotionofequalityonboth *columns.Werelaxthisbyallowingdifferentcollationsif *theyarebothdeterministic.(Thisisalsoforbackward *compatibility,becausePostgreSQLhasalwaysallowedthis.)
*/ if ((!pkcolldet || !fkcolldet) && pkcoll != fkcoll)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
errmsg("foreign key constraint \"%s\" cannot be implemented", fkconstraint->conname),
errdetail("Key columns \"%s\" of the referencing table and \"%s\" of the referenced table " "have incompatible collations: \"%s\" and \"%s\". " "If either collation is nondeterministic, then both collations have to be the same.",
strVal(list_nth(fkconstraint->fk_attrs, i)),
strVal(list_nth(fkconstraint->pk_attrs, i)),
get_collation_name(fkcoll),
get_collation_name(pkcoll))));
}
if (old_check_ok)
{ /* *Whenapfeqopchanges,revalidatetheconstraint.Wecould *permitintra-opfamilychanges,butthataddssubtlecomplexity *withoutanyconcretebenefitforcoretypes.Weneednot *assessppeqoporffeqop,whichRI_Initial_Check()doesnotuse.
*/
old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
old_pfeqop_item);
} if (old_check_ok)
{
Oid old_fktype;
Oid new_fktype;
CoercionPathType old_pathtype;
CoercionPathType new_pathtype;
Oid old_castfunc;
Oid new_castfunc;
Oid old_fkcoll;
Oid new_fkcoll;
Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
fkattnum[i] - 1);
/* *ForFKswithPERIODweneedadditionaloperatorstocheckwhetherthe *referencingrow'srangeiscontainedbytheaggregatedrangesofthe *referencedrow(s).Forrangetypesandmultirangetypesthisis *fk.periodatt<@range_agg(pk.periodatt).Thosearetheonlytypeswe *supportfornow.FKswilllooktheseupat"runtime",butweshould *makesurethelookupworkshere,evenifwedon'tusethevalues.
*/ if (with_period)
{
Oid periodoperoid;
Oid aggedperiodoperoid;
Oid intersectoperoid;
/* 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,
with_period);
/* Lastly create the check triggers at the referencing side and recurse */
addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
indexOid,
address.objectId,
numfks,
pkattnum,
fkattnum,
pfeqoperators,
ppeqoperators,
ffeqoperators,
numfkdelsetcols,
fkdelsetcols,
old_check_ok,
lockmode,
InvalidOid, InvalidOid,
with_period);
/* *validateFkOnDeleteSetColumns *VerifiesthatcolumnsusedinONDELETESETNULL/DEFAULT(...) *columnlistsarevalid. * *Ifthereareduplicatesinthefksetcolsattnums[]array,thissilently *removesthedups.Thenewcountofnumfksetcolsisreturned.
*/ staticint
validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums, 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)
{
seen = true; break;
}
}
if (!seen)
{ char *col = strVal(list_nth(fksetcols, i));
ereport(ERROR,
(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;
}
/* *addFkConstraint *Installpg_constraintentriestoimplementaforeignkeyconstraint. *CallermustseparatelyinvokeaddFkRecurseReferencedand *addFkRecurseReferencing,asappropriate,toinstallpg_triggerentries *and(forpartitionedtables)recursetopartitions. * *fkside:thesideoftheFK(orboth)tocreate.Callershould *calladdFkRecurseReferencedifthisisaddFkReferencedSide, *addFkRecurseReferencingifit'saddFkReferencingSide,orbothifit's *addFkBothSides. *constraintname:thebasenamefortheconstraintbeingadded, *copiedtofkconstraint->connameifthelatterisnotset *fkconstraint:theconstraintbeingadded *rel:therootreferencingrelation *pkrel:thereferencedrelation;mightbeapartition,ifrecursing *indexOid:theOIDoftheindex(onpkrel)implementingthisconstraint *parentConstr:theOIDofaparentconstraint;InvalidOidifthisisa *top-levelconstraint *numfks:thenumberofcolumnsintheforeignkey *pkattnum:theattnumarrayofreferencedattributes *fkattnum:theattnumarrayofreferencingattributes *pf/pp/ffeqoperators:OIDarrayofoperatorsbetweencolumns *numfkdelsetcols:thenumberofcolumnsintheONDELETESETNULL/DEFAULT *(...)clause *fkdelsetcols:theattnumarrayofthecolumnsintheONDELETESET *NULL/DEFAULTclause *with_period:trueifthisisatemporalFK
*/ 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, bool with_period)
{
ObjectAddress address;
Oid constrOid; char *conname; bool conislocal;
int16 coninhcount; bool connoinherit;
/* *Verifyrelkindforeachreferencedpartition.Atthetoplevel,this *isredundantwithapreviouscheck,butweneeditwhenrecursing.
*/ if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("referenced relation \"%s\" is not a table",
RelationGetRelationName(pkrel))));
/* 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));
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("foreign key constraints are not supported on foreign tables")));
/* *Addthenewforeignkeyconstraintpointingtothenewpartition. *Becausethisnewpartitionappearsinthereferencedsideofthe *constraint,wedon'tneedtosetupforPhase3check.
*/
partIndexId = index_get_partition(partitionRel, indexOid); if (!OidIsValid(partIndexId))
elog(ERROR, "index for %u not found in partition %s",
indexOid, RelationGetRelationName(partitionRel));
/* obtain a list of constraints that we need to clone */
foreach(cell, RelationGetFKeyList(parentRel))
{
ForeignKeyCacheInfo *fk = lfirst(cell);
/* *Refusetoattachatableaspartitionthatthispartitionedtable *alreadyhasaforeignkeyto.Thisisn'tusefulschema,whichis *provenbythefactthattherehavebeennousercomplaintsthat *it'salreadyimpossibletoachievethisintheoppositedirection, *i.e.,creatingaforeignkeythatreferencesapartition.This *restrictionallowsustododgesomecomplexitiesaround *pg_constraintandpg_triggerrowcreationsthatwouldbeneeded *duringATTACH/DETACHforthiskindofrelationship.
*/ 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);
}
/* *Silentlydonothingifthere'snothingtodo.Inparticular,this *avoidsthrowingaspuriouserrorforforeigntables.
*/ if (clone == NIL) return;
if (partRel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("foreign key constraints are not supported on foreign tables")));
/* Use the same lock as for AT_ValidateConstraint */
QueueFKConstraintValidation(wqueue, conrel, partition, confrelid,
partcontup, ShareUpdateExclusiveLock);
ReleaseSysCache(partcontup);
table_close(conrel, RowExclusiveLock);
}
}
/* Invalid if trigger is not for a referential integrity constraint */ if (!OidIsValid(trgform->tgconstrrelid)) continue; if (OidIsValid(conrelid) && trgform->tgconstrrelid != conrelid) continue; if (OidIsValid(confrelid) && trgform->tgrelid != confrelid) continue;
/* *Theconstraintisoriginallysetuptocontainthistriggerasan *implementationobject,sothere'sadependencyrecordthatlinks *thetwo;however,sincethetriggerisnolongerneeded,weremove *thedependencylinkinordertobeabletodropthetriggerwhile *keepingtheconstraintintact.
*/
deleteDependencyRecordsFor(TriggerRelationId,
trgform->oid, false); /* make dependency deletion visible to performDeletion */
CommandCounterIncrement();
ObjectAddressSet(trigger, TriggerRelationId,
trgform->oid);
performDeletion(&trigger, DROP_RESTRICT, 0); /* make trigger drop visible, in case the loop iterates */
CommandCounterIncrement();
}
systable_endscan(scan);
}
/* *GetForeignKeyActionTriggers *Returnsdeleteandupdate"action"triggersofthegivenrelation *belongingtothegivenconstraint
*/ staticvoid
GetForeignKeyActionTriggers(Relation trigrel,
Oid conoid, Oid confrelid, Oid conrelid,
Oid *deleteTriggerOid,
Oid *updateTriggerOid)
{
ScanKeyData key;
SysScanDesc scan;
HeapTuple trigtup;
if (trgform->tgconstrrelid != conrelid) continue; if (trgform->tgrelid != confrelid) continue; /* Only ever look at "action" triggers on the PK side. */ if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_PK) continue; if (TRIGGER_FOR_DELETE(trgform->tgtype))
{
Assert(*deleteTriggerOid == InvalidOid);
*deleteTriggerOid = trgform->oid;
} elseif (TRIGGER_FOR_UPDATE(trgform->tgtype))
{
Assert(*updateTriggerOid == InvalidOid);
*updateTriggerOid = trgform->oid;
} #ifndef USE_ASSERT_CHECKING /* In an assert-enabled build, continue looking to find duplicates */ if (OidIsValid(*deleteTriggerOid) && OidIsValid(*updateTriggerOid)) break; #endif
}
if (!OidIsValid(*deleteTriggerOid))
elog(ERROR, "could not find ON DELETE action trigger of foreign key constraint %u",
conoid); if (!OidIsValid(*updateTriggerOid))
elog(ERROR, "could not find ON UPDATE action trigger of foreign key constraint %u",
conoid);
systable_endscan(scan);
}
/* *GetForeignKeyCheckTriggers *Returnsinsertandupdate"check"triggersofthegivenrelation *belongingtothegivenconstraint
*/ staticvoid
GetForeignKeyCheckTriggers(Relation trigrel,
Oid conoid, Oid confrelid, Oid conrelid,
Oid *insertTriggerOid,
Oid *updateTriggerOid)
{
ScanKeyData key;
SysScanDesc scan;
HeapTuple trigtup;
if (trgform->tgconstrrelid != confrelid) continue; if (trgform->tgrelid != conrelid) continue; /* Only ever look at "check" triggers on the FK side. */ if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_FK) continue; if (TRIGGER_FOR_INSERT(trgform->tgtype))
{
Assert(*insertTriggerOid == InvalidOid);
*insertTriggerOid = trgform->oid;
} elseif (TRIGGER_FOR_UPDATE(trgform->tgtype))
{
Assert(*updateTriggerOid == InvalidOid);
*updateTriggerOid = trgform->oid;
} #ifndef USE_ASSERT_CHECKING /* In an assert-enabled build, continue looking to find duplicates. */ if (OidIsValid(*insertTriggerOid) && OidIsValid(*updateTriggerOid)) break; #endif
}
if (!OidIsValid(*insertTriggerOid))
elog(ERROR, "could not find ON INSERT check triggers of foreign key constraint %u",
conoid); if (!OidIsValid(*updateTriggerOid))
elog(ERROR, "could not find ON UPDATE check triggers of foreign key constraint %u",
conoid);
/* *DisallowalteringONLYapartitionedtable,asitwouldmakenosense. *Thisisokayforlegacyinheritance.
*/ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
ereport(ERROR,
errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraint must be altered in child tables too"),
errhint("Do not specify the ONLY keyword."));
/* There can be at most one matching row */ if (!HeapTupleIsValid(contuple = systable_getnext(scan)))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("constraint \"%s\" of relation \"%s\" does not exist",
cmdcon->conname, RelationGetRelationName(rel))));
currcon = (Form_pg_constraint) GETSTRUCT(contuple); if (cmdcon->alterDeferrability && currcon->contype != CONSTRAINT_FOREIGN)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
cmdcon->conname, RelationGetRelationName(rel)))); if (cmdcon->alterEnforceability && currcon->contype != CONSTRAINT_FOREIGN)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
cmdcon->conname, RelationGetRelationName(rel)))); if (cmdcon->alterInheritability &&
currcon->contype != CONSTRAINT_NOTNULL)
ereport(ERROR,
errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint \"%s\" of relation \"%s\" is not a not-null constraint",
cmdcon->conname, RelationGetRelationName(rel)));
/* Refuse to modify inheritability of inherited constraints */ if (cmdcon->alterInheritability &&
cmdcon->noinherit && currcon->coninhcount > 0)
ereport(ERROR,
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot alter inherited constraint \"%s\" on relation \"%s\"",
NameStr(currcon->conname),
RelationGetRelationName(rel)));
/* Loop to find the topmost constraint */ while (HeapTupleIsValid(tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parent))))
{
Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);
/* If no parent, this is the constraint we want */ if (!OidIsValid(contup->conparentid))
{
ancestorname = pstrdup(NameStr(contup->conname));
ancestortable = get_rel_name(contup->conrelid);
ReleaseSysCache(tp); break;
}
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot alter constraint \"%s\" on relation \"%s\"",
cmdcon->conname, RelationGetRelationName(rel)),
ancestorname && ancestortable ?
errdetail("Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\".",
cmdcon->conname, ancestorname, ancestortable) : 0,
errhint("You may alter the constraint it derives from instead.")));
}
/* There can be at most one matching row */ if (!HeapTupleIsValid(tuple = systable_getnext(scan)))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("constraint \"%s\" of relation \"%s\" does not exist",
constrName, RelationGetRelationName(rel))));
con = (Form_pg_constraint) GETSTRUCT(tuple); if (con->contype != CONSTRAINT_FOREIGN &&
con->contype != CONSTRAINT_CHECK &&
con->contype != CONSTRAINT_NOTNULL)
ereport(ERROR,
errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot validate constraint \"%s\" of relation \"%s\"",
constrName, RelationGetRelationName(rel)),
errdetail("This operation is not supported for this type of constraint."));
if (!con->conenforced)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot validate NOT ENFORCED constraint")));
/* Queue validation for phase 3 */
fkconstraint = makeNode(Constraint); /* for now this is all we need */
fkconstraint->conname = pstrdup(NameStr(con->conname));
/* *Ifwearetoldnottorecurse,therehadbetternotbeanychild *tables,becausewecan'tmarktheconstraintontheparentvalid *unlessitisvalidforallchildtables.
*/ if (!recurse)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraint must be validated on child tables too")));
/* *Ifwearetoldnottorecurse,therehadbetternotbeanychild *tables,becausewecan'tmarktheconstraintontheparentvalid *unlessitisvalidforallchildtables.
*/ if (!recurse)
ereport(ERROR,
errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraint must be validated on child tables too"));
/* *Thecolumnonchildmighthaveadifferentattnum,sosearchby *columnname.
*/
contup = findNotNullConstraint(childoid, colname); if (!contup)
elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
colname, get_rel_name(childoid));
childcon = (Form_pg_constraint) GETSTRUCT(contup); if (childcon->convalidated) continue;
atttuple = SearchSysCacheAttName(relId, attname); if (!HeapTupleIsValid(atttuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" referenced in foreign key constraint does not exist",
attname)));
attform = (Form_pg_attribute) GETSTRUCT(atttuple); if (attform->attnum < 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("system columns cannot be used in foreign keys"))); if (attnum >= INDEX_MAX_KEYS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("cannot have more than %d keys in a foreign key",
INDEX_MAX_KEYS)));
attnums[attnum] = attform->attnum; if (atttypids != NULL)
atttypids[attnum] = attform->atttypid; if (attcollids != NULL)
attcollids[attnum] = attform->attcollation;
ReleaseSysCache(atttuple);
attnum++;
}
return attnum;
}
/* *transformFkeyGetPrimaryKey- * *Lookupthenames,attnums,types,andcollationsoftheprimarykeyattributes *forthepkrel.AlsoreturntheindexOIDandindexopclassesofthe *indexsupportingtheprimarykey.Alsoreturnwhethertheindexhas *WITHOUTOVERLAPS. * *Allparametersexceptpkrelareoutputparameters.Also,thefunction *returnvalueisthenumberofattributesintheprimarykey. * *UsedwhenthecolumnlistintheREFERENCESspecificationisomitted.
*/ staticint
transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
List **attnamelist,
int16 *attnums, Oid *atttypids, Oid *attcollids,
Oid *opclasses, bool *pk_has_without_overlaps)
{
List *indexoidlist;
ListCell *indexoidscan;
HeapTuple indexTuple = NULL;
Form_pg_index indexStruct = NULL;
Datum indclassDatum;
oidvector *indclass; int i;
/* *Checkthatwefoundit
*/ if (!OidIsValid(*indexOid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("there is no primary key for referenced table \"%s\"",
RelationGetRelationName(pkrel))));
/* Must get indclass the hard way */
indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
Anum_pg_index_indclass);
indclass = (oidvector *) DatumGetPointer(indclassDatum);
/* *NowbuildthelistofPKattributesfromtheindkeydefinition(we *assumeaprimarykeycannothaveexpressionalelements)
*/
*attnamelist = NIL; for (i = 0; i < indexStruct->indnkeyatts; i++)
{ int pkattno = indexStruct->indkey.values[i];
/* *transformFkeyCheckAttrs- * *Validatethatthe'attnums'columnsinthe'pkrel'relationarevalidto *referenceaspartofaforeignkeyconstraint. * *ReturnstheOIDoftheuniqueindexsupportingtheconstraintand *populatesthecaller-provided'opclasses'arraywiththeopclasses *associatedwiththeindexcolumns.Alsosetswhethertheindex *usesWITHOUTOVERLAPS. * *RaisesanERRORonvalidationfailure.
*/ static Oid
transformFkeyCheckAttrs(Relation pkrel, int numattrs, int16 *attnums, bool with_period, Oid *opclasses, bool *pk_has_without_overlaps)
{
Oid indexoid = InvalidOid; bool found = false; bool found_deferrable = false;
List *indexoidlist;
ListCell *indexoidscan; int i,
j;
/* *Rejectduplicateappearancesofcolumnsinthereferenced-columnslist. *SuchacaseisforbiddenbytheSQLstandard,andevenifwethoughtit *usefultoallowit,therewouldbeambiguityabouthowtomatchthe *listtouniqueindexes(inparticular,it'dbeunclearwhichindex *opclassgoeswithwhichFKcolumn).
*/ for (i = 0; i < numattrs; i++)
{ for (j = i + 1; j < numattrs; j++)
{ if (attnums[i] == attnums[j])
ereport(ERROR,
(errcode(ERRCODE_INVALID_FOREIGN_KEY),
errmsg("foreign key referenced-columns list must not contain duplicates")));
}
}
/* Must get indclass the hard way */
indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
Anum_pg_index_indclass);
indclass = (oidvector *) DatumGetPointer(indclassDatum);
/* *Thegivenattnumlistmaymatchtheindexcolumnsinanyorder. *Checkforamatch,andextracttheappropriateopclasseswhile *we'reatit. * *Weknowthatattnums[]isduplicate-freeperthetestatthe *startofthisfunction,andwecheckedabovethatthenumberof *indexcolumnsagrees,soifwefindamatchforeachattnums[] *entrythenwemusthaveaone-to-onematchinsomeorder.
*/ for (i = 0; i < numattrs; i++)
{
found = false; for (j = 0; j < numattrs; j++)
{ if (attnums[i] == indexStruct->indkey.values[j])
{
opclasses[i] = indclass->values[j];
found = true; break;
}
} if (!found) break;
} /* The last attribute in the index must be the PERIOD FK part */ if (found && with_period)
{
int16 periodattnum = attnums[numattrs - 1];
found = (periodattnum == indexStruct->indkey.values[numattrs - 1]);
}
/* We need to know whether the index has WITHOUT OVERLAPS */ if (found)
*pk_has_without_overlaps = indexStruct->indisexclusion;
}
ReleaseSysCache(indexTuple); if (found) break;
}
if (!found)
{ if (found_deferrable)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot use a deferrable unique constraint for referenced table \"%s\"",
RelationGetRelationName(pkrel)))); else
ereport(ERROR,
(errcode(ERRCODE_INVALID_FOREIGN_KEY),
errmsg("there is no unique constraint matching given keys for referenced table \"%s\"",
RelationGetRelationName(pkrel))));
}
list_free(indexoidlist);
return indexoid;
}
/* *findFkeyCast- * *Wrapperaroundfind_coercion_pathway()forATAddForeignKeyConstraint(). *Callerhasequalregardforbinarycoercibilityandforanexactmatch.
*/ static CoercionPathType
findFkeyCast(Oid targetTypeId, Oid sourceTypeId, Oid *funcid)
{
CoercionPathType ret;
if (targetTypeId == sourceTypeId)
{
ret = COERCION_PATH_RELABELTYPE;
*funcid = InvalidOid;
} else
{
ret = find_coercion_pathway(targetTypeId, sourceTypeId,
COERCION_IMPLICIT, funcid); if (ret == COERCION_PATH_NONE) /* A previously-relied-upon cast is now gone. */
elog(ERROR, "could not find cast from %u to %u",
sourceTypeId, targetTypeId);
}
return ret;
}
/* *PermissionschecksonthereferencedtableforADDFOREIGNKEY * *Note:wehavealreadycheckedthattheuserownsthereferencingtable, *elsewe'dhavefailedmuchearlier;noadditionalchecksareneededforit.
*/ staticvoid
checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
{
Oid roleid = GetUserId();
AclResult aclresult; int i;
/* Okay if we have relation-level REFERENCES permission */
aclresult = pg_class_aclcheck(RelationGetRelid(rel), roleid,
ACL_REFERENCES); if (aclresult == ACLCHECK_OK) return; /* Else we must have REFERENCES on each column */ for (i = 0; i < natts; i++)
{
aclresult = pg_attribute_aclcheck(RelationGetRelid(rel), attnums[i],
roleid, ACL_REFERENCES); if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
}
}
/* Make changes-so-far visible */
CommandCounterIncrement();
return trigAddress.objectId;
}
/* *createForeignKeyActionTriggers *Createthereferenced-side"action"triggersthatimplementaforeign *key. * *ReturnstheOIDsofthesocreatedtriggersin*deleteTrigOidand **updateTrigOid.
*/ staticvoid
createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
Oid constraintOid, Oid indexOid,
Oid parentDelTrigger, Oid parentUpdTrigger,
Oid *deleteTrigOid, Oid *updateTrigOid)
{
CreateTrigStmt *fk_trigger;
ObjectAddress trigAddress;
/* There can be at most one matching row */ if (HeapTupleIsValid(tuple = systable_getnext(scan)))
{
dropconstraint_internal(rel, tuple, behavior, recurse, false,
missing_ok, lockmode);
found = true;
}
systable_endscan(scan);
if (!found)
{ if (!missing_ok)
ereport(ERROR,
errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("constraint \"%s\" of relation \"%s\" does not exist",
constrName, RelationGetRelationName(rel))); else
ereport(NOTICE,
errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping",
constrName, RelationGetRelationName(rel)));
}
/* Guard against stack overflow due to overly deep inheritance tree. */
check_stack_depth();
/* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing)
ATSimplePermissions(AT_DropConstraint, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
if (pkattrs == NULL &&
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
Oid pkindex = RelationGetPrimaryKeyIndex(rel, true);
if (OidIsValid(pkindex))
{
Relation pk = relation_open(pkindex, AccessShareLock);
pkattrs = NULL; for (int i = 0; i < pk->rd_index->indnkeyatts; i++)
pkattrs = bms_add_member(pkattrs, pk->rd_index->indkey.values[i]);
relation_close(pk, AccessShareLock);
}
}
if (pkattrs &&
bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, pkattrs))
ereport(ERROR,
errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" is in a primary key",
get_attname(RelationGetRelid(rel), attnum, false)));
/* Disallow if it's in the replica identity */
irattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_IDENTITY_KEY); if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, irattrs))
ereport(ERROR,
errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" is in index used as replica identity",
get_attname(RelationGetRelid(rel), attnum, false)));
/* Disallow if it's a GENERATED AS IDENTITY column */
atttup = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum); if (!HeapTupleIsValid(atttup))
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
attnum, RelationGetRelid(rel));
attForm = (Form_pg_attribute) GETSTRUCT(atttup); if (attForm->attidentity != '\0')
ereport(ERROR,
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("column \"%s\" of relation \"%s\" is an identity column",
get_attname(RelationGetRelid(rel), attnum, false),
RelationGetRelationName(rel)));
/* All good -- reset attnotnull if needed */ if (attForm->attnotnull)
{
attForm->attnotnull = false;
CatalogTupleUpdate(attrel, &atttup->t_self, atttup);
}
/* Must match lock taken by RemoveTriggerById: */
frel = table_open(con->confrelid, AccessExclusiveLock);
CheckAlterTableIsSafe(frel);
table_close(frel, NoLock);
}
/* *Propagatetochildrenasappropriate.UnlikemostotherALTER *routines,wehavetodothisonelevelofrecursionatatime;wecan't *usefind_all_inheritorstodoitinonepass.
*/ if (!is_no_inherit_constraint)
children = find_inheritance_children(RelationGetRelid(rel), lockmode); else
children = NIL;
/* *Wesearchfornot-nullconstraintsbycolumnname,andothersby *constraintname.
*/ if (con->contype == CONSTRAINT_NOTNULL)
{
tuple = findNotNullConstraint(childrelid, colname); if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
colname, RelationGetRelid(childrel));
} else
{
SysScanDesc scan;
ScanKeyData skey[3];
ScanKeyInit(&skey[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(childrelid));
ScanKeyInit(&skey[1],
Anum_pg_constraint_contypid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(InvalidOid));
ScanKeyInit(&skey[2],
Anum_pg_constraint_conname,
BTEqualStrategyNumber, F_NAMEEQ,
CStringGetDatum(constrName));
scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true, NULL, 3, skey); /* There can only be one, so no need to loop */
tuple = systable_getnext(scan); if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("constraint \"%s\" of relation \"%s\" does not exist",
constrName,
RelationGetRelationName(childrel))));
tuple = heap_copytuple(tuple);
systable_endscan(scan);
}
childcon = (Form_pg_constraint) GETSTRUCT(tuple);
/* Right now only CHECK and not-null constraints can be inherited */ if (childcon->contype != CONSTRAINT_CHECK &&
childcon->contype != CONSTRAINT_NOTNULL)
elog(ERROR, "inherited constraint is not a CHECK or not-null constraint");
if (childcon->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
childrelid, NameStr(childcon->conname));
if (recurse)
{ /* *Ifthechildconstrainthasotherdefinitionsources,just *decrementitsinheritancecount;ifnot,recursetodeleteit.
*/ if (childcon->coninhcount == 1 && !childcon->conislocal)
{ /* Time to delete this child constraint, too */
dropconstraint_internal(childrel, tuple, behavior,
recurse, true, missing_ok,
lockmode);
} else
{ /* Child constraint must survive my deletion */
childcon->coninhcount--;
CatalogTupleUpdate(conrel, &tuple->t_self, tuple);
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table"),
parser_errposition(pstate, def->location)));
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel)),
parser_errposition(pstate, def->location)));
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = attTup->attnum;
/* Can't alter a system attribute */ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"", colName),
parser_errposition(pstate, def->location)));
/* *CannotspecifyUSINGwhenalteringtypeofageneratedcolumn,because *thatwouldviolatethegenerationexpression.
*/ 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),
parser_errposition(pstate, def->location)));
/* Don't alter columns used in the partition key */ if (has_partition_attrs(rel,
bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
&is_expr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot alter column \"%s\" because it is part of the partition key of relation \"%s\"",
colName, RelationGetRelationName(rel)),
parser_errposition(pstate, def->location)));
/* Look up the target type */
typenameTypeIdAndMod(pstate, typeName, &targettype, &targettypmod);
transform = coerce_to_target_type(pstate,
transform, exprType(transform),
targettype, targettypmod,
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1); if (transform == NULL)
{ /* error text depends on whether USING was specified or not */ if (def->cooked_default != NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("result of USING clause for column \"%s\"" " cannot be cast automatically to type %s",
colName, format_type_be(targettype)),
errhint("You might need to add an explicit cast."))); else
ereport(ERROR,
(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)) : 0));
}
/* Fix collations after all else */
assign_expr_collations(pstate, transform);
/* Expand virtual generated columns in the expr. */
transform = expand_generated_columns_in_expr(transform, rel, 1);
/* Plan the expr now so we can accurately assess the need to rewrite. */
transform = (Node *) expression_planner((Expr *) transform);
for (;;)
{ /* only one varno, so no need to check that */ if (IsA(expr, Var) && ((Var *) expr)->varattno == varattno) returnfalse; elseif (IsA(expr, RelabelType))
expr = (Node *) ((RelabelType *) expr)->arg; elseif (IsA(expr, CoerceToDomain))
{
CoerceToDomain *d = (CoerceToDomain *) expr;
/* *Clearallthemissingvaluesifwe'rerewritingthetable,sincethis *rendersthempointless.
*/ if (tab->rewrite)
{
Relation newrel;
newrel = table_open(RelationGetRelid(rel), NoLock);
RelationClearMissing(newrel);
relation_close(newrel, NoLock); /* make sure we don't conflict with later attribute modifications */
CommandCounterIncrement();
}
/* Look up the target column */
heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
attnum = attTup->attnum;
attOldTup = TupleDescAttr(tab->oldDesc, attnum - 1);
/* Check for multiple ALTER TYPE on same column --- can't cope */ if (attTup->atttypid != attOldTup->atttypid ||
attTup->atttypmod != attOldTup->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of column \"%s\" twice",
colName)));
/* Look up the target type (should not fail, since prep found it) */
typeTuple = typenameType(NULL, typeName, &targettypmod);
tform = (Form_pg_type) GETSTRUCT(typeTuple);
targettype = tform->oid; /* And the collation */
targetcollid = GetColumnDefCollation(NULL, def, targettype);
/* *Ifthereisadefaultexpressionforthecolumn,getitandensurewe *cancoerceittothenewdatatype.(Wemustdothisbeforechanging *thecolumntype,becausebuild_column_defaultitselfwilltryto *coerce,andwillnotissuetheerrormessagewewantifitfails.) * *Weremoveanyimplicitcoercionstepsatthetopleveloftheold *defaultexpression;thishasbeenagreedtosatisfytheprincipleof *leastsurprise.(Theconversiontothenewcolumntypeshouldactlike *itstartedfromwhattheuserseesasthestoredexpression,andthe *implicitcoercionsaren'tgoingtobeshown.)
*/ if (attTup->atthasdef)
{
defaultexpr = build_column_default(rel, attnum);
Assert(defaultexpr);
defaultexpr = strip_implicit_coercions(defaultexpr);
defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */
defaultexpr, exprType(defaultexpr),
targettype, targettypmod,
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1); if (defaultexpr == NULL)
{ if (attTup->attgenerated)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s",
colName, format_type_be(targettype)))); else
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("default for column \"%s\" cannot be cast automatically to type %s",
colName, format_type_be(targettype))));
}
} else
defaultexpr = NULL;
/* Install dependencies on new datatype and collation */
add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype);
add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid);
switch (foundObject.classId)
{ case RelationRelationId:
{ char relKind = get_rel_relkind(foundObject.objectId);
if (relKind == RELKIND_INDEX ||
relKind == RELKIND_PARTITIONED_INDEX)
{
Assert(foundObject.objectSubId == 0);
RememberIndexForRebuilding(foundObject.objectId, tab);
} elseif (relKind == RELKIND_SEQUENCE)
{ /* *ThismustbeaSERIALcolumn'ssequence.Weneed *notdoanythingtoit.
*/
Assert(foundObject.objectSubId == 0);
} else
{ /* Not expecting any other direct dependencies... */
elog(ERROR, "unexpected object depending on column: %s",
getObjectDescription(&foundObject, false));
} break;
}
case ConstraintRelationId:
Assert(foundObject.objectSubId == 0);
RememberConstraintForRebuilding(foundObject.objectId, tab); break;
case ProcedureRelationId:
/* *Anew-styleSQLfunctioncandependonacolumn,ifthat *columnisreferencedintheparsedfunctionbody.Ideally *we'dautomaticallyupdatethefunctionbydeparsingand *reparsingit,butthat'sriskyandmightwellfailanyhow. *FIXMEsomeday. * *ThisisonlyaproblemforAT_AlterColumnType,not *AT_SetExpression.
*/ if (subtype == AT_AlterColumnType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a function or procedure"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName))); break;
case RewriteRelationId:
/* *View/rulebodieshaveprettymuchthesameissuesas *functionbodies.FIXMEsomeday.
*/ if (subtype == AT_AlterColumnType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName))); break;
case TriggerRelationId:
/* *Atriggercandependonacolumnbecausethecolumnis *specifiedasanupdatetarget,orbecausethecolumnis *usedinthetrigger'sWHENcondition.Thefirstcasewould *notrequireanyextrawork,butthesecondcasewould *requireupdatingtheWHENexpression,whichhasthesame *issuesasabove.Sincewecan'teasilytellwhichcase *applies,wepuntforboth.FIXMEsomeday.
*/ if (subtype == AT_AlterColumnType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used in a trigger definition"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName))); break;
case PolicyRelationId:
/* *Apolicycandependonacolumnbecausethecolumnis *specifiedinthepolicy'sUSINGorWITHCHECKqual *expressions.Itmightbepossibletorewriteandrecheck *thepolicyexpression,butpuntfornow.It'scertainly *easyenoughtoremoveandrecreatethepolicy;still,FIXME *someday.
*/ if (subtype == AT_AlterColumnType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used in a policy definition"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName))); break;
case AttrDefaultRelationId:
{
ObjectAddress col = GetAttrDefaultColumnAddress(foundObject.objectId);
if (col.objectId == RelationGetRelid(rel) &&
col.objectSubId == attnum)
{ /* *Ignorethecolumn'sowndefaultexpression.The *callerdealswithit.
*/
} else
{ /* *Thismustbeareferencefromtheexpressionofa *generatedcolumnelsewhereinthesametable. *Changingthetype/generatedexpressionofacolumn *thatisusedbyageneratedcolumnisnotallowed *bySQLstandard,sojustpuntfornow.Itmightbe *doablewithsomethinkingandeffort.
*/ if (subtype == AT_AlterColumnType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a generated column"),
errdetail("Column \"%s\" is used by generated column \"%s\".",
colName,
get_attname(col.objectId,
col.objectSubId, false))));
} break;
}
/* *ColumnreferenceinaPUBLICATION...FORTABLE...WHERE *clause.Sameissuesasabove.FIXMEsomeday.
*/ if (subtype == AT_AlterColumnType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a publication WHERE clause"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName))); break;
tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId)); if (!HeapTupleIsValid(tup)) /* should not happen */
elog(ERROR, "cache lookup failed for constraint %u", oldId);
con = (Form_pg_constraint) GETSTRUCT(tup); if (OidIsValid(con->conrelid))
relid = con->conrelid; else
{ /* must be a domain constraint */
relid = get_typ_typrelid(getBaseType(con->contypid)); if (!OidIsValid(relid))
elog(ERROR, "could not identify relation associated with constraint %u", oldId);
}
confrelid = con->confrelid;
conislocal = con->conislocal;
ReleaseSysCache(tup);
/* add dependencies for new statistics */
forboth(oid_item, tab->changedStatisticsOids,
def_item, tab->changedStatisticsDefs)
{
Oid oldId = lfirst_oid(oid_item);
Oid relid;
if (!rewrite)
TryReuseIndex(indoid, indstmt); /* keep any comment on the index */
indstmt->idxcomment = GetComment(indoid,
RelationRelationId, 0);
indstmt->reset_default_tblspc = true;
/* If it's a partitioned index, there is no storage to share. */ if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
{
stmt->oldNumber = irel->rd_locator.relNumber;
stmt->oldCreateSubid = irel->rd_createSubid;
stmt->oldFirstRelfilelocatorSubid = irel->rd_firstRelfilelocatorSubid;
}
index_close(irel, NoLock);
}
}
/* *SubroutineforATPostAlterTypeParse(). * *StashtheoldP-FequalityoperatorintotheConstraintnode,forpossible *usebyATAddForeignKeyConstraint()indeterminingwhetherrevalidationof *thisconstraintcanbeskipped.
*/ staticvoid
TryReuseForeignKey(Oid oldId, Constraint *con)
{
HeapTuple tup;
Datum adatum;
ArrayType *arr;
Oid *rawarr; int numkeys; int i;
tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId)); if (!HeapTupleIsValid(tup)) /* should not happen */
elog(ERROR, "cache lookup failed for constraint %u", oldId);
adatum = SysCacheGetAttrNotNull(CONSTROID, tup,
Anum_pg_constraint_conpfeqop);
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
numkeys = ARR_DIMS(arr)[0]; /* test follows the one in ri_FetchConstraintInfo() */ if (ARR_NDIM(arr) != 1 ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "conpfeqop is not a 1-D Oid array");
rawarr = (Oid *) ARR_DATA_PTR(arr);
/* stash a List of the operator Oids in our Constraint node */ for (i = 0; i < numkeys; i++)
con->old_conpfeqop = lappend_oid(con->old_conpfeqop, rawarr[i]);
attrel = table_open(AttributeRelationId, RowExclusiveLock);
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
/* Prevent them from altering a system attribute */
atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = atttableform->attnum; if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"", colName)));
/* Initialize buffers for new tuple values */
memset(repl_val, 0, sizeof(repl_val));
memset(repl_null, false, sizeof(repl_null));
memset(repl_repl, false, sizeof(repl_repl));
/* Extract the current options */
datum = SysCacheGetAttr(ATTNAME,
tuple,
Anum_pg_attribute_attfdwoptions,
&isnull); if (isnull)
datum = PointerGetDatum(NULL);
/* Transform the options */
datum = transformGenericOptions(AttributeRelationId,
datum,
options,
fdw->fdwvalidator);
/* Get its pg_class tuple, too */
class_rel = table_open(RelationRelationId, RowExclusiveLock);
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationOid)); if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relationOid);
tuple_class = (Form_pg_class) GETSTRUCT(tuple);
/* Can we change the ownership of this tuple? */ switch (tuple_class->relkind)
{ case RELKIND_RELATION: case RELKIND_VIEW: case RELKIND_MATVIEW: case RELKIND_FOREIGN_TABLE: case RELKIND_PARTITIONED_TABLE: /* ok to change owner */ break; case RELKIND_INDEX: if (!recursing)
{ /* *BecauseALTERINDEXOWNERusedtobeallowed,andinfact *isgeneratedbyoldversionsofpg_dump,wegiveawarning *anddonothingratherthanerroringout.Also,toavoid *unnecessarychatterwhilerestoringthoseolddumps,say *nothingatallifthecommandwouldbeano-opanyway.
*/ if (tuple_class->relowner != newOwnerId)
ereport(WARNING,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change owner of index \"%s\"",
NameStr(tuple_class->relname)),
errhint("Change the ownership of the index's table instead."))); /* quick hack to exit via the no-op path */
newOwnerId = tuple_class->relowner;
} break; case RELKIND_PARTITIONED_INDEX: if (recursing) break;
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change owner of index \"%s\"",
NameStr(tuple_class->relname)),
errhint("Change the ownership of the index's table instead."))); break; case RELKIND_SEQUENCE: if (!recursing &&
tuple_class->relowner != newOwnerId)
{ /* if it's an owned sequence, disallow changing it by itself */
Oid tableId;
int32 colId;
if (sequenceIsOwned(relationOid, DEPENDENCY_AUTO, &tableId, &colId) ||
sequenceIsOwned(relationOid, DEPENDENCY_INTERNAL, &tableId, &colId))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot change owner of sequence \"%s\"",
NameStr(tuple_class->relname)),
errdetail("Sequence \"%s\" is linked to table \"%s\".",
NameStr(tuple_class->relname),
get_rel_name(tableId))));
} break; case RELKIND_COMPOSITE_TYPE: if (recursing) break;
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a composite type",
NameStr(tuple_class->relname)), /* translator: %s is an SQL ALTER command */
errhint("Use %s instead.", "ALTER TYPE"))); break; case RELKIND_TOASTVALUE: if (recursing) break; /* FALL THRU */ default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change owner of relation \"%s\"",
NameStr(tuple_class->relname)),
errdetail_relkind_not_supported(tuple_class->relkind)));
}
/* *Ifthenewowneristhesameastheexistingowner,considerthe *commandtohavesucceeded.Thisisfordumprestorationpurposes.
*/ if (tuple_class->relowner != newOwnerId)
{
Datum repl_val[Natts_pg_class]; bool repl_null[Natts_pg_class]; bool repl_repl[Natts_pg_class];
Acl *newAcl;
Datum aclDatum; bool isNull;
HeapTuple newtuple;
/* skip permission checks when recursing to index or toast table */ if (!recursing)
{ /* Superusers can always do it */ if (!superuser())
{
Oid namespaceOid = tuple_class->relnamespace;
AclResult aclresult;
/* Otherwise, must be owner of the existing object */ if (!object_ownercheck(RelationRelationId, relationOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relationOid)),
RelationGetRelationName(target_rel));
/* Must be able to become new owner */
check_can_set_role(GetUserId(), newOwnerId);
/* New owner must have CREATE privilege on namespace */
aclresult = object_aclcheck(NamespaceRelationId, namespaceOid, newOwnerId,
ACL_CREATE); if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_SCHEMA,
get_namespace_name(namespaceOid));
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.