Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
pg_dump: Lock all relations, not just plain tables
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 27 Oct 2020 17:31:37 +0000 (14:31 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 27 Oct 2020 17:31:37 +0000 (14:31 -0300)
Now that LOCK TABLE can take any relation type, acquire lock on all
relations that are to be dumped.  This prevents schema changes or
deadlock errors that could cause a dump to fail after expending much
effort.  The server is tested to have the capability and the feature
disabled if it doesn't, so that a patched pg_dump doesn't fail when
connecting to an unpatched server.

Backpatch to 9.5.

Author: Álvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reported-by: Wells Oliver <wells.oliver@gmail.com>
Discussion: https://postgr.es/m/20201021200659.GA32358@alvherre.pgsql

src/bin/pg_dump/pg_backup.h
src/bin/pg_dump/pg_backup_db.c
src/bin/pg_dump/pg_backup_db.h
src/bin/pg_dump/pg_dump.c

index cbd7541577d215182412ccf5cee495bdd5fae792..4f0844255720d59819f4ea2f042b80438b21d9d8 100644 (file)
@@ -188,6 +188,8 @@ typedef struct Archive
    int         minRemoteVersion;       /* allowable range */
    int         maxRemoteVersion;
 
+   bool        hasGenericLockTable;    /* can LOCK TABLE do non-table rels */
+
    int         numWorkers;     /* number of parallel processes */
    char       *sync_snapshot_id;       /* sync snapshot id for parallel
                                         * operation */
index 944d68a53c8d8f581e0eddf4c1332005df564ebb..3680ffabed76166ea303918bf6f4f76d4ad632e6 100644 (file)
@@ -552,6 +552,71 @@ EndDBCopyMode(Archive *AHX, const char *tocEntryTag)
    }
 }
 
+/*
+ * Does LOCK TABLE work on non-table relations on this server?
+ *
+ * Note: assumes it is called out of any transaction
+ */
+bool
+IsLockTableGeneric(Archive *AHX)
+{
+   ArchiveHandle *AH = (ArchiveHandle *) AHX;
+   PGresult *res;
+   char     *sqlstate;
+   bool    retval;
+
+   if (AHX->remoteVersion >= 140000)
+       return true;
+   else if (AHX->remoteVersion < 90500)
+       return false;
+
+   StartTransaction(AHX);
+
+   /*
+    * Try a LOCK TABLE on a well-known non-table catalog; WRONG_OBJECT_TYPE
+    * tells us that this server doesn't support locking non-table rels, while
+    * LOCK_NOT_AVAILABLE and INSUFFICIENT_PRIVILEGE tell us that it does.
+    * Report anything else as a fatal problem.
+    */
+#define ERRCODE_INSUFFICIENT_PRIVILEGE "42501"
+#define ERRCODE_WRONG_OBJECT_TYPE  "42809"
+#define ERRCODE_LOCK_NOT_AVAILABLE "55P03"
+   res = PQexec(AH->connection,
+                "LOCK TABLE pg_catalog.pg_class_tblspc_relfilenode_index IN ACCESS SHARE MODE NOWAIT");
+   switch (PQresultStatus(res))
+   {
+       case PGRES_COMMAND_OK:
+           retval = true;
+           break;
+       case PGRES_FATAL_ERROR:
+           sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
+           if (strcmp(sqlstate, ERRCODE_WRONG_OBJECT_TYPE) == 0)
+           {
+               retval = false;
+               break;
+           }
+           else if (strcmp(sqlstate, ERRCODE_LOCK_NOT_AVAILABLE) == 0 ||
+                    strcmp(sqlstate, ERRCODE_INSUFFICIENT_PRIVILEGE) == 0)
+           {
+               retval = true;
+               break;
+           }
+           /* else, falls through */
+       default:
+           warn_or_exit_horribly(AH, modulename,
+                                 "LOCK TABLE failed for \"%s\": %s",
+                                 "pg_catalog.pg_class_tblspc_relfilenode_index",
+                                 PQerrorMessage(AH->connection));
+           retval = false;     /* not reached */
+           break;
+   }
+   PQclear(res);
+
+   CommitTransaction(AHX);
+
+   return retval;
+}
+
 void
 StartTransaction(Archive *AHX)
 {
index 527449e04401314006c00631d81b377663cf1e08..9e7a84fdee907eb91181d2260473037018825467 100644 (file)
@@ -20,6 +20,8 @@ extern PGresult *ExecuteSqlQueryForSingleRow(Archive *fout, char *query);
 
 extern void EndDBCopyMode(Archive *AHX, const char *tocEntryTag);
 
+extern bool IsLockTableGeneric(Archive *AHX);
+
 extern void StartTransaction(Archive *AHX);
 extern void CommitTransaction(Archive *AHX);
 
index cda9f490a288e8f0897df0e2a89ea778165928fd..b2b3a0fc817c1e344549600998ed71e9c7f8971c 100644 (file)
@@ -1081,6 +1081,9 @@ setup_connection(Archive *AH, const char *dumpencoding,
            ExecuteSqlStatement(AH, "SET row_security = off");
    }
 
+   /* Detect whether LOCK TABLE can handle non-table relations */
+   AH->hasGenericLockTable = IsLockTableGeneric(AH);
+
    /*
     * Start transaction-snapshot mode transaction to dump consistent data.
     */
@@ -6197,13 +6200,15 @@ getTables(Archive *fout, int *numTables)
         * assume our lock on the child is enough to prevent schema
         * alterations to parent tables.
         *
-        * NOTE: it'd be kinda nice to lock other relations too, not only
-        * plain tables, but the backend doesn't presently allow that.
-        *
-        * We only need to lock the table for certain components; see
+        * We only need to lock the relation for certain components; see
         * pg_dump.h
+        *
+        * On server versions that support it, we lock all relations not just
+        * plain tables.
         */
-       if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+       if (tblinfo[i].dobj.dump &&
+           (fout->hasGenericLockTable ||
+            tblinfo[i].relkind == RELKIND_RELATION) &&
            (tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
        {
            resetPQExpBuffer(query);