Fix dumps of partitioned tables with table AMs
authorMichael Paquier <[email protected]>
Mon, 22 Apr 2024 06:15:36 +0000 (15:15 +0900)
committerMichael Paquier <[email protected]>
Mon, 22 Apr 2024 06:15:36 +0000 (15:15 +0900)
pg_dump/restore failed to properly set the table access method for
partitioned tables, as it relies on SET queries that would change
default_table_access_method.  However, SET affects only tables and
materialized views, not partitioned tables which would always be
restored with their pg_class.relam set to 0, losing their table AM set
by either a CREATE TABLE .. USING or by a ALTER TABLE .. SET ACCESS
METHOD.

Appending a USING clause to the definition of CREATE TABLE is not
possible as users may specify --no-table-access-method at restore or for
a dump, meaning that the table AM portions may have to be skipped.
Rather than SET, the solution used by this commit is to generate an
extra ALTER TABLE .. SET ACCESS METHOD when restoring a partitioned
table, based on the table AM set in its TOC entry.  The choice of using
a SET query or an ALTER TABLE query for a relation requires the addition
of the relkind to the TOC entry to be able to choose between one or the
other.  Note that using ALTER TABLE SET ACCESS METHOD on a relation with
physical storage would require a full rewrite, which would be costly for
one.  This also creates problems with binary upgrades where the rewrite
would not be able to keep the OID of the relation consistent across the
upgrade.

This commit would normally require a protocol bump, but a45c78e3284b has
already done one for this release cycle.

Regression tests are adjusted with the new expected output, with some
tweaks for the table AMs of the partitions to make the output more
readable.

Issue introduced by 374c7a229042, that has added support for table AMs
in partitioned tables.

Author: Michael Paquier
Reviewed-by: Álvaro Herrera
Discussion: https://postgr.es/m/[email protected]

src/bin/pg_dump/pg_backup_archiver.c
src/bin/pg_dump/pg_backup_archiver.h
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/t/002_pg_dump.pl

index c7a6c918a6557c3d89158aa4d706fd7ea8081473..c6c101c118b7ab59d38d2a3008dbcef9a0ca9c30 100644 (file)
@@ -30,6 +30,7 @@
 #include <io.h>
 #endif
 
+#include "catalog/pg_class_d.h"
 #include "common/string.h"
 #include "compress_io.h"
 #include "dumputils.h"
@@ -62,6 +63,8 @@ static void _becomeOwner(ArchiveHandle *AH, TocEntry *te);
 static void _selectOutputSchema(ArchiveHandle *AH, const char *schemaName);
 static void _selectTablespace(ArchiveHandle *AH, const char *tablespace);
 static void _selectTableAccessMethod(ArchiveHandle *AH, const char *tableam);
+static void _printTableAccessMethodNoStorage(ArchiveHandle *AH,
+                                            TocEntry *te);
 static void processEncodingEntry(ArchiveHandle *AH, TocEntry *te);
 static void processStdStringsEntry(ArchiveHandle *AH, TocEntry *te);
 static void processSearchPathEntry(ArchiveHandle *AH, TocEntry *te);
@@ -1222,6 +1225,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
    newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL;
    newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL;
    newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL;
+   newToc->relkind = opts->relkind;
    newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL;
    newToc->desc = pg_strdup(opts->description);
    newToc->defn = opts->createStmt ? pg_strdup(opts->createStmt) : NULL;
@@ -2602,6 +2606,7 @@ WriteToc(ArchiveHandle *AH)
        WriteStr(AH, te->namespace);
        WriteStr(AH, te->tablespace);
        WriteStr(AH, te->tableam);
+       WriteInt(AH, te->relkind);
        WriteStr(AH, te->owner);
        WriteStr(AH, "false");
 
@@ -2707,6 +2712,9 @@ ReadToc(ArchiveHandle *AH)
        if (AH->version >= K_VERS_1_14)
            te->tableam = ReadStr(AH);
 
+       if (AH->version >= K_VERS_1_16)
+           te->relkind = ReadInt(AH);
+
        te->owner = ReadStr(AH);
        is_supported = true;
        if (AH->version < K_VERS_1_9)
@@ -3567,6 +3575,51 @@ _selectTableAccessMethod(ArchiveHandle *AH, const char *tableam)
    AH->currTableAm = pg_strdup(want);
 }
 
