Fix ALTER SEQUENCE OWNED BY to not rewrite the sequence relation.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 12 Jun 2017 20:57:31 +0000 (16:57 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 12 Jun 2017 20:57:31 +0000 (16:57 -0400)
It's not necessary for it to do that, since OWNED BY requires only ordinary
catalog updates and doesn't affect future sequence values.  And pg_upgrade
needs to use OWNED BY without having it change the sequence's relfilenode.
Commit 3d79013b9 broke this by making all forms of ALTER SEQUENCE change
the relfilenode; that seems to be the explanation for the hard-to-reproduce
buildfarm failures we've been seeing since then.

Discussion: https://postgr.es/m/19785.1497215827@sss.pgh.pa.us

src/backend/commands/sequence.c

index 7e85b69ab8fea68e7032a0416f287db21b3338a0..2d820e5cebc859bfd9dfbdef4fcb5b0cfd46d870 100644 (file)
@@ -101,7 +101,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
            bool isInit,
            Form_pg_sequence seqform,
-           Form_pg_sequence_data seqdataform, List **owned_by);
+           Form_pg_sequence_data seqdataform,
+           bool *need_seq_rewrite,
+           List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
 
@@ -115,6 +117,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
    FormData_pg_sequence seqform;
    FormData_pg_sequence_data seqdataform;
+   bool        need_seq_rewrite;
    List       *owned_by;
    CreateStmt *stmt = makeNode(CreateStmt);
    Oid         seqoid;
@@ -153,7 +156,9 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
    }
 
    /* Check and set all option values */
-   init_params(pstate, seq->options, seq->for_identity, true, &seqform, &seqdataform, &owned_by);
+   init_params(pstate, seq->options, seq->for_identity, true,
+               &seqform, &seqdataform,
+               &need_seq_rewrite, &owned_by);
 
    /*
     * Create relation (and fill value[] and null[] for the tuple)
@@ -417,6 +422,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
    HeapTupleData datatuple;
    Form_pg_sequence seqform;
    Form_pg_sequence_data newdataform;
+   bool        need_seq_rewrite;
    List       *owned_by;
    ObjectAddress address;
    Relation    rel;
@@ -461,35 +467,41 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
    UnlockReleaseBuffer(buf);
 
    /* Check and set new values */
-   init_params(pstate, stmt->options, stmt->for_identity, false, seqform,
-               newdataform, &owned_by);
+   init_params(pstate, stmt->options, stmt->for_identity, false,
+               seqform, newdataform,
+               &need_seq_rewrite, &owned_by);
 
    /* Clear local cache so that we don't think we have cached numbers */
    /* Note that we do not change the currval() state */
    elm->cached = elm->last;
 
-   /* check the comment above nextval_internal()'s equivalent call. */
-   if (RelationNeedsWAL(seqrel))
-       GetTopTransactionId();
+   /* If needed, rewrite the sequence relation itself */
+   if (need_seq_rewrite)
+   {
+       /* check the comment above nextval_internal()'s equivalent call. */
+       if (RelationNeedsWAL(seqrel))
+           GetTopTransactionId();
 
-   /*
-    * Create a new storage file for the sequence, making the state changes
-    * transactional.  We want to keep the sequence's relfrozenxid at 0, since
-    * it won't contain any unfrozen XIDs.  Same with relminmxid, since a
-    * sequence will never contain multixacts.
-    */
-   RelationSetNewRelfilenode(seqrel, seqrel->rd_rel->relpersistence,
-                             InvalidTransactionId, InvalidMultiXactId);
+       /*
+        * Create a new storage file for the sequence, making the state
+        * changes transactional.  We want to keep the sequence's relfrozenxid
+        * at 0, since it won't contain any unfrozen XIDs.  Same with
+        * relminmxid, since a sequence will never contain multixacts.
+        */
+       RelationSetNewRelfilenode(seqrel, seqrel->rd_rel->relpersistence,
+                                 InvalidTransactionId, InvalidMultiXactId);
 
-   /*
-    * Insert the modified tuple into the new storage file.
-    */
-   fill_seq_with_data(seqrel, newdatatuple);
+       /*
+        * Insert the modified tuple into the new storage file.
+        */
+       fill_seq_with_data(seqrel, newdatatuple);
+   }
 
    /* process OWNED BY if given */
    if (owned_by)
        process_owned_by(seqrel, owned_by, stmt->for_identity);
 
