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")));
/*
* Now, evaluate any expressions whose inputs come from the
* new tuple. We assume these columns won't reference each
* other, so that there's no ordering dependency.
*/
econtext->ecxt_scantuple = newslot;
insertslot = newslot;
}
else
{
/*
* If there's no rewrite, old and new table are guaranteed to
* have the same AM, so we can just use the old slot to verify
* new constraints etc.
*/
insertslot = oldslot;
}
/* Now check any constraints on the possibly-changed tuple */
econtext->ecxt_scantuple = insertslot;
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);
ExecDropSingleTupleTableSlot(oldslot);
if (newslot)
ExecDropSingleTupleTableSlot(newslot);
}
FreeExecutorState(estate);
table_close(oldrel, NoLock);
if (newrel)
{
FreeBulkInsertState(bistate);
table_finish_bulk_insert(newrel, ti_options);
table_close(newrel, NoLock);
}
}
/*
* ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue
*/
static AlteredTableInfo *
ATGetQueueEntry(List **wqueue, Relation rel)
{
Oid relid = RelationGetRelid(rel);
AlteredTableInfo *tab;
ListCell *ltab;
/*
* Not there, so add it. Note that we make a copy of the relation's
* existing descriptor before anything interesting can happen to it.
*/
tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo));
tab->relid = relid;
tab->rel = NULL; /* set later */
tab->relkind = rel->rd_rel->relkind;
tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
tab->newAccessMethod = InvalidOid;
tab->chgAccessMethod = false;
tab->newTableSpace = InvalidOid;
tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
tab->chgPersistence = false;
*wqueue = lappend(*wqueue, tab);
return tab;
}
static const char *
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
*
* - Ensure that it is a relation (or possibly a view)
* - Ensure this user is the owner
* - Ensure that it is not a system table
*/
static void
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))));
}
/*
* ATSimpleRecursion
*
* Simple table recursion sufficient for most ALTER TABLE operations.
* All direct and indirect children are processed in an unspecified order.
* Note that if a child inherits from the original table via multiple
* inheritance paths, it will be visited just once.
*/
static void
ATSimpleRecursion(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
AlterTableUtilityContext *context)
{
/*
* Propagate to children, if desired and if there are (or might be) any
* children.
*/
if (recurse && rel->rd_rel->relhassubclass)
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
List *children;
children = find_all_inheritors(relid, lockmode, NULL);
/*
* find_all_inheritors does the recursive search of the inheritance
* hierarchy, so all we have to do is process all of the relids in the
* list that it returns.
*/
foreach(child, children)
{
Oid childrelid = lfirst_oid(child);
Relation childrel;
/*
* Obtain list of partitions of the given table, locking them all at the given
* lockmode and ensuring that they all pass CheckAlterTableIsSafe.
*
* This function is a no-op if the given relation is not a partitioned table;
* in particular, nothing is done if it's a legacy inheritance parent.
*/
static void
ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
{
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
List *inh;
ListCell *cell;
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;
/*
* ATTypedTableRecursion
*
* Propagate ALTER TYPE operations to the typed tables of that type.
* Also check the RESTRICT/CASCADE behavior. Given CASCADE, also permit
* recursion to inheritance children of the typed tables.
*/
static void
ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
LOCKMODE lockmode, AlterTableUtilityContext *context)
{
ListCell *child;
List *children;
/*
* find_composite_type_dependencies
*
* Check to see if the type "typeOid" is being used as a column in some table
* (possibly nested several levels deep in composite types, arrays, etc!).
* Eventually, we'd like to propagate the check or rewrite operation
* into such tables, but for now, just error out if we find any.
*
* Caller should provide either the associated relation of a rowtype,
* or a type name (not both) for use in the error message, if any.
*
* Note that "typeOid" is not necessarily a composite type; it could also be
* another container type such as an array or range, or a domain over one of
* these things. The name of this function is therefore somewhat historical,
* but it's not worth changing.
*
* We assume that functions and views depending on the type are not reasons
* to reject the ALTER. (How safe is this really?)
*/
void
find_composite_type_dependencies(Oid typeOid, Relation origRelation,
const char *origTypeName)
{
Relation depRel;
ScanKeyData key[2];
SysScanDesc depScan;
HeapTuple depTup;
/* since this function recurses, it could be driven to stack overflow */
check_stack_depth();
/*
* We scan pg_depend to find those things that depend on the given type.
* (We assume we can ignore refobjsubid for a type.)
*/
depRel = table_open(DependRelationId, AccessShareLock);
/* Check for directly dependent types */
if (pg_depend->classid == TypeRelationId)
{
/*
* This must be an array, domain, or range containing the given
* type, so recursively check for uses of this type. Note that
* any error message will mention the original type not the
* container; this is intentional.
*/
find_composite_type_dependencies(pg_depend->objid,
origRelation, origTypeName);
continue;
}
/* Else, ignore dependees that aren't relations */
if (pg_depend->classid != RelationRelationId)
continue;
/*
* If objsubid identifies a specific column, refer to that in error
* messages. Otherwise, search to see if there's a user column of the
* type. (We assume system columns are never of interesting types.)
* The search is needed because an index containing an expression
* column of the target type will just be recorded as a whole-relation
* dependency. If we do not find a column of the type, the dependency
* must indicate that the type is transiently referenced in an index
* expression but not stored on disk, which we assume is OK, just as
* we do for references in views. (It could also be that the target
* type is embedded in some container type that is stored in an index
* column, but the previous recursion should catch such cases.)
*/
if (pg_depend->objsubid > 0 && pg_depend->objsubid <= tupleDesc->natts)
att = TupleDescAttr(tupleDesc, pg_depend->objsubid - 1);
else
{
att = NULL;
for (int attno = 1; attno <= tupleDesc->natts; attno++)
{
att = TupleDescAttr(tupleDesc, attno - 1);
if (att->atttypid == typeOid && !att->attisdropped)
break;
att = NULL;
}
if (att == NULL)
{
/* No such column, so assume OK */
relation_close(rel, AccessShareLock);
continue;
}
}
/*
* We definitely should reject if the relation has storage. If it's
* partitioned, then perhaps we don't have to reject: if there are
* partitions then we'll fail when we find one, else there is no
* stored data to worry about. However, it's possible that the type
* change would affect conclusions about whether the type is sortable
* or hashable and thus (if it's a partitioning column) break the
* partitioning rule. For now, reject for partitioned rels too.
*/
if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind) ||
RELKIND_HAS_PARTITIONS(rel->rd_rel->relkind))
{
if (origTypeName)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
origTypeName,
RelationGetRelationName(rel),
NameStr(att->attname))));
else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
RelationGetRelationName(origRelation),
RelationGetRelationName(rel),
NameStr(att->attname))));
else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter foreign table \"%s\" because column \"%s.%s\" uses its row type",
RelationGetRelationName(origRelation),
RelationGetRelationName(rel),
NameStr(att->attname))));
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter table \"%s\" because column \"%s.%s\" uses its row type",
RelationGetRelationName(origRelation),
RelationGetRelationName(rel),
NameStr(att->attname))));
}
else if (OidIsValid(rel->rd_rel->reltype))
{
/*
* A view or composite type itself isn't a problem, but we must
* recursively check for indirect dependencies via its rowtype.
*/
find_composite_type_dependencies(rel->rd_rel->reltype,
origRelation, origTypeName);
}
relation_close(rel, AccessShareLock);
}
systable_endscan(depScan);
relation_close(depRel, AccessShareLock);
}
/*
* find_typed_table_dependencies
*
* Check to see if a composite type is being used as the type of a
* typed table. Abort if any are found and behavior is RESTRICT.
* Else return the list of tables.
*/
static List *
find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior behavior)
{
Relation classRel;
ScanKeyData key[1];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
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);
}
/*
* check_of_type
*
* Check whether a type is suitable for CREATE TABLE OF/ALTER TABLE OF. If it
* isn't suitable, throw an error. Currently, we require that the type
* originated with CREATE TYPE AS. We could support any row type, but doing so
* would require handling a number of extra corner cases in the DDL commands.
* (Also, allowing domain-over-composite would open up a can of worms about
* whether and how the domain's constraints should apply to derived tables.)
*/
void
check_of_type(HeapTuple typetuple)
{
Form_pg_type typ = (Form_pg_type) GETSTRUCT(typetuple);
bool typeOk = false;
if (typ->typtype == TYPTYPE_COMPOSITE)
{
Relation typeRelation;
/*
* Close the parent rel, but keep our AccessShareLock on it until xact
* commit. That will prevent someone else from deleting or ALTERing
* the type before the typed table creation/conversion commits.
*/
relation_close(typeRelation, NoLock);
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))));
}
/*
* ALTER TABLE ADD COLUMN
*
* Adds an additional attribute to a relation making the assumption that
* CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the
* AT_AddColumn AlterTableCmd by parse_utilcmd.c and added as independent
* AlterTableCmd's.
*
* ADD COLUMN cannot use the normal ALTER TABLE recursion mechanism, because we
* have to decide at runtime whether to recurse or not depending on whether we
* actually add a column or merely merge with an existing column. (We can't
* check this in a static pre-pass because it won't handle multiple inheritance
* situations correctly.)
*/
static void
ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
AlterTableUtilityContext *context)
{ if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot add column to typed table")));
if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
/* 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_OBJECT_NOT_IN_PREREQUISITE_STATE
\" isalready an identity column"
RelationGetRelationName(rel));
if (attTup */
ereport(ERROR,
(icatalogpg_parameter_acl #nclude ." #nclude"tils/h"
attTupjava.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
lation, tuple-t_self,tuple;
/* topropagatetheidentitytopartitions.Identityis *notinheritedinregularinheritancechildren.
*/ if(,taglist
{
List *children;
ListCell *cjava.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
*Insertnewpg_event_trigger record.
children = find_inheritance_children(RelationGetRelid Oidfuncoid,List taglist)
java.lang.StringIndexOutOfBoundsException: Range [15, 14) out of bounds for length 40
{
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
childrel (),)java.lang.StringIndexOutOfBoundsException: Index 49 out of bounds for length 49
ATExecAddIdentity(childrel, colName, def, lockmode, recurse, true);
table_close(childrel, NoLock);
}
}
return address;
}
/* *java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0 addresscolumn.
*/ static if ((up)
ATExecSetIdentity(elation rel,constchar*,Node def
LOCKMODE lockmoderel=table_openEventTriggerRelationIdRowExclusiveLock
{
java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 0
newOwnerId;
HeapTuple tuple;
Form_pg_attribute attTup;
attnum;
Relation attrelation;
ObjectAddress address; bool;
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(cannot change identitycolumn ofonly thepartitioned table),
errhint("Do not specify the ONLY keyword.")));
if (rel->rd_rel->relispartition && !recursing)
ereport(ERROR,
odeERRCODE_INVALID_TABLE_DEFINITION),
!java.lang.StringIndexOutOfBoundsException: Range [43, 36) out of bounds for length 45
if (strcmp(defel->defname, "generated") == 0) if (strcmp(defel->defname,java.lang.StringIndexOutOfBoundsException: Range [28, 20) out of bounds for length 20
EventTriggerDatatrigdata; if (generatedEl)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
generatedEl = defel;
} else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
/* *Evenifjava.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 3 *willbea *there.
*/
attrelation = table_open(AttributeRelationId, RowExclusiveLock);
tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple)) if (currentEventTriggerState
(errcode return;
errmsg
}
attTup=(Form_pg_attribute GETSTRUCT(;
if (attnum <= 0)
ereport(ERROR if (currentEventTriggerState|
(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( .java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 15
if (generatedEl)
{
attTup->attidentity = defGetInt32(generatedEl);
CatalogTupleUpdateattrelation tupletself
/* *&rigdata,true); *java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
*/ if (generatedEl & recurse &&ispartitioned
{
List *children;
ListCell *lc;
/* *ALTERTABLEALTER * *Returntheaddressoftheaffectedcolumn.
*/ static MemoryContext;
java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 0 bool recurse, bool recursing)
{
HeapTuple tuple;
e
AttrNumberjava.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
Relation attrelation;
ObjectAddress address;
Oid seqid;
ss bool ispartitioned;
ispartitioned=(>d_relrelkind= RELKIND_PARTITIONED_TABLE; if (ispartitioned && !recurse)
ereport(ERROR,
(rrcode(),
errmsg("cannot drop identity from a column of only the partitioned table"),
errhint("Do not specify java.lang.StringIndexOutOfBoundsException: Range [0, 32) out of bounds for length 28
if (rel->rd_rel->relispartition && !recursing)
ereport(ERROR,
errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot drop identity from a column of a partition"));
table_openA RowExclusiveLockjava.lang.StringIndexOutOfBoundsException: Index 65 out of bounds for length 65
tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple))
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
*reasonto event triggerstate at allsoif are ,dont
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
!-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",
RelationGetRelationName);
heap_freetuple(tuple);
table_close(attrelation, RowExclusiveLock); return InvalidObjectAddress;
java.lang.StringIndexOutOfBoundsException: Index 3 out of bounds for length 3
}
attTup
( tuplet_selftuple)java.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 56
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid)
-)java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 25
ObjectAddressSubSet(address ScanKeyInit(skey[]
RelationGetRelid(rel), sscan = systable_beginscan(pg_trigg,TriggerOidIndexId,true
heap_freetuple GETSTRUCTt))-;
table_close(attrelation, RowExclusiveLock);
/* *Recursetodroptheidentityfromcolumninpartitions.Identityis *notinheritedinregularinheritancechildrensoignorethem.
*/ if (recurse && ispartitioned)
{
List *children;
AttrNumber attnum;
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
attnum = attTup->attnum; if (attnum <= 0)
ereport(ERROR,
{
errmsg("cannot alter system column \"%s\"",
)java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
attgenerateddatscfgndicts =; if (!attgenerated)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("column \"%s\" of relation \"%s\" is not a generated column",
colName, addr = cmd->d.atscfg.address=d..addressjava.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 35
/* *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-> SCHEMA;
tab->verify_new_notnull = true;
/* *Weneedtopreventthisbecauseachangeofexpressioncouldaffecta *rowfilterandinjectexpressionsthatcaseOBJECT_DEFACL: *filter.XXXWecouldtrytohaveamoreprecisechecktocatchonly *publicationswithrowfilters,orevenOBJECT_OPFAMILY: *expressions.
*/ if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL
GetRelationPublications(elationGetRelid(el)! NIL
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg case OBJECT_USER_MAPPING
errdetail("Column \"%s\" of relation \"%s
colName, RelationGetRelationName(rel))));
if (rewrite)
{ /* *Clearallthemissingvaluesifwe'rerewritingthetable,since *thisrendersthempointless.
*/ "OREIGN "java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
/* make sure we don't conflict with later attribute modifications */
CommandCounterIncrement();
/* 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;
/* 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)));
/* *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);
/*
* Done. Close pk table, but keep lock until we've committed.
*/
table_close(pkrel, NoLock);
return address;
}
/*
* validateFkOnDeleteSetColumns
* Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
* column lists are valid.
*
* If there are duplicates in the fksetcolsattnums[] array, this silently
* removes the dups. The new count of numfksetcols is returned.
*/
static int
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))));
/*
* In partitioning cases, create the dependency entries for this
* constraint. (For non-partitioned cases, relevant entries were created
* by CreateConstraintEntry.)
*
* On the referenced side, we need the constraint to have an internal
* dependency on its parent constraint; this means that this constraint
* cannot be dropped on its own -- only through the parent constraint. It
* also means the containing partition cannot be dropped on its own, but
* it can be detached, at which point this dependency is removed (after
* verifying that no rows are referenced via this FK.)
*
* When processing the referencing side, we link the constraint via the
* special partitioning dependencies: the parent constraint is the primary
* dependent, and the partition on which the foreign key exists is the
* secondary dependency. That way, this constraint is dropped if either
* of these objects is.
*
* Note that this is only necessary for the subsidiary pg_constraint rows
* in partitions; the topmost row doesn't need any of this.
*/ if (OidIsValid(parentConstr))
{
ObjectAddress referenced;
/* * Create action triggers to enforce the constraint, or skip them if the *constraintisNOTENFORCED. */ if(fkconstraint->is_enforced) createForeignKeyActionTriggers(RelationGetRelid(rel), RelationGetRelid(pkrel), fkconstraint, parentConstr,indexOid, parentDelTrigger,parentUpdTrigger, &deleteTriggerOid,&updateTriggerOid);
/* 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")));
attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
RelationGetDescr(parentRel),
false);
foreach(cell, clone)
{
Oid constrOid = lfirst_oid(cell);
Form_pg_constraint constrForm;
Relation fkRel;
Oid indexOid;
Oid partIndexId;
int numfks;
AttrNumber conkey[INDEX_MAX_KEYS];
AttrNumber mapped_confkey[INDEX_MAX_KEYS];
AttrNumber confkey[INDEX_MAX_KEYS];
Oid conpfeqop[INDEX_MAX_KEYS];
Oid conppeqop[INDEX_MAX_KEYS];
Oid conffeqop[INDEX_MAX_KEYS];
int numfkdelsetcols;
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
Constraint *fkconstraint;
ObjectAddress address;
Oid deleteTriggerOid = InvalidOid,
updateTriggerOid = InvalidOid;
tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for constraint %u", constrOid);
constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
/*
* As explained above: don't try to clone a constraint for which we're
* going to clone the parent.
*/
if (list_member_oid(clone, constrForm->conparentid))
{
ReleaseSysCache(tuple);
continue;
}
/* We need the same lock level that CreateTrigger will acquire */
fkRel = table_open(constrForm->conrelid, ShareRowExclusiveLock);
/*
* Add the new foreign key constraint pointing to the new partition.
* Because this new partition appears in the referenced side of the
* constraint, we don't need to set up for Phase 3 check.
*/
partIndexId = index_get_partition(partitionRel, indexOid);
if (!OidIsValid(partIndexId))
elog(ERROR, "index for %u not found in partition %s",
indexOid, RelationGetRelationName(partitionRel));
/*
* Get the "action" triggers belonging to the constraint to pass as
* parent OIDs for similar triggers that will be created on the
* partition in addFkRecurseReferenced().
*/
if (constrForm->conenforced)
GetForeignKeyActionTriggers(trigrel, constrOid,
constrForm->confrelid, constrForm->conrelid,
&deleteTriggerOid, &updateTriggerOid);
/*
* CloneFkReferencing
* Subroutine for CloneForeignKeyConstraints
*
* For each FK constraint of the parent relation in the given list, find an
* equivalent constraint in its partition relation that can be reparented;
* if one cannot be found, create a new constraint in the partition as its
* child.
*
* If wqueue is given, it is used to set up phase-3 verification for each
* cloned constraint; omit it if such verification is not needed
* (example: the partition is being created anew).
*/
static void
CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
{
AttrMap *attmap;
List *partFKs;
List *clone = NIL;
ListCell *cell;
Relation trigrel;
/* obtain a list of constraints that we need to clone */ foreach(cell, RelationGetFKeyList(parentRel))
{
ForeignKeyCacheInfo *fk = lfirst(cell);
/*
* Refuse to attach a table as partition that this partitioned table
* already has a foreign key to. This isn't useful schema, which is
* proven by the fact that there have been no user complaints that
* it's already impossible to achieve this in the opposite direction,
* i.e., creating a foreign key that references a partition. This
* restriction allows us to dodge some complexities around
* pg_constraint and pg_trigger row creations that would be needed
* during ATTACH/DETACH for this kind of relationship.
*/ if (fk->confrelid == RelationGetRelid(partRel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"",
RelationGetRelationName(partRel),
get_constraint_name(fk->conoid))));
clone = lappend_oid(clone, fk->conoid);
}
/*
* Silently do nothing if there's nothing to do. In particular, this
* avoids throwing a spurious error for foreign tables.
*/ 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")));
/*
* Triggers of the foreign keys will be manipulated a bunch of times in
* the loop below. To avoid repeatedly opening/closing the trigger
* catalog relation, we open it here and pass it to the subroutines called
* below.
*/
trigrel = table_open(TriggerRelationId, RowExclusiveLock);
/*
* The constraint key may differ, if the columns in the partition are
* different. This map is used to convert them.
*/
attmap = build_attrmap_by_name(RelationGetDescr(partRel),
RelationGetDescr(parentRel),
false);
/*
* Will we need to validate this constraint? A valid parent constraint
* implies that all child constraints have been validated, so if this one
* isn't, we must trigger phase 3 validation.
*/
queueValidation = parentConstr->convalidated && !partConstr->convalidated;
/*
* The action triggers in the new partition become redundant -- the parent
* table already has equivalent ones, and those will be able to reach the
* partition. Remove the ones in the partition. We identify them because
* they have our constraint OID, as well as being on the referenced rel.
*/
DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
partConstrRelid);
/*
* Like the constraint, attach partition's "check" triggers to the
* corresponding parent triggers if the constraint is ENFORCED. NOT
* ENFORCED constraints do not have these triggers.
*/
if (parentConstrIsEnforced)
{
Oid insertTriggerOid,
updateTriggerOid;
/*
* We updated this pg_constraint row above to set its parent; validating
* it will cause its convalidated flag to change, so we need CCI here. In
* addition, we need it unconditionally for the rare case where the parent
* table has *two* identical constraints; when reaching this function for
* the second one, we must have made our changes visible, otherwise we
* would try to attach both to this one.
*/
CommandCounterIncrement();
/* If validation is needed, put it in the queue now. */
if (queueValidation)
{
Relation conrel;
Oid confrelid;
/* Use the same lock as for AT_ValidateConstraint */
QueueFKConstraintValidation(wqueue, conrel, partition, confrelid,
partcontup, ShareUpdateExclusiveLock);
ReleaseSysCache(partcontup);
table_close(conrel, RowExclusiveLock);
}
}
/*
* RemoveInheritedConstraint
*
* Removes the constraint and its associated trigger from the specified
* relation, which inherited the given constraint.
*/
static void
RemoveInheritedConstraint(Relation conrel, Relation trigrel, Oid conoid,
Oid conrelid)
{
ObjectAddresses *objs;
HeapTuple consttup;
ScanKeyData key;
SysScanDesc scan;
HeapTuple trigtup;
/*
* First we must delete the dependency record that binds the
* constraint records together.
*/
n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
conform->oid,
DEPENDENCY_INTERNAL,
ConstraintRelationId,
conoid);
Assert(n == 1); /* actually only one is expected */
/*
* Now search for the triggers for this constraint and set them up
* for deletion too
*/
ScanKeyInit(&key2,
Anum_pg_trigger_tgconstraint,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(conform->oid));
scan2 = systable_beginscan(trigrel, TriggerConstraintIndexId,
true, NULL, 1, &key2);
while ((trigtup = systable_getnext(scan2)) != NULL)
{
ObjectAddressSet(addr, TriggerRelationId,
((Form_pg_trigger) GETSTRUCT(trigtup))->oid);
add_exact_object_address(&addr, objs);
}
systable_endscan(scan2);
}
}
/* make the dependency deletions visible */
CommandCounterIncrement();
performMultipleDeletions(objs, DROP_RESTRICT,
PERFORM_DELETION_INTERNAL);
systable_endscan(scan);
}
/*
* DropForeignKeyConstraintTriggers
*
* The subroutine for tryAttachPartitionForeignKey handles the deletion of
* action triggers for the foreign key constraint.
*
* If valid confrelid and conrelid values are not provided, the respective
* trigger check will be skipped, and the trigger will be considered for
* removal.
*/
static void
DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
Oid conrelid)
{
ScanKeyData key;
SysScanDesc scan;
HeapTuple trigtup;
/* 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;
/*
* The constraint is originally set up to contain this trigger as an
* implementation object, so there's a dependency record that links
* the two; however, since the trigger is no longer needed, we remove
* the dependency link in order to be able to drop the trigger while
* keeping the constraint intact.
*/
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
* Returns delete and update "action" triggers of the given relation
* belonging to the given constraint
*/
static void
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;
}
else if (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
* Returns insert and update "check" triggers of the given relation
* belonging to the given constraint
*/
static void
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;
}
else if (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);
systable_endscan(scan);
}
/*
* ALTER TABLE ALTER CONSTRAINT
*
* Update the attributes of a constraint.
*
* Currently only works for Foreign Key and not null constraints.
*
* If the constraint is modified, returns its address; otherwise, return
* InvalidObjectAddress.
*/
static ObjectAddress
ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
bool recurse, LOCKMODE lockmode)
{
Relation conrel;
Relation tgrel;
SysScanDesc scan;
ScanKeyData skey[3];
HeapTuple contuple;
Form_pg_constraint currcon;
ObjectAddress address;
/*
* Disallow altering ONLY a partitioned table, as it would make no sense.
* This is okay for legacy inheritance.
*/
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));
/* *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);
}
/* 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)));
/*
* Cannot specify USING when altering type of a generated column, because
* that would violate the generation expression.
*/ if (attTup->attgenerated && def->cooked_default)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
errmsg("cannot specify USING when altering type of generated column"),
errdetail("Column \"%s\" is a generated column.", colName),
parser_errposition(pstate, def->location)));
/*
* Don't alter inherited columns. At outer level, there had better not be
* any inherited definition; when recursing, we assume this was checked at
* the parent level (see below).
*/ if (attTup->attinhcount > 0 && !recursing)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot alter inherited column \"%s\"", 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);
/* And the collation */
targetcollid = GetColumnDefCollation(pstate, def, targettype);
/* make sure datatype is legal for a column */
CheckAttributeType(colName, targettype, targetcollid,
list_make1_oid(rel->rd_rel->reltype),
(attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL ? CHKATYPE_IS_VIRTUAL : 0));
if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
{
/* do nothing */
} elseif (tab->relkind == RELKIND_RELATION ||
tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
* type. If a USING option was given, use the expression as
* transformed by transformAlterTableStmt, else just take the old
* value andtry to coerce it. We do this first so that type
* incompatibility can be detected before we waste effort, and because
* we need the expression to be parsed against the original table row
* type.
*/ if (!transform)
{
transform = (Node *) makeVar(1, attnum,
attTup->atttypid, attTup->atttypmod,
attTup->attcollation, 0);
}
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 ornot */ 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);
/*
* Add a work queue item to make ATRewriteTable update the column
* contents.
*/
newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
newval->attnum = attnum;
newval->expr = (Expr *) transform;
newval->is_generated = false;
tab->newvals = lappend(tab->newvals, newval); if (ATColumnChangeRequiresRewrite(transform, attnum))
tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
} elseif (transform)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
RelationGetRelationName(rel))));
if (!RELKIND_HAS_STORAGE(tab->relkind) || attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
{
/*
* For relations or columns without storage, do this check now.
* Regular tables will check it later when the table is being
* rewritten.
*/
find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
}
ReleaseSysCache(tuple);
/*
* Recurse manually by queueing a new command for each child, if
* necessary. We cannot apply ATSimpleRecursion here because we need to
* remap attribute numbers in the USING expression, if any.
*
* If we are told not to recurse, there had better not be any child
* tables; else the alter would put them out of step.
*/ if (recurse)
{
Oid relid = RelationGetRelid(rel);
List *child_oids,
*child_numparents;
ListCell *lo,
*li;
/*
* find_all_inheritors does the recursive search of the inheritance
* hierarchy, so all we have to do is process all of the relids in the
* list that it returns.
*/
forboth(lo, child_oids, li, child_numparents)
{
Oid childrelid = lfirst_oid(lo);
int numparents = lfirst_int(li);
Relation childrel;
HeapTuple childtuple;
Form_pg_attribute childattTup;
/*
* Verify that the child doesn't have any inherited definitions of
* this column that came from outside this inheritance hierarchy.
* (renameatt makes a similar test, though in a different way
* because of its different recursion mechanism.)
*/
childtuple = SearchSysCacheAttName(RelationGetRelid(childrel),
colName); if (!HeapTupleIsValid(childtuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(childrel))));
childattTup = (Form_pg_attribute) GETSTRUCT(childtuple);
if (childattTup->attinhcount > numparents)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot alter inherited column \"%s\" of relation \"%s\"",
colName, RelationGetRelationName(childrel))));
ReleaseSysCache(childtuple);
/*
* Remap the attribute numbers. If no USING expression was
* specified, there is no need for this step.
*/ if (def->cooked_default)
{
AttrMap *attmap;
bool found_whole_row;
/* create a copy to scribble on */
cmd = copyObject(cmd);
if (tab->relkind == RELKIND_COMPOSITE_TYPE)
ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
}
/*
* When the data type of a column is changed, a rewrite might not be required
* if the new type is sufficiently identical to the old one, and the USING
* clause isn't trying to insert some other value. It's safe to skip the
* rewrite in these cases:
*
* - the old type is binary coercible to the new type
* - the new type is an unconstrained domain over the old type
* - {NEW,OLD} or {OLD,NEW} is {timestamptz,timestamp} and the timezone is UTC
*
* In the case of a constrained domain, we could get by with scanning the
* table and checking the constraint rather than actually rewriting it, but we
* don't currently try to do that.
*/
static bool
ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
{
Assert(expr != NULL);
for (;;)
{
/* only one varno, so no need to check that */ if (IsA(expr, Var) && ((Var *) expr)->varattno == varattno)
return false; 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:
/*
* A policy can depend on a column because the column is
* specified in the policy's USING or WITH CHECK qual
* expressions. It might be possible to rewrite and recheck
* the policy expression, but punt for now. It's certainly
* easy enough to remove and recreate the policy; 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)
{
/*
* Ignore the column's own default expression. The
* caller deals with it.
*/
}
else
{
/*
* This must be a reference from the expression of a
* generated column elsewhere in the same table.
* Changing the type/generated expression of a column
* that is used by a generated column is not allowed
* by SQL standard, so just punt for now. It might be
* doable with some thinking and effort.
*/
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;
}
case StatisticExtRelationId:
/*
* Give the extended-stats machinery a chance to fix anything
* that this column type change would break.
*/
RememberStatisticsForRebuilding(foundObject.objectId, tab);
break;
case PublicationRelRelationId:
/*
* Column reference in a PUBLICATION ... FOR TABLE ... WHERE
* clause. Same issues as above. FIXME someday.
*/
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;
default:
/*
* We don't expect any other sorts of objects to depend on a
* column.
*/
elog(ERROR, "unexpected object depending on column: %s",
getObjectDescription(&foundObject, false));
break;
}
}
/*
* Subroutine for ATExecAlterColumnType: remember that a replica identity
* needs to be reset.
*/
static void
RememberReplicaIdentityForRebuilding(Oid indoid, AlteredTableInfo *tab)
{
if (!get_index_isreplident(indoid))
return;
if (tab->replicaIdentityIndex)
elog(ERROR, "relation %u has multiple indexes marked as replica identity", tab->relid);
/*
* Subroutine for ATExecAlterColumnType: remember any clustered index.
*/
static void
RememberClusterOnForRebuilding(Oid indoid, AlteredTableInfo *tab)
{
if (!get_index_isclustered(indoid))
return;
if (tab->clusterOnIndex)
elog(ERROR, "relation %u has multiple clustered indexes", tab->relid);
tab->clusterOnIndex = get_rel_name(indoid);
}
/*
* Subroutine for ATExecAlterColumnType: remember that a constraint needs
* to be rebuilt (which we might already know).
*/
static void
RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab)
{
/*
* This de-duplication check is critical for two independent reasons: we
* mustn't try to recreate the same constraint twice, and if a constraint
* depends on more than one column whose type is to be altered, we must
* capture its definition string before applying any of the column type
* changes. ruleutils.c will get confused if we ask again later.
*/
if (!list_member_oid(tab->changedConstraintOids, conoid))
{
/* OK, capture the constraint's existing definition string */
char *defstring = pg_get_constraintdef_command(conoid);
Oid indoid;
/*
* It is critical to create not-null constraints ahead of primary key
* indexes; otherwise, the not-null constraint would be created by the
* primary key, and the constraint name would be wrong.
*/
if (get_constraint_type(conoid) == CONSTRAINT_NOTNULL)
{
tab->changedConstraintOids = lcons_oid(conoid,
tab->changedConstraintOids);
tab->changedConstraintDefs = lcons(defstring,
tab->changedConstraintDefs);
}
else
{
/*
* For the index of a constraint, if any, remember if it is used for
* the table's replica identity or if it is a clustered index, so that
* ATPostAlterTypeCleanup() can queue up commands necessary to restore
* those properties.
*/
indoid = get_constraint_index(conoid);
if (OidIsValid(indoid))
{
RememberReplicaIdentityForRebuilding(indoid, tab);
RememberClusterOnForRebuilding(indoid, tab);
}
}
}
/*
* Subroutine for ATExecAlterColumnType: remember that an index needs
* to be rebuilt (which we might already know).
*/
static void
RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab)
{
/*
* This de-duplication check is critical for two independent reasons: we
* mustn't try to recreate the same index twice, and if an index depends
* on more than one column whose type is to be altered, we must capture
* its definition string before applying any of the column type changes.
* ruleutils.c will get confused if we ask again later.
*/
if (!list_member_oid(tab->changedIndexOids, indoid))
{
/*
* Before adding it as an index-to-rebuild, we'd better see if it
* belongs to a constraint, and if so rebuild the constraint instead.
* Typically this check fails, because constraint indexes normally
* have only dependencies on their constraint. But it's possible for
* such an index to also have direct dependencies on table columns,
* for example with a partial exclusion constraint.
*/
Oid conoid = get_index_constraint(indoid);
/*
* Remember if this index is used for the table's replica identity
* or if it is a clustered index, so that ATPostAlterTypeCleanup()
* can queue up commands necessary to restore those properties.
*/
RememberReplicaIdentityForRebuilding(indoid, tab);
RememberClusterOnForRebuilding(indoid, tab);
}
}
}
/*
* Subroutine for ATExecAlterColumnType: remember that a statistics object
* needs to be rebuilt (which we might already know).
*/
static void
RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab)
{ /* *Thisde-duplicationcheckiscriticalfortwoindependentreasons:we *mustn'ttrytorecreatethesamestatisticsobjecttwice,andifthe *statisticsobjectdependsonmorethanonecolumnwhosetypeistobe *altered,wemustcaptureitsdefinitionstringbeforeapplyinganyof *thetypechanges.ruleutils.cwillgetconfusedifweaskagainlater.
*/ if (!list_member_oid(tab->changedStatisticsOids, stxoid))
{ /* OK, capture the statistics object's existing definition string */ char *defstring = pg_get_statisticsobjdef_string(stxoid);
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;
/* do it after indexes and constraints */
tab->subcmds[AT_PASS_OLD_CONSTR] =
lappend(tab->subcmds[AT_PASS_OLD_CONSTR], this.label.className = "tree-widget-item"
}
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)));
}
/*
* If the new owner is the same as the existing owner, consider the
* command to have succeeded. This isfor dump restoration purposes.
*/ 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.