aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser/parse_utilcmd.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2024-09-17 15:53:26 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2024-09-17 15:53:35 -0400
commit918e21d25178c8ae09808c581a782002f702ed9e (patch)
treed15798662be397175ee9ece367a5a25537d004a5 /src/backend/parser/parse_utilcmd.c
parent2520226c953c0b443791a185a8d1fb8b71d9fe9e (diff)
downloadpostgresql-918e21d25178c8ae09808c581a782002f702ed9e.tar.gz
postgresql-918e21d25178c8ae09808c581a782002f702ed9e.zip
Repair pg_upgrade for identity sequences with non-default persistence.
Since we introduced unlogged sequences in v15, identity sequences have defaulted to having the same persistence as their owning table. However, it is possible to change that with ALTER SEQUENCE, and pg_dump tries to preserve the logged-ness of sequences when it doesn't match (as indeed it wouldn't for an unlogged table from before v15). The fly in the ointment is that ALTER SEQUENCE SET [UN]LOGGED fails in binary-upgrade mode, because it needs to assign a new relfilenode which we cannot permit in that mode. Thus, trying to pg_upgrade a database containing a mismatching identity sequence failed. To fix, add syntax to ADD/ALTER COLUMN GENERATED AS IDENTITY to allow the sequence's persistence to be set correctly at creation, and use that instead of ALTER SEQUENCE SET [UN]LOGGED in pg_dump. (I tried to make SET [UN]LOGGED work without any pg_dump modifications, but that seems too fragile to be a desirable answer. This way should be markedly faster anyhow.) In passing, document the previously-undocumented SEQUENCE NAME option that pg_dump also relies on for identity sequences; I see no value in trying to pretend it doesn't exist. Per bug #18618 from Anthony Hsu. Back-patch to v15 where we invented this stuff. Discussion: https://postgr.es/m/18618-d4eb26d669ed110a@postgresql.org
Diffstat (limited to 'src/backend/parser/parse_utilcmd.c')
-rw-r--r--src/backend/parser/parse_utilcmd.c80
1 files changed, 54 insertions, 26 deletions
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 15274607542..1e15ce10b48 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -365,30 +365,22 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
{
ListCell *option;
DefElem *nameEl = NULL;
+ DefElem *loggedEl = NULL;
Oid snamespaceid;
char *snamespace;
char *sname;
+ char seqpersistence;
CreateSeqStmt *seqstmt;
AlterSeqStmt *altseqstmt;
List *attnamelist;
- int nameEl_idx = -1;
/* Make a copy of this as we may end up modifying it in the code below */
seqoptions = list_copy(seqoptions);
/*
- * Determine namespace and name to use for the sequence.
- *
- * First, check if a sequence name was passed in as an option. This is
- * used by pg_dump. Else, generate a name.
- *
- * Although we use ChooseRelationName, it's not guaranteed that the
- * selected sequence name won't conflict; given sufficiently long field
- * names, two different serial columns in the same table could be assigned
- * the same sequence name, and we'd not notice since we aren't creating
- * the sequence quite yet. In practice this seems quite unlikely to be a
- * problem, especially since few people would need two serial columns in
- * one table.
+ * Check for non-SQL-standard options (not supported within CREATE
+ * SEQUENCE, because they'd be redundant), and remove them from the
+ * seqoptions list if found.
*/
foreach(option, seqoptions)
{
@@ -399,12 +391,24 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
if (nameEl)
errorConflictingDefElem(defel, cxt->pstate);
nameEl = defel;
- nameEl_idx = foreach_current_index(option);
+ seqoptions = foreach_delete_current(seqoptions, option);
+ }
+ else if (strcmp(defel->defname, "logged") == 0 ||
+ strcmp(defel->defname, "unlogged") == 0)
+ {
+ if (loggedEl)
+ errorConflictingDefElem(defel, cxt->pstate);
+ loggedEl = defel;
+ seqoptions = foreach_delete_current(seqoptions, option);
}
}
+ /*
+ * Determine namespace and name to use for the sequence.
+ */
if (nameEl)
{
+ /* Use specified name */
RangeVar *rv = makeRangeVarFromNameList(castNode(List, nameEl->arg));
snamespace = rv->schemaname;
@@ -418,11 +422,20 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
snamespace = get_namespace_name(snamespaceid);
}
sname = rv->relname;
- /* Remove the SEQUENCE NAME item from seqoptions */
- seqoptions = list_delete_nth_cell(seqoptions, nameEl_idx);
}
else
{
+ /*
+ * Generate a name.
+ *
+ * Although we use ChooseRelationName, it's not guaranteed that the
+ * selected sequence name won't conflict; given sufficiently long
+ * field names, two different serial columns in the same table could
+ * be assigned the same sequence name, and we'd not notice since we
+ * aren't creating the sequence quite yet. In practice this seems
+ * quite unlikely to be a problem, especially since few people would
+ * need two serial columns in one table.
+ */
if (cxt->rel)
snamespaceid = RelationGetNamespace(cxt->rel);
else
@@ -444,22 +457,37 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
cxt->relation->relname, column->colname)));
/*
+ * Determine the persistence of the sequence. By default we copy the
+ * persistence of the table, but if LOGGED or UNLOGGED was specified, use
+ * that (as long as the table isn't TEMP).
+ *
+ * For CREATE TABLE, we get the persistence from cxt->relation, which
+ * comes from the CreateStmt in progress. For ALTER TABLE, the parser
+ * won't set cxt->relation->relpersistence, but we have cxt->rel as the
+ * existing table, so we copy the persistence from there.
+ */
+ seqpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence;
+ if (loggedEl)
+ {
+ if (seqpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot set logged status of a temporary sequence"),
+ parser_errposition(cxt->pstate, loggedEl->location)));
+ else if (strcmp(loggedEl->defname, "logged") == 0)
+ seqpersistence = RELPERSISTENCE_PERMANENT;
+ else
+ seqpersistence = RELPERSISTENCE_UNLOGGED;
+ }
+
+ /*
* Build a CREATE SEQUENCE command to create the sequence object, and add
* it to the list of things to be done before this CREATE/ALTER TABLE.
*/
seqstmt = makeNode(CreateSeqStmt);
seqstmt->for_identity = for_identity;
seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
-
- /*
- * Copy the persistence of the table. For CREATE TABLE, we get the
- * persistence from cxt->relation, which comes from the CreateStmt in
- * progress. For ALTER TABLE, the parser won't set
- * cxt->relation->relpersistence, but we have cxt->rel as the existing
- * table, so we copy the persistence from there.
- */
- seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence;
-
+ seqstmt->sequence->relpersistence = seqpersistence;
seqstmt->options = seqoptions;
/*