From 10a3471bed7b57fb986a5be8afdee5f0dda419de Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 16 May 2008 23:36:05 +0000 Subject: [PATCH] Add a RESTART (without parameter) option to ALTER SEQUENCE, allowing a sequence to be reset to its original starting value. This requires adding the original start value to the set of parameters (columns) of a sequence object, which is a user-visible change with potential compatibility implications; it also forces initdb. Also add hopefully-SQL-compatible RESTART/CONTINUE IDENTITY options to TRUNCATE TABLE. RESTART IDENTITY executes ALTER SEQUENCE RESTART for all sequences "owned by" any of the truncated relations. CONTINUE IDENTITY is a no-op option. Zoltan Boszormenyi --- doc/src/sgml/ref/alter_sequence.sgml | 11 ++- doc/src/sgml/ref/truncate.sgml | 60 ++++++++++-- src/backend/catalog/pg_depend.c | 54 ++++++++++- src/backend/commands/sequence.c | 129 +++++++++++++++++++------ src/backend/commands/tablecmds.c | 57 ++++++++++- src/backend/nodes/copyfuncs.c | 3 +- src/backend/nodes/equalfuncs.c | 3 +- src/backend/parser/gram.y | 31 ++++-- src/backend/parser/keywords.c | 4 +- src/bin/pg_dump/pg_dump.c | 83 +++++++++++----- src/include/catalog/catversion.h | 4 +- src/include/catalog/dependency.h | 4 +- src/include/commands/sequence.h | 31 +++--- src/include/nodes/parsenodes.h | 3 +- src/test/regress/expected/sequence.out | 63 ++++++++++-- src/test/regress/expected/truncate.out | 38 ++++++++ src/test/regress/sql/sequence.sql | 15 ++- src/test/regress/sql/truncate.sql | 26 +++++ 18 files changed, 513 insertions(+), 106 deletions(-) diff --git a/doc/src/sgml/ref/alter_sequence.sgml b/doc/src/sgml/ref/alter_sequence.sgml index 3c982eee6f..31e64dac35 100644 --- a/doc/src/sgml/ref/alter_sequence.sgml +++ b/doc/src/sgml/ref/alter_sequence.sgml @@ -1,5 +1,5 @@ @@ -26,7 +26,7 @@ PostgreSQL documentation ALTER SEQUENCE name [ INCREMENT [ BY ] increment ] [ MINVALUE minvalue | NO MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ] - [ RESTART [ WITH ] start ] [ CACHE cache ] [ [ NO ] CYCLE ] + [ RESTART [ [ WITH ] start ] ] [ CACHE cache ] [ [ NO ] CYCLE ] [ OWNED BY { table.column | NONE } ] ALTER SEQUENCE name RENAME TO new_name ALTER SEQUENCE name SET SCHEMA new_schema @@ -112,12 +112,15 @@ ALTER SEQUENCE name SET SCHEMA start - The optional clause RESTART WITH start changes the + The optional clause RESTART [ WITH start ] changes the current value of the sequence. This is equivalent to calling the setval function with is_called = false: the specified value will be returned by the next call of nextval. + Writing RESTART with no start value is equivalent to supplying + the start value used when the sequence was created. diff --git a/doc/src/sgml/ref/truncate.sgml b/doc/src/sgml/ref/truncate.sgml index 486a2d3e99..effe903b09 100644 --- a/doc/src/sgml/ref/truncate.sgml +++ b/doc/src/sgml/ref/truncate.sgml @@ -1,5 +1,5 @@ @@ -20,7 +20,8 @@ PostgreSQL documentation -TRUNCATE [ TABLE ] name [, ...] [ CASCADE | RESTRICT ] +TRUNCATE [ TABLE ] name [, ... ] + [ RESTART IDENTITY | CONTINUE IDENTITY ] [ CASCADE | RESTRICT ] @@ -50,6 +51,25 @@ TRUNCATE [ TABLE ] name [, ...] [ C + + RESTART IDENTITY + + + Automatically restart sequences owned by columns of + the truncated table(s). + + + + + + CONTINUE IDENTITY + + + Do not change the values of sequences. This is the default. + + + + CASCADE @@ -66,7 +86,7 @@ TRUNCATE [ TABLE ] name [, ...] [ C Refuse to truncate if any of the tables have foreign-key references - from tables that are not to be truncated. This is the default. + from tables that are not listed in the command. This is the default. @@ -119,11 +139,23 @@ TRUNCATE [ TABLE ] name [, ...] [ C cause visible inconsistency between the contents of the truncated table and other tables in the database. + + + + TRUNCATE is transaction-safe with respect to the data + in the tables: the truncation will be safely rolled back if the surrounding + transaction does not commit. + + - TRUNCATE is transaction-safe, however: the truncation - will be safely rolled back if the surrounding transaction does not - commit. + Any ALTER SEQUENCE RESTART operations performed as a + consequence of using the RESTART IDENTITY option are + nontransactional and will not be rolled back. To minimize risk, + these operations are performed only after all the rest of + TRUNCATE's work is done. In practice this will only + be an issue if TRUNCATE is performed inside a + transaction block that is aborted afterwards. @@ -132,13 +164,22 @@ TRUNCATE [ TABLE ] name [, ...] [ C Examples - Truncate the tables bigtable and fattable: + Truncate the tables bigtable and + fattable: TRUNCATE bigtable, fattable; + + The same, and also reset any associated sequence generators: + + +TRUNCATE bigtable, fattable RESTART IDENTITY; + + + Truncate the table othertable, and cascade to any tables that reference othertable via foreign-key @@ -154,7 +195,10 @@ TRUNCATE othertable CASCADE; Compatibility - There is no TRUNCATE command in the SQL standard. + The draft SQL:2008 standard includes a TRUNCATE command, + but at this writing it is uncertain whether that will reach standardization + or be fully compatible with PostgreSQL's + implementation. diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c index 32d8d07044..d1fd8f5200 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.27 2008/03/26 21:10:37 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_depend.c,v 1.28 2008/05/16 23:36:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -418,6 +418,58 @@ markSequenceUnowned(Oid seqId) heap_close(depRel, RowExclusiveLock); } +/* + * Collect a list of OIDs of all sequences owned by the specified relation. + */ +List * +getOwnedSequences(Oid relid) +{ + List *result = NIL; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = heap_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + scan = systable_beginscan(depRel, DependReferenceIndexId, true, + SnapshotNow, 2, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup); + + /* + * We assume any auto dependency of a sequence on a column must be + * what we are looking for. (We need the relkind test because indexes + * can also have auto dependencies on columns.) + */ + if (deprec->classid == RelationRelationId && + deprec->objsubid == 0 && + deprec->refobjsubid != 0 && + deprec->deptype == DEPENDENCY_AUTO && + get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE) + { + result = lappend_oid(result, deprec->objid); + } + } + + systable_endscan(scan); + + heap_close(depRel, AccessShareLock); + + return result; +} + /* * get_constraint_index diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 670fcbcfbd..748413ebed 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.150 2008/05/12 00:00:47 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/commands/sequence.c,v 1.151 2008/05/16 23:36:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -91,7 +91,7 @@ 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, bool isInit, - Form_pg_sequence new, List **owned_by); + Form_pg_sequence new, Form_pg_sequence old, List **owned_by); static void do_setval(Oid relid, int64 next, bool iscalled); static void process_owned_by(Relation seqrel, List *owned_by); @@ -119,10 +119,10 @@ DefineSequence(CreateSeqStmt *seq) NameData name; /* Check and set all option values */ - init_params(seq->options, true, &new, &owned_by); + init_params(seq->options, true, &new, NULL, &owned_by); /* - * Create relation (and fill *null & *value) + * Create relation (and fill value[] and null[] for the tuple) */ stmt->tableElts = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) @@ -151,6 +151,11 @@ DefineSequence(CreateSeqStmt *seq) coldef->colname = "last_value"; value[i - 1] = Int64GetDatumFast(new.last_value); break; + case SEQ_COL_STARTVAL: + coldef->typename = makeTypeNameFromOid(INT8OID, -1); + coldef->colname = "start_value"; + value[i - 1] = Int64GetDatumFast(new.start_value); + break; case SEQ_COL_INCBY: coldef->typename = makeTypeNameFromOid(INT8OID, -1); coldef->colname = "increment_by"; @@ -314,6 +319,29 @@ void AlterSequence(AlterSeqStmt *stmt) { Oid relid; + + /* find sequence */ + relid = RangeVarGetRelid(stmt->sequence, false); + + /* allow ALTER to sequence owner only */ + /* if you change this, see also callers of AlterSequenceInternal! */ + if (!pg_class_ownercheck(relid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, + stmt->sequence->relname); + + /* do the work */ + AlterSequenceInternal(relid, stmt->options); +} + +/* + * AlterSequenceInternal + * + * Same as AlterSequence except that the sequence is specified by OID + * and we assume the caller already checked permissions. + */ +void +AlterSequenceInternal(Oid relid, List *options) +{ SeqTable elm; Relation seqrel; Buffer buf; @@ -323,23 +351,14 @@ AlterSequence(AlterSeqStmt *stmt) List *owned_by; /* open and AccessShareLock sequence */ - relid = RangeVarGetRelid(stmt->sequence, false); init_sequence(relid, &elm, &seqrel); - /* allow ALTER to sequence owner only */ - if (!pg_class_ownercheck(elm->relid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - stmt->sequence->relname); - /* lock page' buffer and read tuple into new sequence structure */ seq = read_info(elm, seqrel, &buf); page = BufferGetPage(buf); - /* Copy old values of options into workspace */ - memcpy(&new, seq, sizeof(FormData_pg_sequence)); - - /* Check and set new values */ - init_params(stmt->options, false, &new, &owned_by); + /* Fill workspace with appropriate new info */ + init_params(options, false, &new, seq, &owned_by); /* Clear local cache so that we don't think we have cached numbers */ /* Note that we do not change the currval() state */ @@ -970,7 +989,7 @@ read_info(SeqTable elm, Relation rel, Buffer *buf) */ static void init_params(List *options, bool isInit, - Form_pg_sequence new, List **owned_by) + Form_pg_sequence new, Form_pg_sequence old, List **owned_by) { DefElem *last_value = NULL; DefElem *increment_by = NULL; @@ -982,6 +1001,12 @@ init_params(List *options, bool isInit, *owned_by = NIL; + /* Copy old values of options into workspace */ + if (old != NULL) + memcpy(new, old, sizeof(FormData_pg_sequence)); + else + memset(new, 0, sizeof(FormData_pg_sequence)); + foreach(option, options) { DefElem *defel = (DefElem *) lfirst(option); @@ -994,13 +1019,24 @@ init_params(List *options, bool isInit, errmsg("conflicting or redundant options"))); increment_by = defel; } - - /* - * start is for a new sequence restart is for alter - */ - else if (strcmp(defel->defname, "start") == 0 || - strcmp(defel->defname, "restart") == 0) + else if (strcmp(defel->defname, "start") == 0) + { + if (!isInit) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("use RESTART not START in ALTER SEQUENCE"))); + if (last_value) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + last_value = defel; + } + else if (strcmp(defel->defname, "restart") == 0) { + if (isInit) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("use START not RESTART in CREATE SEQUENCE"))); if (last_value) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -1109,24 +1145,59 @@ init_params(List *options, bool isInit, bufm, bufx))); } - /* START WITH */ + /* START/RESTART [WITH] */ if (last_value != NULL) { - new->last_value = defGetInt64(last_value); + if (last_value->arg != NULL) + new->last_value = defGetInt64(last_value); + else + { + Assert(old != NULL); + new->last_value = old->start_value; + } + if (isInit) + new->start_value = new->last_value; new->is_called = false; new->log_cnt = 1; } else if (isInit) { if (new->increment_by > 0) - new->last_value = new->min_value; /* ascending seq */ + new->start_value = new->min_value; /* ascending seq */ else - new->last_value = new->max_value; /* descending seq */ + new->start_value = new->max_value; /* descending seq */ + new->last_value = new->start_value; new->is_called = false; new->log_cnt = 1; } - /* crosscheck */ + /* crosscheck START */ + if (new->start_value < new->min_value) + { + char bufs[100], + bufm[100]; + + snprintf(bufs, sizeof(bufs), INT64_FORMAT, new->start_value); + snprintf(bufm, sizeof(bufm), INT64_FORMAT, new->min_value); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("START value (%s) cannot be less than MINVALUE (%s)", + bufs, bufm))); + } + if (new->start_value > new->max_value) + { + char bufs[100], + bufm[100]; + + snprintf(bufs, sizeof(bufs), INT64_FORMAT, new->start_value); + snprintf(bufm, sizeof(bufm), INT64_FORMAT, new->max_value); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("START value (%s) cannot be greater than MAXVALUE (%s)", + bufs, bufm))); + } + + /* must crosscheck RESTART separately */ if (new->last_value < new->min_value) { char bufs[100], @@ -1136,7 +1207,7 @@ init_params(List *options, bool isInit, snprintf(bufm, sizeof(bufm), INT64_FORMAT, new->min_value); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("START value (%s) cannot be less than MINVALUE (%s)", + errmsg("RESTART value (%s) cannot be less than MINVALUE (%s)", bufs, bufm))); } if (new->last_value > new->max_value) @@ -1148,7 +1219,7 @@ init_params(List *options, bool isInit, snprintf(bufm, sizeof(bufm), INT64_FORMAT, new->max_value); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("START value (%s) cannot be greater than MAXVALUE (%s)", + errmsg("RESTART value (%s) cannot be greater than MAXVALUE (%s)", bufs, bufm))); } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 48e229e0fb..0bef48e07b 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.253 2008/05/12 00:00:48 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.254 2008/05/16 23:36:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,6 +37,7 @@ #include "catalog/toasting.h" #include "commands/cluster.h" #include "commands/defrem.h" +#include "commands/sequence.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" #include "commands/trigger.h" @@ -531,6 +532,7 @@ ExecuteTruncate(TruncateStmt *stmt) { List *rels = NIL; List *relids = NIL; + List *seq_relids = NIL; EState *estate; ResultRelInfo *resultRelInfos; ResultRelInfo *resultRelInfo; @@ -596,6 +598,40 @@ ExecuteTruncate(TruncateStmt *stmt) heap_truncate_check_FKs(rels, false); #endif + /* + * If we are asked to restart sequences, find all the sequences, + * lock them (we only need AccessShareLock because that's all that + * ALTER SEQUENCE takes), and check permissions. We want to do this + * early since it's pointless to do all the truncation work only to fail + * on sequence permissions. + */ + if (stmt->restart_seqs) + { + foreach(cell, rels) + { + Relation rel = (Relation) lfirst(cell); + List *seqlist = getOwnedSequences(RelationGetRelid(rel)); + ListCell *seqcell; + + foreach(seqcell, seqlist) + { + Oid seq_relid = lfirst_oid(seqcell); + Relation seq_rel; + + seq_rel = relation_open(seq_relid, AccessShareLock); + + /* This check must match AlterSequence! */ + if (!pg_class_ownercheck(seq_relid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, + RelationGetRelationName(seq_rel)); + + seq_relids = lappend_oid(seq_relids, seq_relid); + + relation_close(seq_rel, NoLock); + } + } + } + /* Prepare to catch AFTER triggers. */ AfterTriggerBeginQuery(); @@ -694,6 +730,25 @@ ExecuteTruncate(TruncateStmt *stmt) heap_close(rel, NoLock); } + + /* + * Lastly, restart any owned sequences if we were asked to. This is done + * last because it's nontransactional: restarts will not roll back if + * we abort later. Hence it's important to postpone them as long as + * possible. (This is also a big reason why we locked and + * permission-checked the sequences beforehand.) + */ + if (stmt->restart_seqs) + { + List *options = list_make1(makeDefElem("restart", NULL)); + + foreach(cell, seq_relids) + { + Oid seq_relid = lfirst_oid(cell); + + AlterSequenceInternal(seq_relid, options); + } + } } /* diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 67dcf9a218..a42c40327f 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.393 2008/04/29 14:59:16 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.394 2008/05/16 23:36:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2156,6 +2156,7 @@ _copyTruncateStmt(TruncateStmt *from) TruncateStmt *newnode = makeNode(TruncateStmt); COPY_NODE_FIELD(relations); + COPY_SCALAR_FIELD(restart_seqs); COPY_SCALAR_FIELD(behavior); return newnode; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 0c44490132..435ee6a6af 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.322 2008/04/29 14:59:16 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.323 2008/05/16 23:36:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1010,6 +1010,7 @@ static bool _equalTruncateStmt(TruncateStmt *a, TruncateStmt *b) { COMPARE_NODE_FIELD(relations); + COMPARE_SCALAR_FIELD(restart_seqs); COMPARE_SCALAR_FIELD(behavior); return true; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 56a56627e7..591920d6f6 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.614 2008/04/29 20:44:49 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.615 2008/05/16 23:36:05 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -206,7 +206,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args) %type OptSchemaName %type OptSchemaEltList -%type TriggerActionTime TriggerForSpec opt_trusted +%type TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs %type opt_lancompiler %type TriggerEvents @@ -381,7 +381,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args) CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS - CONTENT_P CONVERSION_P COPY COST CREATE CREATEDB + CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB CREATEROLE CREATEUSER CROSS CSV CURRENT_P CURRENT_DATE CURRENT_ROLE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE @@ -399,10 +399,10 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args) HANDLER HAVING HEADER_P HOLD HOUR_P - IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT - INDEX INDEXES INHERIT INHERITS INITIALLY INNER_P INOUT INPUT_P - INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT - INTERVAL INTO INVOKER IS ISNULL ISOLATION + IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P + INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY + INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER + INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION JOIN @@ -2489,6 +2489,10 @@ OptSeqElem: CACHE NumericOnly { $$ = makeDefElem("start", (Node *)$3); } + | RESTART + { + $$ = makeDefElem("restart", NULL); + } | RESTART opt_with NumericOnly { $$ = makeDefElem("restart", (Node *)$3); @@ -3364,15 +3368,22 @@ attrs: '.' attr_name *****************************************************************************/ TruncateStmt: - TRUNCATE opt_table qualified_name_list opt_drop_behavior + TRUNCATE opt_table qualified_name_list opt_restart_seqs opt_drop_behavior { TruncateStmt *n = makeNode(TruncateStmt); n->relations = $3; - n->behavior = $4; + n->restart_seqs = $4; + n->behavior = $5; $$ = (Node *)n; } ; +opt_restart_seqs: + CONTINUE_P IDENTITY_P { $$ = false; } + | RESTART IDENTITY_P { $$ = true; } + | /* EMPTY */ { $$ = false; } + ; + /***************************************************************************** * * The COMMENT ON statement can take different forms based upon the type of @@ -8964,6 +8975,7 @@ unreserved_keyword: | CONNECTION | CONSTRAINTS | CONTENT_P + | CONTINUE_P | CONVERSION_P | COPY | COST @@ -9014,6 +9026,7 @@ unreserved_keyword: | HEADER_P | HOLD | HOUR_P + | IDENTITY_P | IF_P | IMMEDIATE | IMMUTABLE diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index b5312a487f..46b306b98d 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.195 2008/03/27 03:57:33 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.196 2008/05/16 23:36:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -101,6 +101,7 @@ static const ScanKeyword ScanKeywords[] = { {"constraint", CONSTRAINT, RESERVED_KEYWORD}, {"constraints", CONSTRAINTS, UNRESERVED_KEYWORD}, {"content", CONTENT_P, UNRESERVED_KEYWORD}, + {"continue", CONTINUE_P, UNRESERVED_KEYWORD}, {"conversion", CONVERSION_P, UNRESERVED_KEYWORD}, {"copy", COPY, UNRESERVED_KEYWORD}, {"cost", COST, UNRESERVED_KEYWORD}, @@ -181,6 +182,7 @@ static const ScanKeyword ScanKeywords[] = { {"header", HEADER_P, UNRESERVED_KEYWORD}, {"hold", HOLD, UNRESERVED_KEYWORD}, {"hour", HOUR_P, UNRESERVED_KEYWORD}, + {"identity", IDENTITY_P, UNRESERVED_KEYWORD}, {"if", IF_P, UNRESERVED_KEYWORD}, {"ilike", ILIKE, TYPE_FUNC_NAME_KEYWORD}, {"immediate", IMMEDIATE, UNRESERVED_KEYWORD}, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 4aefd05823..4122dad845 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.491 2008/05/12 00:00:53 alvherre Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.492 2008/05/16 23:36:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -9525,7 +9525,8 @@ static void dumpSequence(Archive *fout, TableInfo *tbinfo) { PGresult *res; - char *last, + char *startv, + *last, *incby, *maxv = NULL, *minv = NULL, @@ -9543,19 +9544,40 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) snprintf(bufm, sizeof(bufm), INT64_FORMAT, SEQ_MINVALUE); snprintf(bufx, sizeof(bufx), INT64_FORMAT, SEQ_MAXVALUE); - appendPQExpBuffer(query, - "SELECT sequence_name, last_value, increment_by, " - "CASE WHEN increment_by > 0 AND max_value = %s THEN NULL " - " WHEN increment_by < 0 AND max_value = -1 THEN NULL " - " ELSE max_value " - "END AS max_value, " - "CASE WHEN increment_by > 0 AND min_value = 1 THEN NULL " - " WHEN increment_by < 0 AND min_value = %s THEN NULL " - " ELSE min_value " - "END AS min_value, " - "cache_value, is_cycled, is_called from %s", - bufx, bufm, - fmtId(tbinfo->dobj.name)); + if (g_fout->remoteVersion >= 80400) + { + appendPQExpBuffer(query, + "SELECT sequence_name, " + "start_value, last_value, increment_by, " + "CASE WHEN increment_by > 0 AND max_value = %s THEN NULL " + " WHEN increment_by < 0 AND max_value = -1 THEN NULL " + " ELSE max_value " + "END AS max_value, " + "CASE WHEN increment_by > 0 AND min_value = 1 THEN NULL " + " WHEN increment_by < 0 AND min_value = %s THEN NULL " + " ELSE min_value " + "END AS min_value, " + "cache_value, is_cycled, is_called from %s", + bufx, bufm, + fmtId(tbinfo->dobj.name)); + } + else + { + appendPQExpBuffer(query, + "SELECT sequence_name, " + "0 as start_value, last_value, increment_by, " + "CASE WHEN increment_by > 0 AND max_value = %s THEN NULL " + " WHEN increment_by < 0 AND max_value = -1 THEN NULL " + " ELSE max_value " + "END AS max_value, " + "CASE WHEN increment_by > 0 AND min_value = 1 THEN NULL " + " WHEN increment_by < 0 AND min_value = %s THEN NULL " + " ELSE min_value " + "END AS min_value, " + "cache_value, is_cycled, is_called from %s", + bufx, bufm, + fmtId(tbinfo->dobj.name)); + } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); @@ -9577,15 +9599,16 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) } #endif - last = PQgetvalue(res, 0, 1); - incby = PQgetvalue(res, 0, 2); - if (!PQgetisnull(res, 0, 3)) - maxv = PQgetvalue(res, 0, 3); + startv = PQgetvalue(res, 0, 1); + last = PQgetvalue(res, 0, 2); + incby = PQgetvalue(res, 0, 3); if (!PQgetisnull(res, 0, 4)) - minv = PQgetvalue(res, 0, 4); - cache = PQgetvalue(res, 0, 5); - cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); - called = (strcmp(PQgetvalue(res, 0, 7), "t") == 0); + maxv = PQgetvalue(res, 0, 4); + if (!PQgetisnull(res, 0, 5)) + minv = PQgetvalue(res, 0, 5); + cache = PQgetvalue(res, 0, 6); + cycled = (strcmp(PQgetvalue(res, 0, 7), "t") == 0); + called = (strcmp(PQgetvalue(res, 0, 8), "t") == 0); /* * The logic we use for restoring sequences is as follows: @@ -9615,8 +9638,18 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) "CREATE SEQUENCE %s\n", fmtId(tbinfo->dobj.name)); - if (!called) - appendPQExpBuffer(query, " START WITH %s\n", last); + if (g_fout->remoteVersion >= 80400) + appendPQExpBuffer(query, " START WITH %s\n", startv); + else + { + /* + * Versions before 8.4 did not remember the true start value. If + * is_called is false then the sequence has never been incremented + * so we can use last_val. Otherwise punt and let it default. + */ + if (!called) + appendPQExpBuffer(query, " START WITH %s\n", last); + } appendPQExpBuffer(query, " INCREMENT BY %s\n", incby); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 94d70c2c87..e5b87fa864 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.460 2008/05/16 16:31:01 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.461 2008/05/16 23:36:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200805161 +#define CATALOG_VERSION_NO 200805162 #endif diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 572dedbc4f..e192c1ed28 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.34 2008/03/24 19:47:35 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.35 2008/05/16 23:36:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -207,6 +207,8 @@ extern bool sequenceIsOwned(Oid seqId, Oid *tableId, int32 *colId); extern void markSequenceUnowned(Oid seqId); +extern List *getOwnedSequences(Oid relid); + extern Oid get_constraint_index(Oid constraintId); extern Oid get_index_constraint(Oid indexId); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index cf95b6394a..fddf9b9ace 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/sequence.h,v 1.40 2008/03/27 03:57:34 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/sequence.h,v 1.41 2008/05/16 23:36:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -30,6 +30,7 @@ typedef struct FormData_pg_sequence NameData sequence_name; #ifndef INT64_IS_BUSTED int64 last_value; + int64 start_value; int64 increment_by; int64 max_value; int64 min_value; @@ -38,16 +39,18 @@ typedef struct FormData_pg_sequence #else int32 last_value; int32 pad1; - int32 increment_by; + int32 start_value; int32 pad2; - int32 max_value; + int32 increment_by; int32 pad3; - int32 min_value; + int32 max_value; int32 pad4; - int32 cache_value; + int32 min_value; int32 pad5; - int32 log_cnt; + int32 cache_value; int32 pad6; + int32 log_cnt; + int32 pad7; #endif bool is_cycled; bool is_called; @@ -61,13 +64,14 @@ typedef FormData_pg_sequence *Form_pg_sequence; #define SEQ_COL_NAME 1 #define SEQ_COL_LASTVAL 2 -#define SEQ_COL_INCBY 3 -#define SEQ_COL_MAXVALUE 4 -#define SEQ_COL_MINVALUE 5 -#define SEQ_COL_CACHE 6 -#define SEQ_COL_LOG 7 -#define SEQ_COL_CYCLE 8 -#define SEQ_COL_CALLED 9 +#define SEQ_COL_STARTVAL 3 +#define SEQ_COL_INCBY 4 +#define SEQ_COL_MAXVALUE 5 +#define SEQ_COL_MINVALUE 6 +#define SEQ_COL_CACHE 7 +#define SEQ_COL_LOG 8 +#define SEQ_COL_CYCLE 9 +#define SEQ_COL_CALLED 10 #define SEQ_COL_FIRSTCOL SEQ_COL_NAME #define SEQ_COL_LASTCOL SEQ_COL_CALLED @@ -90,6 +94,7 @@ extern Datum lastval(PG_FUNCTION_ARGS); extern void DefineSequence(CreateSeqStmt *stmt); extern void AlterSequence(AlterSeqStmt *stmt); +extern void AlterSequenceInternal(Oid relid, List *options); extern void seq_redo(XLogRecPtr lsn, XLogRecord *rptr); extern void seq_desc(StringInfo buf, uint8 xl_info, char *rec); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 0875b99a22..472c07b508 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.365 2008/05/09 23:32:04 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.366 2008/05/16 23:36:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1452,6 +1452,7 @@ typedef struct TruncateStmt { NodeTag type; List *relations; /* relations (RangeVars) to be truncated */ + bool restart_seqs; /* restart owned sequences? */ DropBehavior behavior; /* RESTRICT or CASCADE behavior */ } TruncateStmt; diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out index 0576b575ee..84ece98357 100644 --- a/src/test/regress/expected/sequence.out +++ b/src/test/regress/expected/sequence.out @@ -99,9 +99,27 @@ DROP SEQUENCE sequence_test; CREATE SEQUENCE foo_seq; ALTER TABLE foo_seq RENAME TO foo_seq_new; SELECT * FROM foo_seq_new; - sequence_name | last_value | increment_by | max_value | min_value | cache_value | log_cnt | is_cycled | is_called ----------------+------------+--------------+---------------------+-----------+-------------+---------+-----------+----------- - foo_seq | 1 | 1 | 9223372036854775807 | 1 | 1 | 1 | f | f + sequence_name | last_value | start_value | increment_by | max_value | min_value | cache_value | log_cnt | is_cycled | is_called +---------------+------------+-------------+--------------+---------------------+-----------+-------------+---------+-----------+----------- + foo_seq | 1 | 1 | 1 | 9223372036854775807 | 1 | 1 | 1 | f | f +(1 row) + +SELECT nextval('foo_seq_new'); + nextval +--------- + 1 +(1 row) + +SELECT nextval('foo_seq_new'); + nextval +--------- + 2 +(1 row) + +SELECT * FROM foo_seq_new; + sequence_name | last_value | start_value | increment_by | max_value | min_value | cache_value | log_cnt | is_cycled | is_called +---------------+------------+-------------+--------------+---------------------+-----------+-------------+---------+-----------+----------- + foo_seq | 2 | 1 | 1 | 9223372036854775807 | 1 | 1 | 32 | f | t (1 row) DROP SEQUENCE foo_seq_new; @@ -155,18 +173,49 @@ SELECT nextval('sequence_test2'); 32 (1 row) -ALTER SEQUENCE sequence_test2 RESTART WITH 16 - INCREMENT BY 4 MAXVALUE 22 MINVALUE 5 CYCLE; +ALTER SEQUENCE sequence_test2 RESTART WITH 24 + INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE; +SELECT nextval('sequence_test2'); + nextval +--------- + 24 +(1 row) + SELECT nextval('sequence_test2'); nextval --------- - 16 + 28 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + 32 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + 36 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + 5 +(1 row) + +ALTER SEQUENCE sequence_test2 RESTART; +SELECT nextval('sequence_test2'); + nextval +--------- + 32 (1 row) SELECT nextval('sequence_test2'); nextval --------- - 20 + 36 (1 row) SELECT nextval('sequence_test2'); diff --git a/src/test/regress/expected/truncate.out b/src/test/regress/expected/truncate.out index ed6182c69f..929aba4da7 100644 --- a/src/test/regress/expected/truncate.out +++ b/src/test/regress/expected/truncate.out @@ -223,3 +223,41 @@ SELECT * FROM trunc_trigger_log; DROP TABLE trunc_trigger_test; DROP TABLE trunc_trigger_log; DROP FUNCTION trunctrigger(); +-- test TRUNCATE ... RESTART IDENTITY +CREATE SEQUENCE truncate_a_id1 START WITH 33; +CREATE TABLE truncate_a (id serial, + id1 integer default nextval('truncate_a_id1')); +NOTICE: CREATE TABLE will create implicit sequence "truncate_a_id_seq" for serial column "truncate_a.id" +ALTER SEQUENCE truncate_a_id1 OWNED BY truncate_a.id1; +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + id | id1 +----+----- + 1 | 33 + 2 | 34 +(2 rows) + +TRUNCATE truncate_a; +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + id | id1 +----+----- + 3 | 35 + 4 | 36 +(2 rows) + +TRUNCATE truncate_a RESTART IDENTITY; +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + id | id1 +----+----- + 1 | 33 + 2 | 34 +(2 rows) + +DROP TABLE truncate_a; +SELECT nextval('truncate_a_id1'); -- fail, seq should have been dropped +ERROR: relation "truncate_a_id1" does not exist diff --git a/src/test/regress/sql/sequence.sql b/src/test/regress/sql/sequence.sql index 806a718192..f3eb81ec13 100644 --- a/src/test/regress/sql/sequence.sql +++ b/src/test/regress/sql/sequence.sql @@ -33,6 +33,9 @@ DROP SEQUENCE sequence_test; CREATE SEQUENCE foo_seq; ALTER TABLE foo_seq RENAME TO foo_seq_new; SELECT * FROM foo_seq_new; +SELECT nextval('foo_seq_new'); +SELECT nextval('foo_seq_new'); +SELECT * FROM foo_seq_new; DROP SEQUENCE foo_seq_new; -- renaming serial sequences @@ -68,8 +71,16 @@ CREATE SEQUENCE sequence_test2 START WITH 32; SELECT nextval('sequence_test2'); -ALTER SEQUENCE sequence_test2 RESTART WITH 16 - INCREMENT BY 4 MAXVALUE 22 MINVALUE 5 CYCLE; +ALTER SEQUENCE sequence_test2 RESTART WITH 24 + INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE; +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); + +ALTER SEQUENCE sequence_test2 RESTART; + SELECT nextval('sequence_test2'); SELECT nextval('sequence_test2'); SELECT nextval('sequence_test2'); diff --git a/src/test/regress/sql/truncate.sql b/src/test/regress/sql/truncate.sql index e60349e207..3cce3ee314 100644 --- a/src/test/regress/sql/truncate.sql +++ b/src/test/regress/sql/truncate.sql @@ -130,3 +130,29 @@ DROP TABLE trunc_trigger_test; DROP TABLE trunc_trigger_log; DROP FUNCTION trunctrigger(); + +-- test TRUNCATE ... RESTART IDENTITY +CREATE SEQUENCE truncate_a_id1 START WITH 33; +CREATE TABLE truncate_a (id serial, + id1 integer default nextval('truncate_a_id1')); +ALTER SEQUENCE truncate_a_id1 OWNED BY truncate_a.id1; + +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + +TRUNCATE truncate_a; + +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + +TRUNCATE truncate_a RESTART IDENTITY; + +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + +DROP TABLE truncate_a; + +SELECT nextval('truncate_a_id1'); -- fail, seq should have been dropped -- GitLab