I've had another go at this, and I've found a solution that appears to address all the issues I'm aware of. It's all very similar to the previously discussed patches. The main difference is that previous patches had attempted to use something like tbinfo->attislocal to determine whether a column was inherited, but that's not correct. This patch uses the existing logic in flagInhAttrs() to find whether there is a matching parent column with a generation expression. I've added pg_dump test cases here to check the different variations that the code addresses.

--
Peter Eisentraut
2ndQuadrant, an EDB company
https://www.2ndquadrant.com/
From 6cace6e6e7935844272fb201fc0096d8f2381d00 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Fri, 29 Jan 2021 13:20:41 +0100
Subject: [PATCH v4] pg_dump: Fix dumping of inherited generated columns

Generation expressions of generated columns are always inherited, so
there is no need to set them separately in child tables, and there is
no syntax to do so either.  The code previously used the code paths
for the handling of default values, for which different rules apply;
in particular it might want to set a default value explicitly for an
inherited column.  This resulted in unrestorable dumps.  For generated
columns, just skip them in inherited tables.

Discussion: 
https://www.postgresql.org/message-id/flat/15830.1575468847%40sss.pgh.pa.us
---
 src/bin/pg_dump/common.c         | 31 ++++++++++++++++-----
 src/bin/pg_dump/pg_dump.c        | 37 +++++++++++++++++++++----
 src/bin/pg_dump/t/002_pg_dump.pl | 46 ++++++++++++++++++++++++++++++++
 3 files changed, 102 insertions(+), 12 deletions(-)

diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index b0f02bc1f6..1a261a5545 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -467,12 +467,22 @@ flagInhIndexes(Archive *fout, TableInfo tblinfo[], int 
numTables)
 /* flagInhAttrs -
  *      for each dumpable table in tblinfo, flag its inherited attributes
  *
- * What we need to do here is detect child columns that inherit NOT NULL
- * bits from their parents (so that we needn't specify that again for the
- * child) and child columns that have DEFAULT NULL when their parents had
- * some non-null default.  In the latter case, we make up a dummy AttrDefInfo
- * object so that we'll correctly emit the necessary DEFAULT NULL clause;
- * otherwise the backend will apply an inherited default to the column.
+ * What we need to do here is:
+ *
+ * - Detect child columns that inherit NOT NULL bits from their parents, so
+ *   that we needn't specify that again for the child.
+ *
+ * - Detect child columns that have DEFAULT NULL when their parents had some
+ *   non-null default.  In this case, we make up a dummy AttrDefInfo object so
+ *   that we'll correctly emit the necessary DEFAULT NULL clause; otherwise
+ *   the backend will apply an inherited default to the column.
+ *
+ * - Detect child columns that have a generation expression when their parents
+ *   also have one.  Generation expressions are always inherited, so there is
+ *   no need to set them again in child tables, and there is no syntax for it
+ *   either.  (Exception: In binary upgrade mode we dump them because
+ *   inherited tables are recreated standalone first and then reattached to
+ *   the parent.)
  *
  * modifies tblinfo
  */
@@ -510,6 +520,7 @@ flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int 
numTables)
                {
                        bool            foundNotNull;   /* Attr was NOT NULL in 
a parent */
                        bool            foundDefault;   /* Found a default in a 
parent */
+                       bool            foundGenerated; /* Found a generated in 
a parent */
 
                        /* no point in examining dropped columns */
                        if (tbinfo->attisdropped[j])
@@ -517,6 +528,7 @@ flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int 
numTables)
 
                        foundNotNull = false;
                        foundDefault = false;
+                       foundGenerated = false;
                        for (k = 0; k < numParents; k++)
                        {
                                TableInfo  *parent = parents[k];
@@ -528,7 +540,8 @@ flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int 
numTables)
                                if (inhAttrInd >= 0)
                                {
                                        foundNotNull |= 
parent->notnull[inhAttrInd];
-                                       foundDefault |= 
(parent->attrdefs[inhAttrInd] != NULL);
+                                       foundDefault |= 
(parent->attrdefs[inhAttrInd] != NULL && !parent->attgenerated[inhAttrInd]);
+                                       foundGenerated |= 
parent->attgenerated[inhAttrInd];
                                }
                        }
 
@@ -570,6 +583,10 @@ flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int 
numTables)
 
                                tbinfo->attrdefs[j] = attrDef;
                        }
