mirror of
https://github.com/postgres/postgres.git
synced 2025-06-17 17:02:08 +03:00
Identity columns
This is the SQL standard-conforming variant of PostgreSQL's serial columns. It fixes a few usability issues that serial columns have: - CREATE TABLE / LIKE copies default but refers to same sequence - cannot add/drop serialness with ALTER TABLE - dropping default does not drop sequence - need to grant separate privileges to sequence - other slight weirdnesses because serial is some kind of special macro Reviewed-by: Vitaly Burovoy <vitaly.burovoy@gmail.com>
This commit is contained in:
@ -361,6 +361,11 @@ static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
|
||||
const char *colName, LOCKMODE lockmode);
|
||||
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
|
||||
Node *newDefault, LOCKMODE lockmode);
|
||||
static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
|
||||
Node *def, LOCKMODE lockmode);
|
||||
static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
|
||||
Node *def, LOCKMODE lockmode);
|
||||
static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
|
||||
static void ATPrepSetStatistics(Relation rel, const char *colName,
|
||||
Node *newValue, LOCKMODE lockmode);
|
||||
static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName,
|
||||
@ -696,6 +701,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
|
||||
cookedDefaults = lappend(cookedDefaults, cooked);
|
||||
descriptor->attrs[attnum - 1]->atthasdef = true;
|
||||
}
|
||||
|
||||
if (colDef->identity)
|
||||
descriptor->attrs[attnum - 1]->attidentity = colDef->identity;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1281,7 +1289,7 @@ ExecuteTruncate(TruncateStmt *stmt)
|
||||
foreach(cell, rels)
|
||||
{
|
||||
Relation rel = (Relation) lfirst(cell);
|
||||
List *seqlist = getOwnedSequences(RelationGetRelid(rel));
|
||||
List *seqlist = getOwnedSequences(RelationGetRelid(rel), 0);
|
||||
ListCell *seqcell;
|
||||
|
||||
foreach(seqcell, seqlist)
|
||||
@ -2078,6 +2086,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
|
||||
get_collation_name(defcollid),
|
||||
get_collation_name(newcollid))));
|
||||
|
||||
/*
|
||||
* Identity is never inherited. The new column can have an
|
||||
* identity definition, so we always just take that one.
|
||||
*/
|
||||
def->identity = newdef->identity;
|
||||
|
||||
/* Copy storage parameter */
|
||||
if (def->storage == 0)
|
||||
def->storage = newdef->storage;
|
||||
@ -3217,6 +3231,9 @@ AlterTableGetLockLevel(List *cmds)
|
||||
case AT_DisableRowSecurity:
|
||||
case AT_ForceRowSecurity:
|
||||
case AT_NoForceRowSecurity:
|
||||
case AT_AddIdentity:
|
||||
case AT_DropIdentity:
|
||||
case AT_SetIdentity:
|
||||
cmd_lockmode = AccessExclusiveLock;
|
||||
break;
|
||||
|
||||
@ -3447,6 +3464,18 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
|
||||
/* No command-specific prep needed */
|
||||
pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
|
||||
break;
|
||||
case AT_AddIdentity:
|
||||
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
|
||||
pass = AT_PASS_ADD_CONSTR;
|
||||
break;
|
||||
case AT_DropIdentity:
|
||||
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
|
||||
pass = AT_PASS_DROP;
|
||||
break;
|
||||
case AT_SetIdentity:
|
||||
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
|
||||
pass = AT_PASS_COL_ATTRS;
|
||||
break;
|
||||
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
|
||||
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
|
||||
ATPrepDropNotNull(rel, recurse, recursing);
|
||||
@ -3772,6 +3801,15 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
|
||||
address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
|
||||
break;
|
||||
case AT_AddIdentity:
|
||||
address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode);
|
||||
break;
|
||||
case AT_SetIdentity:
|
||||
address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode);
|
||||
break;
|
||||
case AT_DropIdentity:
|
||||
address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode);
|
||||
break;
|
||||
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
|
||||
address = ATExecDropNotNull(rel, cmd->name, lockmode);
|
||||
break;
|
||||
@ -5120,6 +5158,17 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
elog(ERROR, "cache lookup failed for relation %u", myrelid);
|
||||
relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;
|
||||
|
||||
/*
|
||||
* Cannot add identity column if table has children, because identity does
|
||||
* not inherit. (Adding column and identity separately will work.)
|
||||
*/
|
||||
if (colDef->identity &&
|
||||
recurse &&
|
||||
find_inheritance_children(myrelid, NoLock) != NIL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
|
||||
errmsg("cannot recursively add identity column to table that has child tables")));
|
||||
|
||||
/* skip if the name already exists and if_not_exists is true */
|
||||
if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
|
||||
{
|
||||
@ -5172,6 +5221,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
attribute.attalign = tform->typalign;
|
||||
attribute.attnotnull = colDef->is_not_null;
|
||||
attribute.atthasdef = false;
|
||||
attribute.attidentity = colDef->identity;
|
||||
attribute.attisdropped = false;
|
||||
attribute.attislocal = colDef->is_local;
|
||||
attribute.attinhcount = colDef->inhcount;
|
||||
@ -5539,6 +5589,12 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
|
||||
errmsg("cannot alter system column \"%s\"",
|
||||
colName)));
|
||||
|
||||
if (get_attidentity(RelationGetRelid(rel), attnum))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("column \"%s\" of relation \"%s\" is an identity column",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
|
||||
/*
|
||||
* Check that the attribute is not in a primary key
|
||||
*
|
||||
@ -5755,6 +5811,13 @@ ATExecColumnDefault(Relation rel, const char *colName,
|
||||
errmsg("cannot alter system column \"%s\"",
|
||||
colName)));
|
||||
|
||||
if (get_attidentity(RelationGetRelid(rel), attnum))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("column \"%s\" of relation \"%s\" is an identity column",
|
||||
colName, RelationGetRelationName(rel)),
|
||||
newDefault ? 0 : errhint("Use ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY instead.")));
|
||||
|
||||
/*
|
||||
* Remove any old default for the column. We use RESTRICT here for
|
||||
* safety, but at present we do not expect anything to depend on the
|
||||
@ -5789,6 +5852,224 @@ ATExecColumnDefault(Relation rel, const char *colName,
|
||||
return address;
|
||||
}
|
||||
|
||||
/*
|
||||
* ALTER TABLE ALTER COLUMN ADD IDENTITY
|
||||
*
|
||||
* Return the address of the affected column.
|
||||
*/
|
||||
static ObjectAddress
|
||||
ATExecAddIdentity(Relation rel, const char *colName,
|
||||
Node *def, LOCKMODE lockmode)
|
||||
{
|
||||
Relation attrelation;
|
||||
HeapTuple tuple;
|
||||
Form_pg_attribute attTup;
|
||||
AttrNumber attnum;
|
||||
ObjectAddress address;
|
||||
ColumnDef *cdef = castNode(ColumnDef, def);
|
||||
|
||||
attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
|
||||
|
||||
tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||
errmsg("column \"%s\" of relation \"%s\" does not exist",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
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)));
|
||||
|
||||
/*
|
||||
* Creating a column as identity implies NOT NULL, so adding the identity
|
||||
* to an existing column that is not NOT NULL would create a state that
|
||||
* cannot be reproduced without contortions.
|
||||
*/
|
||||
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))));
|
||||
|
||||
if (attTup->attidentity)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("column \"%s\" of relation \"%s\" is already an identity column",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
|
||||
if (attTup->atthasdef)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("column \"%s\" of relation \"%s\" already has a default value",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
|
||||
attTup->attidentity = cdef->identity;
|
||||
CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
|
||||
|
||||
InvokeObjectPostAlterHook(RelationRelationId,
|
||||
RelationGetRelid(rel),
|
||||
attTup->attnum);
|
||||
ObjectAddressSubSet(address, RelationRelationId,
|
||||
RelationGetRelid(rel), attnum);
|
||||
heap_freetuple(tuple);
|
||||
|
||||
heap_close(attrelation, RowExclusiveLock);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
static ObjectAddress
|
||||
ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode)
|
||||
{
|
||||
ListCell *option;
|
||||
DefElem *generatedEl = NULL;
|
||||
HeapTuple tuple;
|
||||
Form_pg_attribute attTup;
|
||||
AttrNumber attnum;
|
||||
Relation attrelation;
|
||||
ObjectAddress address;
|
||||
|
||||
foreach(option, castNode(List, def))
|
||||
{
|
||||
DefElem *defel = castNode(DefElem, lfirst(option));
|
||||
|
||||
if (strcmp(defel->defname, "generated") == 0)
|
||||
{
|
||||
if (generatedEl)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("conflicting or redundant options")));
|
||||
generatedEl = defel;
|
||||
}
|
||||
else
|
||||
elog(ERROR, "option \"%s\" not recognized",
|
||||
defel->defname);
|
||||
}
|
||||
|
||||
/*
|
||||
* Even if there is nothing to change here, we run all the checks. There
|
||||
* will be a subsequent ALTER SEQUENCE that relies on everything being
|
||||
* there.
|
||||
*/
|
||||
|
||||
attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
|
||||
tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||
errmsg("column \"%s\" of relation \"%s\" does not exist",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
|
||||
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
|
||||
attnum = attTup->attnum;
|
||||
|
||||
if (attnum <= 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot alter system column \"%s\"",
|
||||
colName)));
|
||||
|
||||
if (!attTup->attidentity)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("column \"%s\" of relation \"%s\" is not an identity column",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
|
||||
if (generatedEl)
|
||||
{
|
||||
attTup->attidentity = defGetInt32(generatedEl);
|
||||
CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
|
||||
|
||||
InvokeObjectPostAlterHook(RelationRelationId,
|
||||
RelationGetRelid(rel),
|
||||
attTup->attnum);
|
||||
ObjectAddressSubSet(address, RelationRelationId,
|
||||
RelationGetRelid(rel), attnum);
|
||||
}
|
||||
|
||||
heap_freetuple(tuple);
|
||||
heap_close(attrelation, RowExclusiveLock);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
static ObjectAddress
|
||||
ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode)
|
||||
{
|
||||
HeapTuple tuple;
|
||||
Form_pg_attribute attTup;
|
||||
AttrNumber attnum;
|
||||
Relation attrelation;
|
||||
ObjectAddress address;
|
||||
Oid seqid;
|
||||
ObjectAddress seqaddress;
|
||||
|
||||
attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
|
||||
tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||
errmsg("column \"%s\" of relation \"%s\" does not exist",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
|
||||
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
|
||||
attnum = attTup->attnum;
|
||||
|
||||
if (attnum <= 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot alter system column \"%s\"",
|
||||
colName)));
|
||||
|
||||
if (!attTup->attidentity)
|
||||
{
|
||||
if (!missing_ok)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("column \"%s\" of relation \"%s\" is not an identity column",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
else
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("column \"%s\" of relation \"%s\" is not an identity column, skipping",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
heap_freetuple(tuple);
|
||||
heap_close(attrelation, RowExclusiveLock);
|
||||
return InvalidObjectAddress;
|
||||
}
|
||||
}
|
||||
|
||||
attTup->attidentity = '\0';
|
||||
CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
|
||||
|
||||
InvokeObjectPostAlterHook(RelationRelationId,
|
||||
RelationGetRelid(rel),
|
||||
attTup->attnum);
|
||||
ObjectAddressSubSet(address, RelationRelationId,
|
||||
RelationGetRelid(rel), attnum);
|
||||
heap_freetuple(tuple);
|
||||
|
||||
heap_close(attrelation, RowExclusiveLock);
|
||||
|
||||
/* drop the internal sequence */
|
||||
seqid = getOwnedSequence(RelationGetRelid(rel), attnum);
|
||||
deleteDependencyRecordsForClass(RelationRelationId, seqid,
|
||||
RelationRelationId, DEPENDENCY_INTERNAL);
|
||||
CommandCounterIncrement();
|
||||
seqaddress.classId = RelationRelationId;
|
||||
seqaddress.objectId = seqid;
|
||||
seqaddress.objectSubId = 0;
|
||||
performDeletion(&seqaddress, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
/*
|
||||
* ALTER TABLE ALTER COLUMN SET STATISTICS
|
||||
*/
|
||||
@ -9539,7 +9820,8 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
|
||||
Oid tableId;
|
||||
int32 colId;
|
||||
|
||||
if (sequenceIsOwned(relationOid, &tableId, &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\"",
|
||||
@ -9810,7 +10092,7 @@ change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lock
|
||||
if (depForm->refobjsubid == 0 ||
|
||||
depForm->classid != RelationRelationId ||
|
||||
depForm->objsubid != 0 ||
|
||||
depForm->deptype != DEPENDENCY_AUTO)
|
||||
!(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL))
|
||||
continue;
|
||||
|
||||
/* Use relation_open just in case it's an index */
|
||||
@ -12115,7 +12397,8 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
|
||||
Oid tableId;
|
||||
int32 colId;
|
||||
|
||||
if (sequenceIsOwned(relid, &tableId, &colId))
|
||||
if (sequenceIsOwned(relid, DEPENDENCY_AUTO, &tableId, &colId) ||
|
||||
sequenceIsOwned(relid, DEPENDENCY_INTERNAL, &tableId, &colId))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot move an owned sequence into another schema"),
|
||||
@ -12298,7 +12581,7 @@ AlterIndexNamespaces(Relation classRel, Relation rel,
|
||||
}
|
||||
|
||||
/*
|
||||
* Move all SERIAL-column sequences of the specified relation to another
|
||||
* Move all identity and SERIAL-column sequences of the specified relation to another
|
||||
* namespace.
|
||||
*
|
||||
* Note: we assume adequate permission checking was done by the caller,
|
||||
@ -12342,7 +12625,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
|
||||
if (depForm->refobjsubid == 0 ||
|
||||
depForm->classid != RelationRelationId ||
|
||||
depForm->objsubid != 0 ||
|
||||
depForm->deptype != DEPENDENCY_AUTO)
|
||||
!(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL))
|
||||
continue;
|
||||
|
||||
/* Use relation_open just in case it's an index */
|
||||
|
Reference in New Issue
Block a user