+/*
+ * Set the proper default table access method for a table without storage.
+ * Currently, this is required only for partitioned tables with a table AM.
+ */
+static void
+_printTableAccessMethodNoStorage(ArchiveHandle *AH, TocEntry *te)
+{
+   RestoreOptions *ropt = AH->public.ropt;
+   const char *tableam = te->tableam;
+   PQExpBuffer cmd;
+
+   /* do nothing in --no-table-access-method mode */
+   if (ropt->noTableAm)
+       return;
+
+   if (!tableam)
+       return;
+
+   Assert(te->relkind == RELKIND_PARTITIONED_TABLE);
+
+   cmd = createPQExpBuffer();
+
+   appendPQExpBufferStr(cmd, "ALTER TABLE ");
+   appendPQExpBuffer(cmd, "%s ", fmtQualifiedId(te->namespace, te->tag));
+   appendPQExpBuffer(cmd, "SET ACCESS METHOD %s;",
+                     fmtId(tableam));
+
+   if (RestoringToDB(AH))
+   {
+       PGresult   *res;
+
+       res = PQexec(AH->connection, cmd->data);
+
+       if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+           warn_or_exit_horribly(AH,
+                                 "could not alter table access method: %s",
+                                 PQerrorMessage(AH->connection));
+       PQclear(res);
+   }
+   else
+       ahprintf(AH, "%s\n\n", cmd->data);
+
+   destroyPQExpBuffer(cmd);
+}
+
 /*
  * Extract an object description for a TOC entry, and append it to buf.
  *
@@ -3673,11 +3726,17 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 {
    RestoreOptions *ropt = AH->public.ropt;
 
-   /* Select owner, schema, tablespace and default AM as necessary */
+   /*
+    * Select owner, schema, tablespace and default AM as necessary. The
+    * default access method for partitioned tables is handled after
+    * generating the object definition, as it requires an ALTER command
+    * rather than SET.
+    */
    _becomeOwner(AH, te);
    _selectOutputSchema(AH, te->namespace);
    _selectTablespace(AH, te->tablespace);
-   _selectTableAccessMethod(AH, te->tableam);
+   if (te->relkind != RELKIND_PARTITIONED_TABLE)
+       _selectTableAccessMethod(AH, te->tableam);
 
    /* Emit header comment for item */
    if (!AH->noTocComments)
@@ -3812,6 +3871,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
        }
    }
 
+   /*
+    * Select a partitioned table's default AM, once the table definition has
+    * been generated.
+    */
+   if (te->relkind == RELKIND_PARTITIONED_TABLE)
+       _printTableAccessMethodNoStorage(AH, te);
+
    /*
     * If it's an ACL entry, it might contain SET SESSION AUTHORIZATION
     * commands, so we can no longer assume we know the current auth setting.
index d6104a7196136099a4e165c672cbac086d5ad12e..ce5ed1dd395d630e24f6e5cef8648802d9880ca3 100644 (file)
@@ -69,7 +69,8 @@
                                                     * compression_algorithm
                                                     * in header */
 #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0) /* BLOB METADATA entries
-                                                    * and multiple BLOBS */
+                                                    * and multiple BLOBS,
+                                                    * relkind */
 
 /* Current archive version number (the format we can output) */
 #define K_VERS_MAJOR 1
@@ -353,6 +354,7 @@ struct _tocEntry
    char       *tablespace;     /* null if not in a tablespace; empty string
                                 * means use database default */
    char       *tableam;        /* table access method, only for TABLE tags */
+   char        relkind;        /* relation kind, only for TABLE tags */
    char       *owner;
    char       *desc;
    char       *defn;
@@ -393,6 +395,7 @@ typedef struct _archiveOpts
    const char *namespace;
    const char *tablespace;
    const char *tableam;
+   char        relkind;
    const char *owner;
    const char *description;
    teSection   section;
index 9edda904699dea0c3e89b4452429c021f74cff5f..ed9bab3bfe5b7dfb41898b52681cdfe49cfc8dc6 100644 (file)
@@ -16758,6 +16758,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
                                  .namespace = tbinfo->dobj.namespace->dobj.name,
                                  .tablespace = tablespace,
                                  .tableam = tableam,
+                                 .relkind = tbinfo->relkind,
                                  .owner = tbinfo->rolname,
                                  .description = reltypename,
                                  .section = tbinfo->postponed_def ?
index 0c057fef947bc0453163f719123850d0e3b79ea2..7085053a2d6f063b4d5a9ea34b36de99e7bf238d 100644 (file)
@@ -4587,19 +4587,19 @@ my %tests = (
            CREATE TABLE dump_test.regress_pg_dump_table_am_parent (id int) PARTITION BY LIST (id);
            ALTER TABLE dump_test.regress_pg_dump_table_am_parent SET ACCESS METHOD regress_table_am;
            CREATE TABLE dump_test.regress_pg_dump_table_am_child_1
-             PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (1) USING heap;
+             PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (1);
            CREATE TABLE dump_test.regress_pg_dump_table_am_child_2
-             PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (2);',
+             PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (2) USING heap;',
        regexp => qr/^
-           \QSET default_table_access_method = regress_table_am;\E
-           (\n(?!SET[^;]+;)[^\n]*)*
            \n\QCREATE TABLE dump_test.regress_pg_dump_table_am_parent (\E
+           (\n(?!SET[^;]+;)[^\n]*)*
+           \QALTER TABLE dump_test.regress_pg_dump_table_am_parent SET ACCESS METHOD regress_table_am;\E
            (.*\n)*
-           \QSET default_table_access_method = heap;\E
+           \QSET default_table_access_method = regress_table_am;\E
            (\n(?!SET[^;]+;)[^\n]*)*
            \n\QCREATE TABLE dump_test.regress_pg_dump_table_am_child_1 (\E
            (.*\n)*
-           \QSET default_table_access_method = regress_table_am;\E
+           \QSET default_table_access_method = heap;\E
            (\n(?!SET[^;]+;)[^\n]*)*
            \n\QCREATE TABLE dump_test.regress_pg_dump_table_am_child_2 (\E
            (.*\n)*/xm,