+
+                       /* Remove generation expression from child */
+                       if (foundGenerated && !dopt->binary_upgrade)
+                               tbinfo->attrdefs[j] = NULL;
                }
        }
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 798d14580e..81a83b4a12 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -8839,13 +8839,37 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int 
numTables)
                                attrdefs[j].dobj.dump = tbinfo->dobj.dump;
 
                                /*
-                                * Defaults on a VIEW must always be dumped as 
separate ALTER
-                                * TABLE commands.  Defaults on regular tables 
are dumped as
-                                * part of the CREATE TABLE if possible, which 
it won't be if
-                                * the column is not going to be emitted 
explicitly.
+                                * Figure out whether the default/generation 
expression should
+                                * be dumped as part of the main CREATE TABLE 
(or similar)
+                                * command or as a separate ALTER TABLE (or 
similar) command.
+                                * The preference is to put it into the CREATE 
command, but in
+                                * some cases that's not possible.
                                 */
-                               if (tbinfo->relkind == RELKIND_VIEW)
+                               if (tbinfo->attgenerated[adnum - 1])
                                {
+                                       /*
+                                        * Column generation expressions cannot 
be dumped
+                                        * separately, because there is no 
syntax for it.  The
+                                        * !shouldPrintColumn case below will 
be tempted to set
+                                        * them to separate if they are 
attached to an inherited
+                                        * column without a local definition, 
but that would be
+                                        * wrong and unnecessary, because 
generation expressions
+                                        * are always inherited, so there is no 
need to set them
+                                        * again in child tables, and there is 
no syntax for it
+                                        * either.  By setting separate to 
false here we prevent
+                                        * the "default" from being processed 
as its own dumpable
+                                        * object, and flagInhAttrs() will 
remove it from the
+                                        * table when it detects that it 
belongs to an inherited
+                                        * column.
+                                        */
+                                       attrdefs[j].separate = false;
+                               }
+                               else if (tbinfo->relkind == RELKIND_VIEW)
+                               {
+                                       /*
+                                        * Defaults on a VIEW must always be 
dumped as separate
+                                        * ALTER TABLE commands.
+                                        */
                                        attrdefs[j].separate = true;
                                }
                                else if (!shouldPrintColumn(dopt, tbinfo, adnum 
- 1))
@@ -8856,7 +8880,10 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int 
numTables)
                                else
                                {
                                        attrdefs[j].separate = false;
+                               }
 
+                               if (!attrdefs[j].separate)
+                               {
                                        /*
                                         * Mark the default as needing to 
appear before the table,
                                         * so that any dependencies it has must 
be emitted before
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 798884da36..737e46464a 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2493,6 +2493,52 @@
                unlike => { exclude_dump_test_schema => 1, },
        },
 
+       'CREATE TABLE test_table_generated_child1 (without local columns)' => {
+               create_order => 4,
+               create_sql   => 'CREATE TABLE 
dump_test.test_table_generated_child1 ()
+                                                INHERITS 
(dump_test.test_table_generated);',
+               regexp => qr/^
+                       \QCREATE TABLE dump_test.test_table_generated_child1 
(\E\n
+                       \)\n
+                       \QINHERITS (dump_test.test_table_generated);\E\n
+                       /xms,
+               like =>
+                 { %full_runs, %dump_test_schema_runs, section_pre_data => 1, 
},
+               unlike => {
+                       binary_upgrade           => 1,
+                       exclude_dump_test_schema => 1,
+               },
+       },
+
+       'ALTER TABLE test_table_generated_child1' => {
+               regexp =>
+                 qr/^\QALTER TABLE ONLY dump_test.test_table_generated_child1 
ALTER COLUMN col2 \E/m,
+
+               # should not get emitted
+               like => {},
+       },
+
+       'CREATE TABLE test_table_generated_child2 (with local columns)' => {
+               create_order => 4,
+               create_sql   => 'CREATE TABLE 
dump_test.test_table_generated_child2 (
+                                                  col1 int,
+                                                  col2 int
+                                                ) INHERITS 
(dump_test.test_table_generated);',
+               regexp => qr/^
+                       \QCREATE TABLE dump_test.test_table_generated_child2 
(\E\n
+                       \s+\Qcol1 integer,\E\n
+                       \s+\Qcol2 integer\E\n
+                       \)\n
+                       \QINHERITS (dump_test.test_table_generated);\E\n
+                       /xms,
+               like =>
+                 { %full_runs, %dump_test_schema_runs, section_pre_data => 1, 
},
+               unlike => {
+                       binary_upgrade           => 1,
+                       exclude_dump_test_schema => 1,
+               },
+       },
+
        'CREATE TABLE table_with_stats' => {
                create_order => 98,
                create_sql   => 'CREATE TABLE dump_test.table_index_stats (

base-commit: b034ef9b376dbe712caa076541d6a750f37d85ce
-- 
2.30.0

Reply via email to