Thread: Re: pg_dump crash on identity sequence with not loaded attributes

Re: pg_dump crash on identity sequence with not loaded attributes

From
Tom Lane
Date:
Artur Zakirov <zaartur@gmail.com> writes:
> pg_dump crashes when a table is added into an extension and its
> identity sequence isn't.

Yup, reproduced here.

> The attached patch fixes the issue on pg_dump side by ignoring
> identity sequences if their table attributes are not loaded. I'm not
> sure if this is the best fix.

I don't like it much.  That test is trying to avoid exactly this
situation; why is it failing to do so?  I traced through it with
gdb and found that when we get here,

(gdb) s
7259                    if (owning_tab->dobj.dump == DUMP_COMPONENT_NONE &&
(gdb) p owning_tab->dobj.dump
$7 = 16
(gdb) p seqinfo->is_identity_sequence
$8 = true
(gdb) p owning_tab->interesting
$9 = false

That is, the owning table is marked to dump ACLs and nothing else.
Per getTables(), we only mark a table interesting if we need to
dump its DEFINITION or DATA component.  So kaboom.

I didn't check the git history, but I suspect that this code was
correct when written and got broken by init_privs changes, which
allowed the DUMP_COMPONENT_ACL flag to get set along with nothing
else (cf checkExtensionMembership).  I'm inclined to fix it
like this:

        /*
         * Only dump identity sequences if we're going to dump the table that
         * it belongs to.
         */
-       if (owning_tab->dobj.dump == DUMP_COMPONENT_NONE &&
+       if ((owning_tab->dobj.dump & DUMP_COMPONENT_DEFINITION) &&
            seqinfo->is_identity_sequence)
        {
            seqinfo->dobj.dump = DUMP_COMPONENT_NONE;

(The comment could use a bit of adjustment too.)  The idea here is
that so far as the user is concerned, an identity sequence is part
of the DDL that defines the table, so it should be dumped if and
only if we're dumping the table's definitional DDL.  Ancillary
stuff like ACLs shouldn't change this conclusion.

A different approach that we could take is to decide that by golly,
we're supposed to dump this sequence and we should do so.  That
could be done by forcing owning_tab->interesting to true just below
here.  However, in that case the entire bit quoted above seems wrong,
because we'd no longer be using the policy stated by the comment.
Certainly it still shouldn't matter whether the table is by chance
marked to dump ACLs.

I poked around for other places that might have a similar disease.
The only other direct test like "foo == DUMP_COMPONENT_NONE" is in
getPublicationNamespaces:

        /*
         * We always dump publication namespaces unless the corresponding
         * namespace is excluded from the dump.
         */
        if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
            continue;

I didn't try to break that but I suspect it is equally wrong, ie
it'll behave surprisingly differently for schemas that belong to
extensions vs. those that don't.  I'm less sure what to do here.
It seems like publishing a namespace that belongs to an extension
might not be an unreasonable thing to do, in which case users
would be sad if we suppressed it from the publication's dump.
Perhaps this should be like

        if (!((nspinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) ||
              (nspinfo->ext_member && extension-is-being-dumped)))
            continue;

            regards, tom lane



Re: pg_dump crash on identity sequence with not loaded attributes

From
Tom Lane
Date:
Artur Zakirov <zaartur@gmail.com> writes:
> On Mon, 9 Dec 2024 at 22:51, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> -       if (owning_tab->dobj.dump == DUMP_COMPONENT_NONE &&
>> +       if ((owning_tab->dobj.dump & DUMP_COMPONENT_DEFINITION) &&

> I think it is necessary to use negation in this condition.

D'oh, of course.  But what's your thoughts on the other points?
Is this what we want to do at all?

            regards, tom lane



Re: pg_dump crash on identity sequence with not loaded attributes

From
Artur Zakirov
Date:
On Tue, 10 Dec 2024 at 16:29, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > I think it is necessary to use negation in this condition.
>
> D'oh, of course.  But what's your thoughts on the other points?
> Is this what we want to do at all?

Alternatively I was thinking about this change:

                        continue;
                }

+               if (!(owning_tab->dobj.dump & DUMP_COMPONENT_DEFINITION) &&
+                       seqinfo->is_identity_sequence)
+                       seqinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
+
                /*
                 * Otherwise we need to dump the components that are
being dumped for
                 * the table and any components which the sequence is explicitly

This way we wouldn't ignore ACLs for example. Currently a user might
not have permissions to a sequence but still be able to read/write to
its table if they have permissions. Although the user cannot execute
nextval/setval explicitly.
If an admin grants permissions to the sequence explicitly (for some
reason) the user will be able to execute nextval/setval. And that will
be lost during dump/restore. Currently this doesn't work at all and
ignoring sequences won't break anything.

Ideally I would like to have the ability to dump ACL of those
sequences too, even though this looks like a quite narrow use case.

Alternatively, instead of forcing owning_tab->interesting to true, I
think we could always initialize owning_tab's attributes (i.e. arrays
like owning_tab->attnames, owning_tab->attidenity), which are used by
dumpSequence() and which causes the crash. Are there any downsides of
it?

-- 
Kind regards,
Artur



Re: pg_dump crash on identity sequence with not loaded attributes

From
Tom Lane
Date:
Artur Zakirov <zaartur@gmail.com> writes:
> Alternatively, instead of forcing owning_tab->interesting to true, I
> think we could always initialize owning_tab's attributes (i.e. arrays
> like owning_tab->attnames, owning_tab->attidenity), which are used by
> dumpSequence() and which causes the crash. Are there any downsides of
> it?

Lots.  The entire point of the ->interesting flag is to avoid fetching
additional details about tables that we don't really care about.
Unless I misunderstand, you're proposing throwing away that whole
optimization, which has got to be an overall loss.

            regards, tom lane



Re: pg_dump crash on identity sequence with not loaded attributes

From
Artur Zakirov
Date:
On Tue, 10 Dec 2024 at 19:08, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Artur Zakirov <zaartur@gmail.com> writes:
> > Alternatively, instead of forcing owning_tab->interesting to true, I
> > think we could always initialize owning_tab's attributes (i.e. arrays
> > like owning_tab->attnames, owning_tab->attidenity), which are used by
> > dumpSequence() and which causes the crash. Are there any downsides of
> > it?
>
> Lots.  The entire point of the ->interesting flag is to avoid fetching
> additional details about tables that we don't really care about.
> Unless I misunderstand, you're proposing throwing away that whole
> optimization, which has got to be an overall loss.

Yeah, I rechecked the code and it seems getTableAttrs() is called
later than getOwnedSeqs(). And we can set owning_tab->interesting to
true to load data only of needed tables.

I think it will still be necessary to negate DUMP_COMPONENT_DEFINITION
from seqinfo->dobj.dump because we shouldn't dump statements like ...
ALTER COLUMN ... ADD GENERATED ..., if the table's definition isn't
dumped.

-- 
Kind regards,
Artur



Re: pg_dump crash on identity sequence with not loaded attributes

From
Tom Lane
Date:
Artur Zakirov <zaartur@gmail.com> writes:
> I think it will still be necessary to negate DUMP_COMPONENT_DEFINITION
> from seqinfo->dobj.dump because we shouldn't dump statements like ...
> ALTER COLUMN ... ADD GENERATED ..., if the table's definition isn't
> dumped.

Yeah.  After chewing on this for awhile, I think the cleanest solution
is say that b965f2617 was just wrong, and we should revert it in
favor of adopting this logic:

        if (seqinfo->is_identity_sequence)
            seqinfo->dobj.dump = owning_tab->dobj.dump;
        else
            seqinfo->dobj.dump |= owning_tab->dobj.dump;

That is, for an identity sequence we should dump the exact same
properties as for its owning table.  This basically rejects the
notion that a table can be inside an extension while its identity
sequence is not (or vice versa), which is the same position we
take for indexes, rules, etc.  However, for non-identity owned
sequences (i.e. serials) keep the same behavior as before.
The large comment block just below here (added by f9e439b1c)
carefully describes the behavior that was wanted for non-identity
sequences, and I don't think we need to break that.

The attached patch also gets rid of the dubious coding in
getPublicationNamespaces.  We might get push-back on that ignoring
schemas belonging to extensions, but if so I'd prefer to see the
behavior coded in a more transparent fashion.

            regards, tom lane

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ec0cdf4ed7..53dc5cb2b3 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4538,7 +4538,7 @@ getPublicationNamespaces(Archive *fout)
          * We always dump publication namespaces unless the corresponding
          * namespace is excluded from the dump.
          */
-        if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+        if (!(nspinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
             continue;

         /* OK, make a DumpableObject for this relationship */
@@ -7253,20 +7253,15 @@ getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables)
                      seqinfo->owning_tab, seqinfo->dobj.catId.oid);

         /*
-         * Only dump identity sequences if we're going to dump the table that
-         * it belongs to.
-         */
-        if (owning_tab->dobj.dump == DUMP_COMPONENT_NONE &&
-            seqinfo->is_identity_sequence)
-        {
-            seqinfo->dobj.dump = DUMP_COMPONENT_NONE;
-            continue;
-        }
-
-        /*
-         * Otherwise we need to dump the components that are being dumped for
-         * the table and any components which the sequence is explicitly
-         * marked with.
+         * For an identity sequence, dump exactly the same components for the
+         * sequence as for the owning table.  This is important because we
+         * treat the identity sequence as an integral part of the table.  For
+         * example, there is not any DDL command that allows creation of such
+         * a sequence independently of the table.
+         *
+         * For other owned sequences such as serial sequences, we need to dump
+         * the components that are being dumped for the table and any
+         * components that the sequence is explicitly marked with.
          *
          * We can't simply use the set of components which are being dumped
          * for the table as the table might be in an extension (and only the
@@ -7279,10 +7274,17 @@ getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables)
          * marked by checkExtensionMembership() and this will be a no-op as
          * the table will be equivalently marked.
          */
-        seqinfo->dobj.dump = seqinfo->dobj.dump | owning_tab->dobj.dump;
+        if (seqinfo->is_identity_sequence)
+            seqinfo->dobj.dump = owning_tab->dobj.dump;
+        else
+            seqinfo->dobj.dump |= owning_tab->dobj.dump;

+        /* Make sure that necessary data is available if we're dumping it */
         if (seqinfo->dobj.dump != DUMP_COMPONENT_NONE)
+        {
             seqinfo->interesting = true;
+            owning_tab->interesting = true;
+        }
     }
 }

diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2e55a0e3bb..9c5ddd20cf 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -89,8 +89,16 @@ typedef enum
 /*
  * DumpComponents is a bitmask of the potentially dumpable components of
  * a database object: its core definition, plus optional attributes such
- * as ACL, comments, etc.  The NONE and ALL symbols are convenient
- * shorthands.
+ * as ACL, comments, etc.
+ *
+ * The NONE and ALL symbols are convenient shorthands for assigning values,
+ * but be careful about using them in tests.  For example, a test like
+ * "if (dobj->dump == DUMP_COMPONENT_NONE)" is probably wrong; you likely want
+ * "if (!(dobj->dump & DUMP_COMPONENT_DEFINITION))" instead.  This is because
+ * we aren't too careful about the values of irrelevant bits, as indeed can be
+ * seen in the definition of DUMP_COMPONENT_ALL.  It's also possible that an
+ * object has only subsidiary bits such as DUMP_COMPONENT_ACL set, leading to
+ * unexpected behavior of a test against NONE.
  */
 typedef uint32 DumpComponents;
 #define DUMP_COMPONENT_NONE            (0)

Re: pg_dump crash on identity sequence with not loaded attributes

From
Artur Zakirov
Date:
On Tue, 10 Dec 2024 at 22:15, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Yeah.  After chewing on this for awhile, I think the cleanest solution
> is say that b965f2617 was just wrong, and we should revert it in
> favor of adopting this logic:
>
>         if (seqinfo->is_identity_sequence)
>             seqinfo->dobj.dump = owning_tab->dobj.dump;
>         else
>             seqinfo->dobj.dump |= owning_tab->dobj.dump;

Thank you for the new version of the patch. It looks good to me and it
works as expected after I tested it.

> The attached patch also gets rid of the dubious coding in
> getPublicationNamespaces.  We might get push-back on that ignoring
> schemas belonging to extensions, but if so I'd prefer to see the
> behavior coded in a more transparent fashion.

I don't have a strong opinion about this part and I'm not sure that we
need to ignore mapping between a namespace and a publication if the
namespace belongs to the extension. Maybe we could always dump the
mapping if the publication itself is dumped.

But other than that it seems the patch breaks dumps of mapping of a
publication with the public namespace. The issue with the public
namespace is mentioned in the original thread of the feature of adding
schema mapping [1]. I couldn't find in the thread if it was addressed
in the end. With the new patch the following mapping won't be mapped:

    create publication pub_test for tables in schema public;

pg_dump won't generate corresponding "ALTER PUBLICATION pub_test ADD
TABLES IN SCHEMA public".

1.
https://www.postgresql.org/message-id/OS0PR01MB61137F0B8498E9241F2D960EFB149%40OS0PR01MB6113.jpnprd01.prod.outlook.com

-- 
Kind regards,
Artur



Re: pg_dump crash on identity sequence with not loaded attributes

From
Tom Lane
Date:
Artur Zakirov <zaartur@gmail.com> writes:
> On Tue, 10 Dec 2024 at 22:15, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> The attached patch also gets rid of the dubious coding in
>> getPublicationNamespaces.  We might get push-back on that ignoring
>> schemas belonging to extensions, but if so I'd prefer to see the
>> behavior coded in a more transparent fashion.

> But other than that it seems the patch breaks dumps of mapping of a
> publication with the public namespace. The issue with the public
> namespace is mentioned in the original thread of the feature of adding
> schema mapping [1]. I couldn't find in the thread if it was addressed
> in the end. With the new patch the following mapping won't be mapped:
>     create publication pub_test for tables in schema public;

Ouch.  Okay, I agree that's probably bad, but then it raises the
question of why this filter exists at all.  It certainly seems
100% magic and undocumented that it does the "right" thing for
these cases.

In any case, that's mostly orthogonal to the problem with identity
sequences.  I now propose leaving out that change and just pushing
the change in getOwnedSeqs().  The publication-namespace business
should be discussed on its own thread.

            regards, tom lane



Re: pg_dump crash on identity sequence with not loaded attributes

From
Artur Zakirov
Date:
On Wed, 11 Dec 2024 at 16:48, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> In any case, that's mostly orthogonal to the problem with identity
> sequences.  I now propose leaving out that change and just pushing
> the change in getOwnedSeqs().  The publication-namespace business
> should be discussed on its own thread.

+1, agree it looks like a bit different issue and for now we can fix
only the issue with sequences.

I found another inconsistency with publishing mapping and just mention
it here. According to the code of selectDumpableNamespace() namespaces
"pg_*" and "information_schema" are ignored by pg_dump. But it is
possible to create mapping for publications with "information_schema"
and those mappings will be lost by dump/restore. But I agree that
publication-namespace should have its own thread.

-- 
Kind regards,
Artur