diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 35e71004c1..c5ba6c4d97 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -1,4 +1,4 @@ - + Data Types @@ -705,17 +705,19 @@ CREATE TABLE tablename ( CREATE SEQUENCE tablename_colname_seq; CREATE TABLE tablename ( - colname integer DEFAULT nextval('tablename_colname_seq') NOT NULL + colname integer NOT NULL DEFAULT nextval('tablename_colname_seq') ); +ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname; Thus, we have created an integer column and arranged for its default values to be assigned from a sequence generator. A NOT NULL constraint is applied to ensure that a null value cannot be explicitly - inserted, either. In most cases you would also want to attach a + inserted, either. (In most cases you would also want to attach a UNIQUE or PRIMARY KEY constraint to prevent duplicate values from being inserted by accident, but this is - not automatic. + not automatic.) Lastly, the sequence is marked as owned by + the column, so that it will be dropped if the column or table is dropped. @@ -749,20 +751,9 @@ CREATE TABLE tablename ( The sequence created for a serial column is - automatically dropped when the owning column is dropped, and - cannot be dropped otherwise. (This was not true in - PostgreSQL releases before 7.3. Note - that this automatic drop linkage will not occur for a sequence - created by reloading a dump from a pre-7.3 database; the dump - file does not contain the information needed to establish the - dependency link.) Furthermore, this dependency between sequence - and column is made only for the serial column itself. If - any other columns reference the sequence (perhaps by manually - calling the nextval function), they will be broken - if the sequence is removed. Using a serial column's sequence - in such a fashion is considered bad form; if you wish to feed several - columns from the same sequence generator, create the sequence as an - independent object. + automatically dropped when the owning column is dropped. + You can drop the sequence without dropping the column, but this + will force removal of the column default expression. diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 113411f78b..2b2a0a0d18 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,4 +1,4 @@ - + Functions and Operators @@ -9863,10 +9863,14 @@ SELECT pg_type_is_visible('myschema.widget'::regtype); pg_get_serial_sequence fetches the name of the - sequence associated with a serial or bigserial - column. The name is suitably formatted for passing to the sequence - functions (see ). NULL is - returned if the column does not have an associated sequence. + sequence associated with a column, or NULL if there is no sequence + associated with the column. The result is suitably formatted for passing + to the sequence functions (see ). + This association can be modified or removed with ALTER SEQUENCE + OWNED BY. (The function probably should have been called + pg_get_owned_sequence; its name reflects the fact + that it's typically used with serial or bigserial + columns.) diff --git a/doc/src/sgml/ref/alter_sequence.sgml b/doc/src/sgml/ref/alter_sequence.sgml index 3e7937e185..3d6d5caf8f 100644 --- a/doc/src/sgml/ref/alter_sequence.sgml +++ b/doc/src/sgml/ref/alter_sequence.sgml @@ -1,5 +1,5 @@ @@ -27,6 +27,7 @@ PostgreSQL documentation ALTER SEQUENCE name [ INCREMENT [ BY ] increment ] [ MINVALUE minvalue | NO MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ] [ RESTART [ WITH ] start ] [ CACHE cache ] [ [ NO ] CYCLE ] + [ OWNED BY { table.column | NONE } ] ALTER SEQUENCE name SET SCHEMA new_schema @@ -163,6 +164,24 @@ ALTER SEQUENCE name SET SCHEMA + + OWNED BY table.column + OWNED BY NONE + + + The OWNED BY option causes the sequence to be + associated with a specific table column, such that if that column + (or its whole table) is dropped, the sequence will be automatically + dropped as well. If specified, this association replaces any + previously specified association for the sequence. The specified + table must have the same owner and be in the same schema as the + sequence. + Specifying OWNED BY NONE removes any existing + association, making the sequence free-standing. + + + + new_schema @@ -191,8 +210,11 @@ ALTER SEQUENCE serial RESTART WITH 105; To avoid blocking of concurrent transactions that obtain numbers from the - same sequence, ALTER SEQUENCE is never rolled back; - the changes take effect immediately and are not reversible. + same sequence, ALTER SEQUENCE's effects on the sequence + generation parameters are never rolled back; + those changes take effect immediately and are not reversible. However, + the OWNED BY and SET SCHEMA clauses are ordinary + catalog updates and can be rolled back. @@ -200,7 +222,8 @@ ALTER SEQUENCE serial RESTART WITH 105; nextval results in backends, other than the current one, that have preallocated (cached) sequence values. They will use up all cached values prior to noticing the changed - sequence parameters. The current backend will be affected immediately. + sequence generation parameters. The current backend will be affected + immediately. @@ -217,10 +240,20 @@ ALTER SEQUENCE serial RESTART WITH 105; ALTER SEQUENCE conforms to the SQL standard, - except for the SET SCHEMA variant, which is a - PostgreSQL extension. + except for the OWNED BY and SET SCHEMA + clauses, which are PostgreSQL extensions. + + + See Also + + + + + + + @@ -371,7 +371,7 @@ where action is one of: This form moves the table into another schema. Associated indexes, - constraints, and SERIAL-column sequences are moved as well. + constraints, and sequences owned by table columns are moved as well. diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml index 7a094f6ef2..5fa16c2a81 100644 --- a/doc/src/sgml/ref/create_sequence.sgml +++ b/doc/src/sgml/ref/create_sequence.sgml @@ -1,5 +1,5 @@ @@ -23,6 +23,7 @@ PostgreSQL documentation CREATE [ TEMPORARY | TEMP ] SEQUENCE name [ INCREMENT [ BY ] increment ] [ MINVALUE minvalue | NO MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ] [ START [ WITH ] start ] [ CACHE cache ] [ [ NO ] CYCLE ] + [ OWNED BY { table.column | NONE } ] @@ -193,6 +194,22 @@ SELECT * FROM name; + + + OWNED BY table.column + OWNED BY NONE + + + The OWNED BY option causes the sequence to be + associated with a specific table column, such that if that column + (or its whole table) is dropped, the sequence will be automatically + dropped as well. The specified table must have the same owner and be in + the same schema as the sequence. + OWNED BY NONE, the default, specifies that there + is no such association. + + + @@ -300,11 +317,38 @@ END; CREATE SEQUENCE conforms to the SQL standard, with the following exceptions: - The standard's AS <data type> expression is not supported. - Obtaining the next value is done using the nextval() function instead of the standard's NEXT VALUE FOR expression. + + + The standard's AS <data type> expression is not + supported. + + + + + Obtaining the next value is done using the nextval() + function instead of the standard's NEXT VALUE FOR + expression. + + + + + The OWNED BY clause is a PostgreSQL + extension. + + + + + See Also + + + + + + + @@ -105,6 +105,7 @@ DROP SEQUENCE serial; + diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c index 5a297d4a27..99cdf5e7e6 100644 --- a/src/backend/catalog/pg_depend.c +++ b/src/backend/catalog/pg_depend.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_depend.c,v 1.21 2006/07/11 17:26:58 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_depend.c,v 1.22 2006/08/21 00:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -162,58 +162,6 @@ deleteDependencyRecordsFor(Oid classId, Oid objectId) return count; } -/* - * objectIsInternalDependency -- return whether the specified object - * is listed as an internal dependency for some other object. - * - * This is used to implement DROP/REASSIGN OWNED. We cannot invoke - * performDeletion blindly, because it may try to drop or modify an internal- - * dependent object before the "main" object, so we need to skip the first - * object and expect it to be automatically dropped when the main object is - * dropped. - */ -bool -objectIsInternalDependency(Oid classId, Oid objectId) -{ - Relation depRel; - ScanKeyData key[2]; - SysScanDesc scan; - HeapTuple tup; - bool isdep = false; - - depRel = heap_open(DependRelationId, AccessShareLock); - - ScanKeyInit(&key[0], - Anum_pg_depend_classid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(classId)); - ScanKeyInit(&key[1], - Anum_pg_depend_objid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(objectId)); - - scan = systable_beginscan(depRel, DependDependerIndexId, true, - SnapshotNow, 2, key); - - while (HeapTupleIsValid(tup = systable_getnext(scan))) - { - Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup); - - if (depForm->deptype == DEPENDENCY_INTERNAL) - { - /* No need to keep scanning */ - isdep = true; - break; - } - } - - systable_endscan(scan); - - heap_close(depRel, AccessShareLock); - - return isdep; -} - /* * Adjust dependency record(s) to point to a different object of the same type * @@ -312,6 +260,105 @@ changeDependencyFor(Oid classId, Oid objectId, return count; } +/* + * Detect whether a sequence is marked as "owned" by a column + * + * An ownership marker is an AUTO dependency from the sequence to the + * column. If we find one, store the identity of the owning column + * into *tableId and *colId and return TRUE; else return FALSE. + * + * Note: if there's more than one such pg_depend entry then you get + * a random one of them returned into the out parameters. This should + * not happen, though. + */ +bool +sequenceIsOwned(Oid seqId, Oid *tableId, int32 *colId) +{ + bool ret = false; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = heap_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(seqId)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + SnapshotNow, 2, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->refclassid == RelationRelationId && + depform->deptype == DEPENDENCY_AUTO) + { + *tableId = depform->refobjid; + *colId = depform->refobjsubid; + ret = true; + break; /* no need to keep scanning */ + } + } + + systable_endscan(scan); + + heap_close(depRel, AccessShareLock); + + return ret; +} + +/* + * Remove any existing "owned" markers for the specified sequence. + * + * Note: we don't provide a special function to install an "owned" + * marker; just use recordDependencyOn(). + */ +void +markSequenceUnowned(Oid seqId) +{ + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = heap_open(DependRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(seqId)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + SnapshotNow, 2, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->refclassid == RelationRelationId && + depform->deptype == DEPENDENCY_AUTO) + { + simple_heap_delete(depRel, &tup->t_self); + } + } + + systable_endscan(scan); + + heap_close(depRel, RowExclusiveLock); +} + /* * isObjectPinned() * diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c index 31f1f654de..85e3d968d4 100644 --- a/src/backend/catalog/pg_shdepend.c +++ b/src/backend/catalog/pg_shdepend.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.13 2006/08/20 21:56:16 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.14 2006/08/21 00:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,6 +17,7 @@ #include "access/genam.h" #include "access/heapam.h" #include "access/xact.h" +#include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/pg_authid.h" @@ -869,30 +870,17 @@ shdepDropDependency(Relation sdepRel, Oid classId, Oid objectId, * Get the database Id that should be used in pg_shdepend, given the OID * of the catalog containing the object. For shared objects, it's 0 * (InvalidOid); for all other objects, it's the current database Id. - * - * XXX it's awfully tempting to hard-wire this instead of doing a syscache - * lookup ... but resist the temptation, unless you can prove it's a - * bottleneck. */ static Oid classIdGetDbId(Oid classId) { Oid dbId; - HeapTuple tup; - tup = SearchSysCache(RELOID, - ObjectIdGetDatum(classId), - 0, 0, 0); - if (!HeapTupleIsValid(tup)) - elog(ERROR, "cache lookup failed for relation %u", classId); - - if (((Form_pg_class) GETSTRUCT(tup))->relisshared) + if (IsSharedRelation(classId)) dbId = InvalidOid; else dbId = MyDatabaseId; - ReleaseSysCache(tup); - return dbId; } @@ -1055,6 +1043,11 @@ isSharedObjectPinned(Oid classId, Oid objectId, Relation sdepRel) * Drop the objects owned by any one of the given RoleIds. If a role has * access to an object, the grant will be removed as well (but the object * will not, of course.) + * + * We can revoke grants immediately while doing the scan, but drops are + * saved up and done all at once with performMultipleDeletions. This + * is necessary so that we don't get failures from trying to delete + * interdependent objects in the wrong order. */ void shdepDropOwned(List *roleids, DropBehavior behavior) @@ -1113,7 +1106,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior) InternalGrant istmt; Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tuple); - /* We only operate on objects on the current database */ + /* We only operate on objects in the current database */ if (sdepForm->dbid != MyDatabaseId) continue; @@ -1128,24 +1121,8 @@ shdepDropOwned(List *roleids, DropBehavior behavior) switch (sdepForm->classid) { case RelationRelationId: - { - /* is it a sequence or non-sequence? */ - Form_pg_class pg_class_tuple; - HeapTuple tuple; - - tuple = SearchSysCache(RELOID, - ObjectIdGetDatum(sdepForm->objid), - 0, 0, 0); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "cache lookup failed for relation %u", - sdepForm->objid); - pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple); - if (pg_class_tuple->relkind == RELKIND_SEQUENCE) - istmt.objtype = ACL_OBJECT_SEQUENCE; - else - istmt.objtype = ACL_OBJECT_RELATION; - ReleaseSysCache(tuple); - } + /* it's OK to use RELATION for a sequence */ + istmt.objtype = ACL_OBJECT_RELATION; break; case DatabaseRelationId: istmt.objtype = ACL_OBJECT_DATABASE; @@ -1180,11 +1157,10 @@ shdepDropOwned(List *roleids, DropBehavior behavior) ExecGrantStmt_oids(&istmt); break; case SHARED_DEPENDENCY_OWNER: - /* Save it for later deleting it */ + /* Save it for deletion below */ obj.classId = sdepForm->classid; obj.objectId = sdepForm->objid; obj.objectSubId = 0; - add_exact_object_address(&obj, deleteobjs); break; } @@ -1259,7 +1235,7 @@ shdepReassignOwned(List *roleids, Oid newrole) { Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tuple); - /* We only operate on objects on the current database */ + /* We only operate on objects in the current database */ if (sdepForm->dbid != MyDatabaseId) continue; @@ -1271,15 +1247,7 @@ shdepReassignOwned(List *roleids, Oid newrole) if (sdepForm->deptype != SHARED_DEPENDENCY_OWNER) continue; - /* - * If there's a regular (non-shared) dependency on this object - * marked with DEPENDENCY_INTERNAL, skip this object. We will - * alter the referencer object instead. - */ - if (objectIsInternalDependency(sdepForm->classid, sdepForm->objid)) - continue; - - /* Issue the appropiate ALTER OWNER call */ + /* Issue the appropriate ALTER OWNER call */ switch (sdepForm->classid) { case ConversionRelationId: @@ -1299,7 +1267,12 @@ shdepReassignOwned(List *roleids, Oid newrole) break; case RelationRelationId: - ATExecChangeOwner(sdepForm->objid, newrole, false); + /* + * Pass recursing = true so that we don't fail on + * indexes, owned sequences, etc when we happen + * to visit them before their parent table. + */ + ATExecChangeOwner(sdepForm->objid, newrole, true); break; case ProcedureRelationId: diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 6154a4ed3d..865c2f60fe 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/sequence.c,v 1.138 2006/07/31 20:09:00 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/sequence.c,v 1.139 2006/08/21 00:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,6 +17,7 @@ #include "access/heapam.h" #include "access/transam.h" #include "access/xact.h" +#include "catalog/dependency.h" #include "catalog/namespace.h" #include "catalog/pg_type.h" #include "commands/defrem.h" @@ -26,6 +27,7 @@ #include "nodes/makefuncs.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/lsyscache.h" #include "utils/resowner.h" #include "utils/syscache.h" @@ -82,8 +84,11 @@ static int64 nextval_internal(Oid relid); static Relation open_share_lock(SeqTable seq); static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel); static Form_pg_sequence read_info(SeqTable elm, Relation rel, Buffer *buf); -static void init_params(List *options, Form_pg_sequence new, bool isInit); +static void init_params(List *options, bool isInit, + Form_pg_sequence new, List **owned_by); static void do_setval(Oid relid, int64 next, bool iscalled); +static void process_owned_by(Relation seqrel, List *owned_by); + /* * DefineSequence @@ -93,6 +98,7 @@ void DefineSequence(CreateSeqStmt *seq) { FormData_pg_sequence new; + List *owned_by; CreateStmt *stmt = makeNode(CreateStmt); Oid seqoid; Relation rel; @@ -107,7 +113,7 @@ DefineSequence(CreateSeqStmt *seq) NameData name; /* Check and set all option values */ - init_params(seq->options, &new, true); + init_params(seq->options, true, &new, &owned_by); /* * Create relation (and fill *null & *value) @@ -123,7 +129,6 @@ DefineSequence(CreateSeqStmt *seq) coldef->raw_default = NULL; coldef->cooked_default = NULL; coldef->constraints = NIL; - coldef->support = NULL; null[i - 1] = ' '; @@ -287,6 +292,10 @@ DefineSequence(CreateSeqStmt *seq) UnlockReleaseBuffer(buf); + /* process OWNED BY if given */ + if (owned_by) + process_owned_by(rel, owned_by); + heap_close(rel, NoLock); } @@ -305,6 +314,7 @@ AlterSequence(AlterSeqStmt *stmt) Page page; Form_pg_sequence seq; FormData_pg_sequence new; + List *owned_by; /* open and AccessShareLock sequence */ relid = RangeVarGetRelid(stmt->sequence, false); @@ -323,7 +333,7 @@ AlterSequence(AlterSeqStmt *stmt) memcpy(&new, seq, sizeof(FormData_pg_sequence)); /* Check and set new values */ - init_params(stmt->options, &new, false); + init_params(stmt->options, false, &new, &owned_by); /* Now okay to update the on-disk tuple */ memcpy(seq, &new, sizeof(FormData_pg_sequence)); @@ -366,6 +376,10 @@ AlterSequence(AlterSeqStmt *stmt) UnlockReleaseBuffer(buf); + /* process OWNED BY if given */ + if (owned_by) + process_owned_by(seqrel, owned_by); + relation_close(seqrel, NoLock); } @@ -933,13 +947,15 @@ read_info(SeqTable elm, Relation rel, Buffer *buf) /* * init_params: process the options list of CREATE or ALTER SEQUENCE, - * and store the values into appropriate fields of *new. + * and store the values into appropriate fields of *new. Also set + * *owned_by to any OWNED BY option, or to NIL if there is none. * * If isInit is true, fill any unspecified options with default values; * otherwise, do not change existing options that aren't explicitly overridden. */ static void -init_params(List *options, Form_pg_sequence new, bool isInit) +init_params(List *options, bool isInit, + Form_pg_sequence new, List **owned_by) { DefElem *last_value = NULL; DefElem *increment_by = NULL; @@ -949,6 +965,8 @@ init_params(List *options, Form_pg_sequence new, bool isInit) DefElem *is_cycled = NULL; ListCell *option; + *owned_by = NIL; + foreach(option, options) { DefElem *defel = (DefElem *) lfirst(option); @@ -1006,6 +1024,14 @@ init_params(List *options, Form_pg_sequence new, bool isInit) errmsg("conflicting or redundant options"))); is_cycled = defel; } + else if (strcmp(defel->defname, "owned_by") == 0) + { + if (*owned_by) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + *owned_by = defGetQualifiedName(defel); + } else elog(ERROR, "option \"%s\" not recognized", defel->defname); @@ -1130,6 +1156,99 @@ init_params(List *options, Form_pg_sequence new, bool isInit) new->cache_value = 1; } +/* + * Process an OWNED BY option for CREATE/ALTER SEQUENCE + * + * Ownership permissions on the sequence are already checked, + * but if we are establishing a new owned-by dependency, we must + * enforce that the referenced table has the same owner and namespace + * as the sequence. + */ +static void +process_owned_by(Relation seqrel, List *owned_by) +{ + int nnames; + Relation tablerel; + AttrNumber attnum; + + nnames = list_length(owned_by); + Assert(nnames > 0); + if (nnames == 1) + { + /* Must be OWNED BY NONE */ + if (strcmp(strVal(linitial(owned_by)), "none") != 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid OWNED BY option"), + errhint("Specify OWNED BY table.column or OWNED BY NONE."))); + tablerel = NULL; + attnum = 0; + } + else + { + List *relname; + char *attrname; + RangeVar *rel; + + /* Separate relname and attr name */ + relname = list_truncate(list_copy(owned_by), nnames - 1); + attrname = strVal(lfirst(list_tail(owned_by))); + + /* Open and lock rel to ensure it won't go away meanwhile */ + rel = makeRangeVarFromNameList(relname); + tablerel = relation_openrv(rel, AccessShareLock); + + /* Must be a regular table */ + if (tablerel->rd_rel->relkind != RELKIND_RELATION) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("referenced relation \"%s\" is not a table", + RelationGetRelationName(tablerel)))); + + /* We insist on same owner and schema */ + if (seqrel->rd_rel->relowner != tablerel->rd_rel->relowner) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("sequence must have same owner as table it is owned by"))); + if (RelationGetNamespace(seqrel) != RelationGetNamespace(tablerel)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("sequence must be in same schema as table it is owned by"))); + + /* Now, fetch the attribute number from the system cache */ + attnum = get_attnum(RelationGetRelid(tablerel), attrname); + if (attnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + attrname, RelationGetRelationName(tablerel)))); + } + + /* + * OK, we are ready to update pg_depend. First remove any existing + * AUTO dependencies for the sequence, then optionally add a new one. + */ + markSequenceUnowned(RelationGetRelid(seqrel)); + + if (tablerel) + { + ObjectAddress refobject, + depobject; + + refobject.classId = RelationRelationId; + refobject.objectId = RelationGetRelid(tablerel); + refobject.objectSubId = attnum; + depobject.classId = RelationRelationId; + depobject.objectId = RelationGetRelid(seqrel); + depobject.objectSubId = 0; + recordDependencyOn(&depobject, &refobject, DEPENDENCY_AUTO); + } + + /* Done, but hold lock until commit */ + if (tablerel) + relation_close(tablerel, NoLock); +} + void seq_redo(XLogRecPtr lsn, XLogRecord *record) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index cd4c4eb230..a1f7603337 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.199 2006/08/03 20:57:06 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.200 2006/08/21 00:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -209,8 +209,6 @@ static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, static void ATExecAddColumn(AlteredTableInfo *tab, Relation rel, ColumnDef *colDef); static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid); -static void add_column_support_dependency(Oid relid, int32 attnum, - RangeVar *support); static void ATExecDropNotNull(Relation rel, const char *colName); static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, const char *colName); @@ -476,10 +474,6 @@ DefineRelation(CreateStmt *stmt, char relkind) * work unless we have a pre-existing relation. So, the transformation has * to be postponed to this final step of CREATE TABLE. * - * Another task that's conveniently done at this step is to add dependency - * links between columns and supporting relations (such as SERIAL - * sequences). - * * First, scan schema to find new column defaults. */ rawDefaults = NIL; @@ -502,10 +496,6 @@ DefineRelation(CreateStmt *stmt, char relkind) rawEnt->raw_default = colDef->raw_default; rawDefaults = lappend(rawDefaults, rawEnt); } - - /* Create dependency for supporting relation for this column */ - if (colDef->support != NULL) - add_column_support_dependency(relationId, attnum, colDef->support); } /* @@ -944,7 +934,6 @@ MergeAttributes(List *schema, List *supers, bool istemp, def->raw_default = NULL; def->cooked_default = NULL; def->constraints = NIL; - def->support = NULL; inhSchema = lappend(inhSchema, def); newattno[parent_attno - 1] = ++child_attno; } @@ -1159,9 +1148,10 @@ varattnos_map(TupleDesc old, TupleDesc new) return attmap; } -/* Generate a map for change_varattnos_of_a_node from a tupledesc and a list of - * ColumnDefs */ - +/* + * Generate a map for change_varattnos_of_a_node from a tupledesc and a list of + * ColumnDefs + */ AttrNumber * varattnos_map_schema(TupleDesc old, List *schema) { @@ -3017,8 +3007,6 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, /* Child should see column as singly inherited */ colDefChild->inhcount = 1; colDefChild->is_local = false; - /* and don't make a support dependency on the child */ - colDefChild->support = NULL; ATOneLevelRecursion(wqueue, rel, childCmd); } @@ -3259,8 +3247,6 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel, * Add needed dependency entries for the new column. */ add_column_datatype_dependency(myrelid, i, attribute->atttypid); - if (colDef->support != NULL) - add_column_support_dependency(myrelid, i, colDef->support); } /* @@ -3281,24 +3267,6 @@ add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid) recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } -/* - * Install a dependency for a column's supporting relation (serial sequence). - */ -static void -add_column_support_dependency(Oid relid, int32 attnum, RangeVar *support) -{ - ObjectAddress colobject, - suppobject; - - colobject.classId = RelationRelationId; - colobject.objectId = relid; - colobject.objectSubId = attnum; - suppobject.classId = RelationRelationId; - suppobject.objectId = RangeVarGetRelid(support, false); - suppobject.objectSubId = 0; - recordDependencyOn(&suppobject, &colobject, DEPENDENCY_INTERNAL); -} - /* * ALTER TABLE ALTER COLUMN DROP NOT NULL */ @@ -5444,9 +5412,9 @@ ATPostAlterTypeParse(char *cmd, List **wqueue) /* * ALTER TABLE OWNER * - * recursing is true if we are recursing from a table to its indexes or - * toast table. We don't allow the ownership of those things to be - * changed separately from the parent table. Also, we can skip permission + * recursing is true if we are recursing from a table to its indexes, + * sequences, or toast table. We don't allow the ownership of those things to + * be changed separately from the parent table. Also, we can skip permission * checks (this is necessary not just an optimization, else we'd fail to * handle toast tables properly). */ @@ -5479,7 +5447,6 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing) { case RELKIND_RELATION: case RELKIND_VIEW: - case RELKIND_SEQUENCE: /* ok to change owner */ break; case RELKIND_INDEX: @@ -5502,6 +5469,24 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing) newOwnerId = tuple_class->relowner; } 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, &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_TOASTVALUE: if (recursing) break; @@ -5644,7 +5629,7 @@ change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId) HeapTuple tup; /* - * SERIAL sequences are those having an internal dependency on one of the + * SERIAL sequences are those having an auto dependency on one of the * table's columns (we don't care *which* column, exactly). */ depRel = heap_open(DependRelationId, AccessShareLock); @@ -5667,11 +5652,11 @@ change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId) Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup); Relation seqRel; - /* skip dependencies other than internal dependencies on columns */ + /* skip dependencies other than auto dependencies on columns */ if (depForm->refobjsubid == 0 || depForm->classid != RelationRelationId || depForm->objsubid != 0 || - depForm->deptype != DEPENDENCY_INTERNAL) + depForm->deptype != DEPENDENCY_AUTO) continue; /* Use relation_open just in case it's an index */ @@ -5686,7 +5671,7 @@ change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId) } /* We don't need to close the sequence while we alter it. */ - ATExecChangeOwner(depForm->objid, newOwnerId, false); + ATExecChangeOwner(depForm->objid, newOwnerId, true); /* Now we can close it. Keep the lock till end of transaction. */ relation_close(seqRel, NoLock); @@ -6549,6 +6534,9 @@ AlterTableNamespace(RangeVar *relation, const char *newschema) rel = heap_openrv(relation, AccessExclusiveLock); + relid = RelationGetRelid(rel); + oldNspOid = RelationGetNamespace(rel); + /* heap_openrv allows TOAST, but we don't want to */ if (rel->rd_rel->relkind == RELKIND_TOASTVALUE) ereport(ERROR, @@ -6556,8 +6544,20 @@ AlterTableNamespace(RangeVar *relation, const char *newschema) errmsg("\"%s\" is a TOAST relation", RelationGetRelationName(rel)))); - relid = RelationGetRelid(rel); - oldNspOid = RelationGetNamespace(rel); + /* if it's an owned sequence, disallow moving it by itself */ + if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + { + Oid tableId; + int32 colId; + + if (sequenceIsOwned(relid, &tableId, &colId)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot move an owned sequence into another schema"), + errdetail("Sequence \"%s\" is linked to table \"%s\".", + RelationGetRelationName(rel), + get_rel_name(tableId)))); + } /* get schema OID and check its permissions */ nspOid = LookupCreationNamespace(newschema); @@ -6699,7 +6699,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel, HeapTuple tup; /* - * SERIAL sequences are those having an internal dependency on one of the + * SERIAL sequences are those having an auto dependency on one of the * table's columns (we don't care *which* column, exactly). */ depRel = heap_open(DependRelationId, AccessShareLock); @@ -6722,11 +6722,11 @@ AlterSeqNamespaces(Relation classRel, Relation rel, Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup); Relation seqRel; - /* skip dependencies other than internal dependencies on columns */ + /* skip dependencies other than auto dependencies on columns */ if (depForm->refobjsubid == 0 || depForm->classid != RelationRelationId || depForm->objsubid != 0 || - depForm->deptype != DEPENDENCY_INTERNAL) + depForm->deptype != DEPENDENCY_AUTO) continue; /* Use relation_open just in case it's an index */ diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index 63850cbeae..df7f479f31 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.96 2006/07/13 16:49:14 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.97 2006/08/21 00:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -126,7 +126,6 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace) def->raw_default = NULL; def->cooked_default = NULL; def->constraints = NIL; - def->support = NULL; attrList = lappend(attrList, def); } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 2b8f3af09b..40e35a3796 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.347 2006/08/12 02:52:04 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.348 2006/08/21 00:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1641,7 +1641,6 @@ _copyColumnDef(ColumnDef *from) COPY_NODE_FIELD(raw_default); COPY_STRING_FIELD(cooked_default); COPY_NODE_FIELD(constraints); - COPY_NODE_FIELD(support); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 665d4833be..b4d0632c03 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.281 2006/08/12 02:52:04 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.282 2006/08/21 00:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1665,7 +1665,6 @@ _equalColumnDef(ColumnDef *a, ColumnDef *b) COMPARE_NODE_FIELD(raw_default); COMPARE_STRING_FIELD(cooked_default); COMPARE_NODE_FIELD(constraints); - COMPARE_NODE_FIELD(support); return true; } diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 784dd57831..7555cbd0dd 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/makefuncs.c,v 1.50 2006/03/14 22:48:19 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/makefuncs.c,v 1.51 2006/08/21 00:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -322,3 +322,17 @@ makeFuncExpr(Oid funcid, Oid rettype, List *args, CoercionForm fformat) return funcexpr; } + +/* + * makeDefElem - + * build a DefElem node + */ +DefElem * +makeDefElem(char *name, Node *arg) +{ + DefElem *res = makeNode(DefElem); + + res->defname = name; + res->arg = arg; + return res; +} diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index a1ed403d79..a4b4044385 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.282 2006/08/12 02:52:04 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.283 2006/08/21 00:57:24 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -1444,7 +1444,6 @@ _outColumnDef(StringInfo str, ColumnDef *node) WRITE_NODE_FIELD(raw_default); WRITE_STRING_FIELD(cooked_default); WRITE_NODE_FIELD(constraints); - WRITE_NODE_FIELD(support); } static void diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 39c7372733..8eb50fb573 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.346 2006/08/12 20:05:55 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.347 2006/08/21 00:57:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -980,6 +980,14 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt, } } + /* + * transformIndexConstraints wants cxt.alist to contain only index + * statements, so transfer anything we already have into extras_after + * immediately. + */ + *extras_after = list_concat(cxt.alist, *extras_after); + cxt.alist = NIL; + Assert(stmt->constraints == NIL); /* @@ -1052,6 +1060,8 @@ transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt, A_Const *snamenode; FuncCall *funccallnode; CreateSeqStmt *seqstmt; + AlterSeqStmt *altseqstmt; + List *attnamelist; /* * Determine namespace and name to use for the sequence. @@ -1088,10 +1098,19 @@ transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt, cxt->blist = lappend(cxt->blist, seqstmt); /* - * Mark the ColumnDef so that during execution, an appropriate - * dependency will be added from the sequence to the column. + * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence + * as owned by this column, and add it to the list of things to be + * done after this CREATE/ALTER TABLE. */ - column->support = makeRangeVar(snamespace, sname); + altseqstmt = makeNode(AlterSeqStmt); + altseqstmt->sequence = makeRangeVar(snamespace, sname); + attnamelist = list_make3(makeString(snamespace), + makeString(cxt->relation->relname), + makeString(column->colname)); + altseqstmt->options = list_make1(makeDefElem("owned_by", + (Node *) attnamelist)); + + cxt->alist = lappend(cxt->alist, altseqstmt); /* * Create appropriate constraints for SERIAL. We do this in full, @@ -1349,7 +1368,6 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, def->raw_default = NULL; def->cooked_default = NULL; def->constraints = NIL; - def->support = NULL; /* * Add to column list @@ -1604,7 +1622,7 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt) * XXX in ALTER TABLE case, it'd be nice to look for duplicate * pre-existing indexes, too. */ - cxt->alist = NIL; + Assert(cxt->alist == NIL); if (cxt->pkey != NULL) { /* Make sure we keep the PKEY index in preference to others... */ @@ -3041,6 +3059,14 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt, } } + /* + * transformIndexConstraints wants cxt.alist to contain only index + * statements, so transfer anything we already have into extras_after + * immediately. + */ + *extras_after = list_concat(cxt.alist, *extras_after); + cxt.alist = NIL; + /* Postprocess index and FK constraints */ transformIndexConstraints(pstate, &cxt); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c0744a74a4..60761ae6bc 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.556 2006/08/12 18:58:54 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.557 2006/08/21 00:57:25 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -95,7 +95,6 @@ static Node *makeIntConst(int val); static Node *makeFloatConst(char *str); static Node *makeAConst(Value *v); static Node *makeRowNullTest(NullTestType test, RowExpr *row); -static DefElem *makeDefElem(char *name, Node *arg); static A_Const *makeBoolAConst(bool state); static FuncCall *makeOverlaps(List *largs, List *rargs, int location); static void check_qualified_name(List *names); @@ -2275,7 +2274,6 @@ CreateAsElement: n->raw_default = NULL; n->cooked_default = NULL; n->constraints = NIL; - n->support = NULL; $$ = (Node *)n; } ; @@ -2346,6 +2344,10 @@ OptSeqElem: CACHE NumericOnly { $$ = makeDefElem("minvalue", NULL); } + | OWNED BY any_name + { + $$ = makeDefElem("owned_by", (Node *)$3); + } | START opt_with NumericOnly { $$ = makeDefElem("start", (Node *)$3); @@ -8977,19 +8979,6 @@ makeAConst(Value *v) return n; } -/* makeDefElem() - * Create a DefElem node and set contents. - * Could be moved to nodes/makefuncs.c if this is useful elsewhere. - */ -static DefElem * -makeDefElem(char *name, Node *arg) -{ - DefElem *f = makeNode(DefElem); - f->defname = name; - f->arg = arg; - return f; -} - /* makeBoolAConst() * Create an A_Const node and initialize to a boolean constant. */ diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index e814310120..a88f5ef93c 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -2,7 +2,7 @@ * ruleutils.c - Functions to convert stored expressions/querytrees * back to source text * - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.231 2006/08/12 02:52:05 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.232 2006/08/21 00:57:25 tgl Exp $ **********************************************************************/ #include "postgres.h" @@ -1287,12 +1287,12 @@ pg_get_serial_sequence(PG_FUNCTION_ARGS) Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup); /* - * We assume any internal dependency of a relation on a column must be + * We assume any auto dependency of a relation on a column must be * what we are looking for. */ if (deprec->classid == RelationRelationId && deprec->objsubid == 0 && - deprec->deptype == DEPENDENCY_INTERNAL) + deprec->deptype == DEPENDENCY_AUTO) { sequenceId = deprec->objid; break; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d48cd255c7..78a653fb48 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12,7 +12,7 @@ * by PostgreSQL * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.446 2006/08/04 18:32:15 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.447 2006/08/21 00:57:25 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2833,14 +2833,14 @@ getTables(int *numTables) * Note: in this phase we should collect only a minimal amount of * information about each table, basically just enough to decide if it is * interesting. We must fetch all tables in this phase because otherwise - * we cannot correctly identify inherited columns, serial columns, etc. + * we cannot correctly identify inherited columns, owned sequences, etc. */ if (g_fout->remoteVersion >= 80200) { /* * Left join to pick up dependency info linking sequences to their - * serial column, if any + * owning column, if any (note this dependency is AUTO as of 8.2) */ appendPQExpBuffer(query, "SELECT c.tableoid, c.oid, relname, " @@ -2857,7 +2857,7 @@ getTables(int *numTables) "(c.relkind = '%c' and " "d.classid = c.tableoid and d.objid = c.oid and " "d.objsubid = 0 and " - "d.refclassid = c.tableoid and d.deptype = 'i') " + "d.refclassid = c.tableoid and d.deptype = 'a') " "where relkind in ('%c', '%c', '%c', '%c') " "order by c.oid", username_subquery, @@ -2869,7 +2869,7 @@ getTables(int *numTables) { /* * Left join to pick up dependency info linking sequences to their - * serial column, if any + * owning column, if any */ appendPQExpBuffer(query, "SELECT c.tableoid, c.oid, relname, " @@ -2898,7 +2898,7 @@ getTables(int *numTables) { /* * Left join to pick up dependency info linking sequences to their - * serial column, if any + * owning column, if any */ appendPQExpBuffer(query, "SELECT c.tableoid, c.oid, relname, " @@ -3061,14 +3061,10 @@ getTables(int *numTables) /* other fields were zeroed above */ /* - * Decide whether we want to dump this table. Sequences owned by - * serial columns are never dumpable on their own; we will transpose - * their owning table's dump flag to them below. + * Decide whether we want to dump this table. */ if (tblinfo[i].relkind == RELKIND_COMPOSITE_TYPE) tblinfo[i].dobj.dump = false; - else if (OidIsValid(tblinfo[i].owning_tab)) - tblinfo[i].dobj.dump = false; else selectDumpableTable(&tblinfo[i]); tblinfo[i].interesting = tblinfo[i].dobj.dump; @@ -3101,6 +3097,36 @@ getTables(int *numTables) } PQclear(res); + + /* + * Force sequences that are "owned" by table columns to be dumped + * whenever their owning table is being dumped. + */ + for (i = 0; i < ntups; i++) + { + TableInfo *seqinfo = &tblinfo[i]; + int j; + + if (!OidIsValid(seqinfo->owning_tab)) + continue; /* not an owned sequence */ + if (seqinfo->dobj.dump) + continue; /* no need to search */ + + /* can't use findTableByOid yet, unfortunately */ + for (j = 0; j < ntups; j++) + { + if (tblinfo[j].dobj.catId.oid == seqinfo->owning_tab) + { + if (tblinfo[j].dobj.dump) + { + seqinfo->interesting = true; + seqinfo->dobj.dump = true; + } + break; + } + } + } + destroyPQExpBuffer(query); destroyPQExpBuffer(delqry); destroyPQExpBuffer(lockquery); @@ -4161,8 +4187,7 @@ void getTableAttrs(TableInfo *tblinfo, int numTables) { int i, - j, - k; + j; PQExpBuffer q = createPQExpBuffer(); int i_attnum; int i_attname; @@ -4281,7 +4306,6 @@ getTableAttrs(TableInfo *tblinfo, int numTables) tbinfo->typstorage = (char *) malloc(ntups * sizeof(char)); tbinfo->attisdropped = (bool *) malloc(ntups * sizeof(bool)); tbinfo->attislocal = (bool *) malloc(ntups * sizeof(bool)); - tbinfo->attisserial = (bool *) malloc(ntups * sizeof(bool)); tbinfo->notnull = (bool *) malloc(ntups * sizeof(bool)); tbinfo->attrdefs = (AttrDefInfo **) malloc(ntups * sizeof(AttrDefInfo *)); tbinfo->inhAttrs = (bool *) malloc(ntups * sizeof(bool)); @@ -4305,7 +4329,6 @@ getTableAttrs(TableInfo *tblinfo, int numTables) tbinfo->typstorage[j] = *(PQgetvalue(res, j, i_typstorage)); tbinfo->attisdropped[j] = (PQgetvalue(res, j, i_attisdropped)[0] == 't'); tbinfo->attislocal[j] = (PQgetvalue(res, j, i_attislocal)[0] == 't'); - tbinfo->attisserial[j] = false; /* fix below */ tbinfo->notnull[j] = (PQgetvalue(res, j, i_attnotnull)[0] == 't'); tbinfo->attrdefs[j] = NULL; /* fix below */ if (PQgetvalue(res, j, i_atthasdef)[0] == 't') @@ -4538,44 +4561,6 @@ getTableAttrs(TableInfo *tblinfo, int numTables) } PQclear(res); } - - /* - * Check to see if any columns are serial columns. Our first quick - * filter is that it must be integer or bigint with a default. If so, - * we scan to see if we found a sequence linked to this column. If we - * did, mark the column and sequence appropriately. - */ - for (j = 0; j < ntups; j++) - { - /* - * Note assumption that format_type will show these types as - * exactly "integer" and "bigint" regardless of schema path. This - * is correct in 7.3 but needs to be watched. - */ - if (strcmp(tbinfo->atttypnames[j], "integer") != 0 && - strcmp(tbinfo->atttypnames[j], "bigint") != 0) - continue; - if (tbinfo->attrdefs[j] == NULL) - continue; - for (k = 0; k < numTables; k++) - { - TableInfo *seqinfo = &tblinfo[k]; - - if (OidIsValid(seqinfo->owning_tab) && - seqinfo->owning_tab == tbinfo->dobj.catId.oid && - seqinfo->owning_col == j + 1) - { - /* - * Found a match. Copy the table's interesting and - * dumpable flags to the sequence. - */ - tbinfo->attisserial[j] = true; - seqinfo->interesting = tbinfo->interesting; - seqinfo->dobj.dump = tbinfo->dobj.dump; - break; - } - } - } } destroyPQExpBuffer(q); @@ -7403,16 +7388,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) /* Attribute type */ if (g_fout->remoteVersion >= 70100) { - char *typname = tbinfo->atttypnames[j]; - - if (tbinfo->attisserial[j]) - { - if (strcmp(typname, "integer") == 0) - typname = "serial"; - else if (strcmp(typname, "bigint") == 0) - typname = "bigserial"; - } - appendPQExpBuffer(q, "%s", typname); + appendPQExpBuffer(q, "%s", + tbinfo->atttypnames[j]); } else { @@ -7423,24 +7400,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) } /* - * Default value --- suppress if inherited, serial, or to be + * Default value --- suppress if inherited or to be * printed separately. */ if (tbinfo->attrdefs[j] != NULL && !tbinfo->inhAttrDef[j] && - !tbinfo->attisserial[j] && !tbinfo->attrdefs[j]->separate) appendPQExpBuffer(q, " DEFAULT %s", tbinfo->attrdefs[j]->adef_expr); /* * Not Null constraint --- suppress if inherited - * - * Note: we could suppress this for serial columns since - * SERIAL implies NOT NULL. We choose not to for forward - * compatibility, since there has been some talk of making - * SERIAL not imply NOT NULL, in which case the explicit - * specification would be needed. */ if (tbinfo->notnull[j] && !tbinfo->inhNotNull[j]) appendPQExpBuffer(q, " NOT NULL"); @@ -7597,8 +7567,8 @@ dumpAttrDef(Archive *fout, AttrDefInfo *adinfo) if (!tbinfo->dobj.dump || !adinfo->separate || dataOnly) return; - /* Don't print inherited or serial defaults, either */ - if (tbinfo->inhAttrDef[adnum - 1] || tbinfo->attisserial[adnum - 1]) + /* Don't print inherited defaults, either */ + if (tbinfo->inhAttrDef[adnum - 1]) return; q = createPQExpBuffer(); @@ -8111,15 +8081,14 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) /* * The logic we use for restoring sequences is as follows: * - * Add a basic CREATE SEQUENCE statement (use last_val for start if called - * is false, else use min_val for start_val). Skip this if the sequence - * came from a SERIAL column. + * Add a CREATE SEQUENCE statement as part of a "schema" dump + * (use last_val for start if called is false, else use min_val for + * start_val). Also, if the sequence is owned by a column, add an + * ALTER SEQUENCE SET OWNED command for it. * - * Add a 'SETVAL(seq, last_val, iscalled)' at restore-time iff we load - * data. We do this for serial sequences too. + * Add a 'SETVAL(seq, last_val, iscalled)' as part of a "data" dump. */ - - if (!dataOnly && !OidIsValid(tbinfo->owning_tab)) + if (!dataOnly) { resetPQExpBuffer(delqry); @@ -8166,32 +8135,57 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) false, "SEQUENCE", query->data, delqry->data, NULL, tbinfo->dobj.dependencies, tbinfo->dobj.nDeps, NULL, NULL); + + /* + * If the sequence is owned by a table column, emit the ALTER for it + * as a separate TOC entry immediately following the sequence's own + * entry. It's OK to do this rather than using full sorting logic, + * because the dependency that tells us it's owned will have forced + * the table to be created first. We can't just include the ALTER + * in the TOC entry because it will fail if we haven't reassigned + * the sequence owner to match the table's owner. + * + * We need not schema-qualify the table reference because both + * sequence and table must be in the same schema. + */ + if (OidIsValid(tbinfo->owning_tab)) + { + TableInfo *owning_tab = findTableByOid(tbinfo->owning_tab); + + if (owning_tab) + { + resetPQExpBuffer(query); + appendPQExpBuffer(query, "ALTER SEQUENCE %s", + fmtId(tbinfo->dobj.name)); + appendPQExpBuffer(query, " OWNED BY %s", + fmtId(owning_tab->dobj.name)); + appendPQExpBuffer(query, ".%s;\n", + fmtId(owning_tab->attnames[tbinfo->owning_col - 1])); + + ArchiveEntry(fout, nilCatalogId, createDumpId(), + tbinfo->dobj.name, + tbinfo->dobj.namespace->dobj.name, + NULL, + tbinfo->rolname, + false, "SEQUENCE OWNED BY", query->data, "", NULL, + &(tbinfo->dobj.dumpId), 1, + NULL, NULL); + } + } + + /* Dump Sequence Comments */ + resetPQExpBuffer(query); + appendPQExpBuffer(query, "SEQUENCE %s", fmtId(tbinfo->dobj.name)); + dumpComment(fout, query->data, + tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, + tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId); } if (!schemaOnly) { - TableInfo *owning_tab; - resetPQExpBuffer(query); appendPQExpBuffer(query, "SELECT pg_catalog.setval("); - - /* - * If this is a SERIAL sequence, then use the pg_get_serial_sequence - * function to avoid hard-coding the sequence name. Note that this - * implicitly assumes that the sequence and its owning table are in - * the same schema, because we don't schema-qualify the reference. - */ - if (OidIsValid(tbinfo->owning_tab) && - (owning_tab = findTableByOid(tbinfo->owning_tab)) != NULL) - { - appendPQExpBuffer(query, "pg_catalog.pg_get_serial_sequence("); - appendStringLiteralAH(query, fmtId(owning_tab->dobj.name), fout); - appendPQExpBuffer(query, ", "); - appendStringLiteralAH(query, owning_tab->attnames[tbinfo->owning_col - 1], fout); - appendPQExpBuffer(query, ")"); - } - else - appendStringLiteralAH(query, fmtId(tbinfo->dobj.name), fout); + appendStringLiteralAH(query, fmtId(tbinfo->dobj.name), fout); appendPQExpBuffer(query, ", %s, %s);\n", last, (called ? "true" : "false")); @@ -8205,16 +8199,6 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) NULL, NULL); } - if (!dataOnly) - { - /* Dump Sequence Comments */ - resetPQExpBuffer(query); - appendPQExpBuffer(query, "SEQUENCE %s", fmtId(tbinfo->dobj.name)); - dumpComment(fout, query->data, - tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, - tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId); - } - PQclear(res); destroyPQExpBuffer(query); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 3650ae50de..65f5e84c41 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.128 2006/08/01 18:05:04 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.129 2006/08/21 00:57:25 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -185,7 +185,7 @@ typedef struct _tableInfo bool hasoids; /* does it have OIDs? */ int ncheck; /* # of CHECK expressions */ int ntrig; /* # of triggers */ - /* these two are set only if table is a SERIAL column's sequence: */ + /* these two are set only if table is a sequence owned by a column: */ Oid owning_tab; /* OID of table owning sequence */ int owning_col; /* attr # of column owning sequence */ @@ -204,7 +204,6 @@ typedef struct _tableInfo char *typstorage; /* type storage scheme */ bool *attisdropped; /* true if attr is dropped; don't dump it */ bool *attislocal; /* true if attr has local definition */ - bool *attisserial; /* true if attr is serial or bigserial */ /* * Note: we need to store per-attribute notnull, default, and constraint diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index b27acf5f85..448f9e4ecf 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.351 2006/08/19 01:36:29 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.352 2006/08/21 00:57:26 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200608181 +#define CATALOG_VERSION_NO 200608191 #endif diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 04dc3ac7f3..da7d7ac8e4 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.26 2006/08/20 21:56:16 alvherre Exp $ + * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.27 2006/08/21 00:57:26 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -112,7 +112,7 @@ typedef struct ObjectAddress int32 objectSubId; /* Subitem within the object (column of table) */ } ObjectAddress; -/* expansible list of ObjectAddresses */ +/* expansible list of ObjectAddresses (private in dependency.c) */ typedef struct ObjectAddresses ObjectAddresses; /* @@ -192,7 +192,9 @@ extern long changeDependencyFor(Oid classId, Oid objectId, Oid refClassId, Oid oldRefObjectId, Oid newRefObjectId); -extern bool objectIsInternalDependency(Oid classId, Oid objectId); +extern bool sequenceIsOwned(Oid seqId, Oid *tableId, int32 *colId); + +extern void markSequenceUnowned(Oid seqId); /* in pg_shdepend.c */ diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 23cb0029c0..3118cfb8f1 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/makefuncs.h,v 1.55 2006/03/14 22:48:22 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/makefuncs.h,v 1.56 2006/08/21 00:57:26 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -62,4 +62,6 @@ extern TypeName *makeTypeNameFromOid(Oid typeid, int32 typmod); extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args, CoercionForm fformat); +extern DefElem *makeDefElem(char *name, Node *arg); + #endif /* MAKEFUNC_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 222f5d26cc..a3ae58d17b 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.323 2006/08/12 20:05:56 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.324 2006/08/21 00:57:26 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -393,10 +393,6 @@ typedef struct RangeFunction * parsetree produced by gram.y, but transformCreateStmt will remove * the item and set raw_default instead. CONSTR_DEFAULT items * should not appear in any subsequent processing. - * - * The "support" field, if not null, denotes a supporting relation that - * should be linked by an internal dependency to the column. Currently - * this is only used to link a SERIAL column's sequence to the column. */ typedef struct ColumnDef { @@ -409,7 +405,6 @@ typedef struct ColumnDef Node *raw_default; /* default value (untransformed parse tree) */ char *cooked_default; /* nodeToString representation */ List *constraints; /* other constraints on column */ - RangeVar *support; /* supporting relation, if any */ } ColumnDef; /* diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 3e15228792..e664a1520b 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -1443,6 +1443,6 @@ NOTICE: drop cascades to function alter2.plus1(integer) NOTICE: drop cascades to view alter2.v1 NOTICE: drop cascades to rule _RETURN on view alter2.v1 NOTICE: drop cascades to sequence alter2.t1_f1_seq -NOTICE: drop cascades to table alter2.t1 column f1 +NOTICE: drop cascades to default for table alter2.t1 column f1 NOTICE: drop cascades to table alter2.t1 NOTICE: drop cascades to constraint t1_f2_check on table alter2.t1 diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out index 4781b5f9f2..ba65a351bc 100644 --- a/src/test/regress/expected/dependency.out +++ b/src/test/regress/expected/dependency.out @@ -58,7 +58,8 @@ REASSIGN OWNED BY regression_user1 TO regression_user0; ERROR: permission denied to reassign objects -- this one is allowed DROP OWNED BY regression_user0; -CREATE TABLE deptest1 (); +CREATE TABLE deptest1 (f1 int unique); +NOTICE: CREATE TABLE / UNIQUE will create implicit index "deptest1_f1_key" for table "deptest1" GRANT ALL ON deptest1 TO regression_user1 WITH GRANT OPTION; SET SESSION AUTHORIZATION regression_user1; CREATE TABLE deptest (a serial primary key, b text); @@ -90,6 +91,11 @@ SET SESSION AUTHORIZATION regression_user1; CREATE TABLE deptest (a serial primary key, b text); NOTICE: CREATE TABLE will create implicit sequence "deptest_a_seq" for serial column "deptest.a" NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "deptest_pkey" for table "deptest" +CREATE TABLE deptest2 (f1 int); +-- make a serial column the hard way +CREATE SEQUENCE ss1; +ALTER TABLE deptest2 ALTER f1 SET DEFAULT nextval('ss1'); +ALTER SEQUENCE ss1 OWNED BY deptest2.f1; RESET SESSION AUTHORIZATION; REASSIGN OWNED BY regression_user1 TO regression_user2; \dt deptest diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out index 646bb63979..a9f3e4ab92 100644 --- a/src/test/regress/expected/namespace.out +++ b/src/test/regress/expected/namespace.out @@ -42,6 +42,7 @@ DROP SCHEMA test_schema_1 CASCADE; NOTICE: drop cascades to view test_schema_1.abc_view NOTICE: drop cascades to rule _RETURN on view test_schema_1.abc_view NOTICE: drop cascades to table test_schema_1.abc +NOTICE: drop cascades to default for table test_schema_1.abc column a -- verify that the objects were dropped SELECT COUNT(*) FROM pg_class WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'test_schema_1'); diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out index ca9ece284b..0576b575ee 100644 --- a/src/test/regress/expected/sequence.out +++ b/src/test/regress/expected/sequence.out @@ -130,8 +130,9 @@ CREATE TEMP TABLE t1 ( NOTICE: CREATE TABLE will create implicit sequence "t1_f1_seq" for serial column "t1.f1" -- Both drops should fail, but with different error messages: DROP SEQUENCE t1_f1_seq; -ERROR: cannot drop sequence t1_f1_seq because table t1 column f1 requires it -HINT: You may drop table t1 column f1 instead. +NOTICE: default for table t1 column f1 depends on sequence t1_f1_seq +ERROR: cannot drop sequence t1_f1_seq because other objects depend on it +HINT: Use DROP ... CASCADE to drop the dependent objects too. DROP SEQUENCE myseq2; NOTICE: default for table t1 column f2 depends on sequence myseq2 ERROR: cannot drop sequence myseq2 because other objects depend on it diff --git a/src/test/regress/sql/dependency.sql b/src/test/regress/sql/dependency.sql index c1b189f527..6f8e0e84d5 100644 --- a/src/test/regress/sql/dependency.sql +++ b/src/test/regress/sql/dependency.sql @@ -57,7 +57,7 @@ REASSIGN OWNED BY regression_user1 TO regression_user0; -- this one is allowed DROP OWNED BY regression_user0; -CREATE TABLE deptest1 (); +CREATE TABLE deptest1 (f1 int unique); GRANT ALL ON deptest1 TO regression_user1 WITH GRANT OPTION; SET SESSION AUTHORIZATION regression_user1; @@ -77,10 +77,17 @@ GRANT ALL ON deptest1 TO regression_user1; SET SESSION AUTHORIZATION regression_user1; CREATE TABLE deptest (a serial primary key, b text); + +CREATE TABLE deptest2 (f1 int); +-- make a serial column the hard way +CREATE SEQUENCE ss1; +ALTER TABLE deptest2 ALTER f1 SET DEFAULT nextval('ss1'); +ALTER SEQUENCE ss1 OWNED BY deptest2.f1; RESET SESSION AUTHORIZATION; REASSIGN OWNED BY regression_user1 TO regression_user2; \dt deptest + -- doesn't work: grant still exists DROP USER regression_user1; DROP OWNED BY regression_user1;