Fix all known problems with pg_dump's handling of serial sequences

by abandoning the idea that it should say SERIAL in the dump.  Instead,
dump serial sequences and column defaults just like regular ones.
Add a new backend command ALTER SEQUENCE OWNED BY to let pg_dump recreate
the sequence-to-column dependency that was formerly created "behind the
scenes" by SERIAL.  This restores SERIAL to being truly "just a macro"
consisting of component operations that can be stated explicitly in SQL.
Furthermore, the new command allows sequence ownership to be reassigned,
so that old mistakes can be cleaned up.

Also, downgrade the OWNED-BY dependency from INTERNAL to AUTO, since there
is no longer any very compelling argument why the sequence couldn't be
dropped while keeping the column.  (This forces initdb, to be sure the
right kinds of dependencies are in there.)

Along the way, add checks to prevent ALTER OWNER or SET SCHEMA on an
owned sequence; you can now only do this indirectly by changing the
owning table's owner or schema.  This is an oversight in previous
releases, but probably not worth back-patching.
This commit is contained in:
Tom Lane 2006-08-21 00:57:26 +00:00
parent df18c51f29
commit 2b2a50722c
29 changed files with 590 additions and 356 deletions

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.169 2006/07/06 01:46:37 momjian Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.170 2006/08/21 00:57:23 tgl Exp $ -->
<chapter id="datatype">
<title id="datatype-title">Data Types</title>
@ -705,17 +705,19 @@ CREATE TABLE <replaceable class="parameter">tablename</replaceable> (
<programlisting>
CREATE SEQUENCE <replaceable class="parameter">tablename</replaceable>_<replaceable class="parameter">colname</replaceable>_seq;
CREATE TABLE <replaceable class="parameter">tablename</replaceable> (
<replaceable class="parameter">colname</replaceable> integer DEFAULT nextval('<replaceable class="parameter">tablename</replaceable>_<replaceable class="parameter">colname</replaceable>_seq') NOT NULL
<replaceable class="parameter">colname</replaceable> integer NOT NULL DEFAULT nextval('<replaceable class="parameter">tablename</replaceable>_<replaceable class="parameter">colname</replaceable>_seq')
);
ALTER SEQUENCE <replaceable class="parameter">tablename</replaceable>_<replaceable class="parameter">colname</replaceable>_seq OWNED BY <replaceable class="parameter">tablename</replaceable>.<replaceable class="parameter">colname</replaceable>;
</programlisting>
Thus, we have created an integer column and arranged for its default
values to be assigned from a sequence generator. A <literal>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
<literal>UNIQUE</> or <literal>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 <quote>owned by</>
the column, so that it will be dropped if the column or table is dropped.
</para>
<note>
@ -749,20 +751,9 @@ CREATE TABLE <replaceable class="parameter">tablename</replaceable> (
<para>
The sequence created for a <type>serial</type> column is
automatically dropped when the owning column is dropped, and
cannot be dropped otherwise. (This was not true in
<productname>PostgreSQL</productname> 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 <type>serial</> column itself. If
any other columns reference the sequence (perhaps by manually
calling the <function>nextval</> function), they will be broken
if the sequence is removed. Using a <type>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.
</para>
</sect2>
</sect1>

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.330 2006/08/17 23:04:03 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.331 2006/08/21 00:57:23 tgl Exp $ -->
<chapter id="functions">
<title>Functions and Operators</title>
@ -9863,10 +9863,14 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
<para>
<function>pg_get_serial_sequence</function> fetches the name of the
sequence associated with a <type>serial</> or <type>bigserial</>
column. The name is suitably formatted for passing to the sequence
functions (see <xref linkend="functions-sequence">). 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 <xref linkend="functions-sequence">).
This association can be modified or removed with <command>ALTER SEQUENCE
OWNED BY</>. (The function probably should have been called
<function>pg_get_owned_sequence</function>; its name reflects the fact
that it's typically used with <type>serial</> or <type>bigserial</>
columns.)
</para>
<para>

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/alter_sequence.sgml,v 1.13 2005/11/01 21:09:50 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/alter_sequence.sgml,v 1.14 2006/08/21 00:57:24 tgl Exp $
PostgreSQL documentation
-->
@ -27,6 +27,7 @@ PostgreSQL documentation
ALTER SEQUENCE <replaceable class="parameter">name</replaceable> [ INCREMENT [ BY ] <replaceable class="parameter">increment</replaceable> ]
[ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ]
[ RESTART [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ]
[ OWNED BY { <replaceable class="parameter">table</replaceable>.<replaceable class="parameter">column</replaceable> | NONE } ]
ALTER SEQUENCE <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
</synopsis>
</refsynopsisdiv>
@ -163,6 +164,24 @@ ALTER SEQUENCE <replaceable class="parameter">name</replaceable> SET SCHEMA <rep
</listitem>
</varlistentry>
<varlistentry>
<term><literal>OWNED BY</literal> <replaceable class="parameter">table</replaceable>.<replaceable class="parameter">column</replaceable></term>
<term><literal>OWNED BY NONE</literal></term>
<listitem>
<para>
The <literal>OWNED BY</literal> 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 <literal>OWNED BY NONE</literal> removes any existing
association, making the sequence <quote>free-standing</>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">new_schema</replaceable></term>
<listitem>
@ -191,8 +210,11 @@ ALTER SEQUENCE serial RESTART WITH 105;
<para>
To avoid blocking of concurrent transactions that obtain numbers from the
same sequence, <command>ALTER SEQUENCE</command> is never rolled back;
the changes take effect immediately and are not reversible.
same sequence, <command>ALTER SEQUENCE</command>'s effects on the sequence
generation parameters are never rolled back;
those changes take effect immediately and are not reversible. However,
the <literal>OWNED BY</> and <literal>SET SCHEMA</> clauses are ordinary
catalog updates and can be rolled back.
</para>
<para>
@ -200,7 +222,8 @@ ALTER SEQUENCE serial RESTART WITH 105;
<function>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.
</para>
<para>
@ -217,10 +240,20 @@ ALTER SEQUENCE serial RESTART WITH 105;
<para>
<command>ALTER SEQUENCE</command> conforms to the <acronym>SQL</acronym>
standard,
except for the <literal>SET SCHEMA</literal> variant, which is a
<productname>PostgreSQL</productname> extension.
except for the <literal>OWNED BY</> and <literal>SET SCHEMA</literal>
clauses, which are <productname>PostgreSQL</productname> extensions.
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<simplelist type="inline">
<member><xref linkend="sql-createsequence" endterm="sql-createsequence-title"></member>
<member><xref linkend="sql-dropsequence" endterm="sql-dropsequence-title"></member>
</simplelist>
</refsect1>
</refentry>
<!-- Keep this comment at the end of the file

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.88 2006/08/03 20:57:06 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.89 2006/08/21 00:57:24 tgl Exp $
PostgreSQL documentation
-->
@ -371,7 +371,7 @@ where <replaceable class="PARAMETER">action</replaceable> is one of:
<listitem>
<para>
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.
</para>
</listitem>
</varlistentry>

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/create_sequence.sgml,v 1.43 2005/11/01 21:09:50 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/create_sequence.sgml,v 1.44 2006/08/21 00:57:24 tgl Exp $
PostgreSQL documentation
-->
@ -23,6 +23,7 @@ PostgreSQL documentation
CREATE [ TEMPORARY | TEMP ] SEQUENCE <replaceable class="parameter">name</replaceable> [ INCREMENT [ BY ] <replaceable class="parameter">increment</replaceable> ]
[ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ]
[ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ]
[ OWNED BY { <replaceable class="parameter">table</replaceable>.<replaceable class="parameter">column</replaceable> | NONE } ]
</synopsis>
</refsynopsisdiv>
@ -193,6 +194,22 @@ SELECT * FROM <replaceable>name</replaceable>;
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>OWNED BY</literal> <replaceable class="parameter">table</replaceable>.<replaceable class="parameter">column</replaceable></term>
<term><literal>OWNED BY NONE</literal></term>
<listitem>
<para>
The <literal>OWNED BY</literal> 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.
<literal>OWNED BY NONE</literal>, the default, specifies that there
is no such association.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -300,11 +317,38 @@ END;
<command>CREATE SEQUENCE</command> conforms to the <acronym>SQL</acronym>
standard, with the following exceptions:
<itemizedlist>
<listitem><para>The standard's <literal>AS &lt;data type&gt;</literal> expression is not supported.</para></listitem>
<listitem><para>Obtaining the next value is done using the <function>nextval()</> function instead of the standard's <command>NEXT VALUE FOR</command> expression.</para></listitem>
<listitem>
<para>
The standard's <literal>AS &lt;data type&gt;</literal> expression is not
supported.
</para>
</listitem>
<listitem>
<para>
Obtaining the next value is done using the <function>nextval()</>
function instead of the standard's <command>NEXT VALUE FOR</command>
expression.
</para>
</listitem>
<listitem>
<para>
The <literal>OWNED BY</> clause is a <productname>PostgreSQL</>
extension.
</para>
</listitem>
</itemizedlist>
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<simplelist type="inline">
<member><xref linkend="sql-altersequence" endterm="sql-altersequence-title"></member>
<member><xref linkend="sql-dropsequence" endterm="sql-dropsequence-title"></member>
</simplelist>
</refsect1>
</refentry>
<!-- Keep this comment at the end of the file

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/drop_sequence.sgml,v 1.24 2005/11/19 17:39:44 adunstan Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/drop_sequence.sgml,v 1.25 2006/08/21 00:57:24 tgl Exp $
PostgreSQL documentation
-->
@ -105,6 +105,7 @@ DROP SEQUENCE serial;
<simplelist type="inline">
<member><xref linkend="sql-createsequence" endterm="sql-createsequence-title"></member>
<member><xref linkend="sql-altersequence" endterm="sql-altersequence-title"></member>
</simplelist>
</refsect1>

View File

@ -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()
*

View File

@ -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:

View File

@ -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)

View File

@ -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 */

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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.
*/

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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 */

View File

@ -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 */

View File

@ -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;
/*

View File

@ -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

View File

@ -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

View File

@ -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');

View File

@ -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

View File

@ -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;