+   /* update the pg_sequence tuple (we could skip this in some cases...) */
    CatalogTupleUpdate(rel, &seqtuple->t_self, seqtuple);
 
    InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
@@ -1204,19 +1216,28 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and seqdataform for changes to the sequence
- * relation itself.  Set *changed_seqform to true if seqform was changed
- * (interesting for ALTER SEQUENCE).  Also set *owned_by to any OWNED BY
- * option, or to NIL if there is none.
+ * into the pg_sequence catalog, and fields of seqdataform for changes to the
+ * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
+ * parameters that require rewriting the sequence's relation (interesting for
+ * ALTER SEQUENCE).  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.
+ *
+ * Note: we force a sequence rewrite whenever we change parameters that affect
+ * generation of future sequence values, even if the seqdataform per se is not
+ * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
+ * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
+ * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
+ * break pg_upgrade by causing unwanted changes in the sequence's relfilenode.
  */
 static void
 init_params(ParseState *pstate, List *options, bool for_identity,
            bool isInit,
            Form_pg_sequence seqform,
            Form_pg_sequence_data seqdataform,
+           bool *need_seq_rewrite,
            List **owned_by)
 {
    DefElem    *as_type = NULL;
@@ -1231,6 +1252,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
    bool        reset_max_value = false;
    bool        reset_min_value = false;
 
+   *need_seq_rewrite = false;
    *owned_by = NIL;
 
    foreach(option, options)
@@ -1245,6 +1267,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
                         errmsg("conflicting or redundant options"),
                         parser_errposition(pstate, defel->location)));
            as_type = defel;
+           *need_seq_rewrite = true;
        }
        else if (strcmp(defel->defname, "increment") == 0)
        {
@@ -1254,6 +1277,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
                         errmsg("conflicting or redundant options"),
                         parser_errposition(pstate, defel->location)));
            increment_by = defel;
+           *need_seq_rewrite = true;
        }
        else if (strcmp(defel->defname, "start") == 0)
        {
@@ -1263,6 +1287,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
                         errmsg("conflicting or redundant options"),
                         parser_errposition(pstate, defel->location)));
            start_value = defel;
+           *need_seq_rewrite = true;
        }
        else if (strcmp(defel->defname, "restart") == 0)
        {
@@ -1272,6 +1297,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
                         errmsg("conflicting or redundant options"),
                         parser_errposition(pstate, defel->location)));
            restart_value = defel;
+           *need_seq_rewrite = true;
        }
        else if (strcmp(defel->defname, "maxvalue") == 0)
        {
@@ -1281,6 +1307,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
                         errmsg("conflicting or redundant options"),
                         parser_errposition(pstate, defel->location)));
            max_value = defel;
+           *need_seq_rewrite = true;
        }
        else if (strcmp(defel->defname, "minvalue") == 0)
        {
@@ -1290,6 +1317,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
                         errmsg("conflicting or redundant options"),
                         parser_errposition(pstate, defel->location)));
            min_value = defel;
+           *need_seq_rewrite = true;
        }
        else if (strcmp(defel->defname, "cache") == 0)
        {
@@ -1299,6 +1327,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
                         errmsg("conflicting or redundant options"),
                         parser_errposition(pstate, defel->location)));
            cache_value = defel;
+           *need_seq_rewrite = true;
        }
        else if (strcmp(defel->defname, "cycle") == 0)
        {
@@ -1308,6 +1337,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
                         errmsg("conflicting or redundant options"),
                         parser_errposition(pstate, defel->location)));
            is_cycled = defel;
+           *need_seq_rewrite = true;
        }
        else if (strcmp(defel->defname, "owned_by") == 0)
        {