LCOV - code coverage report
Current view: top level - src/bin/pg_dump - pg_backup_db.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 177 254 69.7 %
Date: 2024-10-10 05:14:53 Functions: 17 19 89.5 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * pg_backup_db.c
       4             :  *
       5             :  *  Implements the basic DB functions used by the archiver.
       6             :  *
       7             :  * IDENTIFICATION
       8             :  *    src/bin/pg_dump/pg_backup_db.c
       9             :  *
      10             :  *-------------------------------------------------------------------------
      11             :  */
      12             : #include "postgres_fe.h"
      13             : 
      14             : #include <unistd.h>
      15             : #include <ctype.h>
      16             : #ifdef HAVE_TERMIOS_H
      17             : #include <termios.h>
      18             : #endif
      19             : 
      20             : #include "common/connect.h"
      21             : #include "common/string.h"
      22             : #include "dumputils.h"
      23             : #include "fe_utils/string_utils.h"
      24             : #include "parallel.h"
      25             : #include "pg_backup_archiver.h"
      26             : #include "pg_backup_db.h"
      27             : #include "pg_backup_utils.h"
      28             : 
      29             : static void _check_database_version(ArchiveHandle *AH);
      30             : static void notice_processor(void *arg, const char *message);
      31             : 
      32             : static void
      33         472 : _check_database_version(ArchiveHandle *AH)
      34             : {
      35             :     const char *remoteversion_str;
      36             :     int         remoteversion;
      37             :     PGresult   *res;
      38             : 
      39         472 :     remoteversion_str = PQparameterStatus(AH->connection, "server_version");
      40         472 :     remoteversion = PQserverVersion(AH->connection);
      41         472 :     if (remoteversion == 0 || !remoteversion_str)
      42           0 :         pg_fatal("could not get \"server_version\" from libpq");
      43             : 
      44         472 :     AH->public.remoteVersionStr = pg_strdup(remoteversion_str);
      45         472 :     AH->public.remoteVersion = remoteversion;
      46         472 :     if (!AH->archiveRemoteVersion)
      47         342 :         AH->archiveRemoteVersion = AH->public.remoteVersionStr;
      48             : 
      49         472 :     if (remoteversion != PG_VERSION_NUM
      50           0 :         && (remoteversion < AH->public.minRemoteVersion ||
      51           0 :             remoteversion > AH->public.maxRemoteVersion))
      52             :     {
      53           0 :         pg_log_error("aborting because of server version mismatch");
      54           0 :         pg_log_error_detail("server version: %s; %s version: %s",
      55             :                             remoteversion_str, progname, PG_VERSION);
      56           0 :         exit(1);
      57             :     }
      58             : 
      59             :     /*
      60             :      * Check if server is in recovery mode, which means we are on a hot
      61             :      * standby.
      62             :      */
      63         472 :     res = ExecuteSqlQueryForSingleRow((Archive *) AH,
      64             :                                       "SELECT pg_catalog.pg_is_in_recovery()");
      65         472 :     AH->public.isStandby = (strcmp(PQgetvalue(res, 0, 0), "t") == 0);
      66         472 :     PQclear(res);
      67         472 : }
      68             : 
      69             : /*
      70             :  * Reconnect to the server.  If dbname is not NULL, use that database,
      71             :  * else the one associated with the archive handle.
      72             :  */
      73             : void
      74          42 : ReconnectToServer(ArchiveHandle *AH, const char *dbname)
      75             : {
      76          42 :     PGconn     *oldConn = AH->connection;
      77          42 :     RestoreOptions *ropt = AH->public.ropt;
      78             : 
      79             :     /*
      80             :      * Save the dbname, if given, in override_dbname so that it will also
      81             :      * affect any later reconnection attempt.
      82             :      */
      83          42 :     if (dbname)
      84          42 :         ropt->cparams.override_dbname = pg_strdup(dbname);
      85             : 
      86             :     /*
      87             :      * Note: we want to establish the new connection, and in particular update
      88             :      * ArchiveHandle's connCancel, before closing old connection.  Otherwise
      89             :      * an ill-timed SIGINT could try to access a dead connection.
      90             :      */
      91          42 :     AH->connection = NULL;       /* dodge error check in ConnectDatabase */
      92             : 
      93          42 :     ConnectDatabase((Archive *) AH, &ropt->cparams, true);
      94             : 
      95          42 :     PQfinish(oldConn);
      96          42 : }
      97             : 
      98             : /*
      99             :  * Make, or remake, a database connection with the given parameters.
     100             :  *
     101             :  * The resulting connection handle is stored in AHX->connection.
     102             :  *
     103             :  * An interactive password prompt is automatically issued if required.
     104             :  * We store the results of that in AHX->savedPassword.
     105             :  * Note: it's not really all that sensible to use a single-entry password
     106             :  * cache if the username keeps changing.  In current usage, however, the
     107             :  * username never does change, so one savedPassword is sufficient.
     108             :  */
     109             : void
     110         476 : ConnectDatabase(Archive *AHX,
     111             :                 const ConnParams *cparams,
     112             :                 bool isReconnect)
     113             : {
     114         476 :     ArchiveHandle *AH = (ArchiveHandle *) AHX;
     115             :     trivalue    prompt_password;
     116             :     char       *password;
     117             :     bool        new_pass;
     118             : 
     119         476 :     if (AH->connection)
     120           0 :         pg_fatal("already connected to a database");
     121             : 
     122             :     /* Never prompt for a password during a reconnection */
     123         476 :     prompt_password = isReconnect ? TRI_NO : cparams->promptPassword;
     124             : 
     125         476 :     password = AH->savedPassword;
     126             : 
     127         476 :     if (prompt_password == TRI_YES && password == NULL)
     128           0 :         password = simple_prompt("Password: ", false);
     129             : 
     130             :     /*
     131             :      * Start the connection.  Loop until we have a password if requested by
     132             :      * backend.
     133             :      */
     134             :     do
     135             :     {
     136             :         const char *keywords[8];
     137             :         const char *values[8];
     138         476 :         int         i = 0;
     139             : 
     140             :         /*
     141             :          * If dbname is a connstring, its entries can override the other
     142             :          * values obtained from cparams; but in turn, override_dbname can
     143             :          * override the dbname component of it.
     144             :          */
     145         476 :         keywords[i] = "host";
     146         476 :         values[i++] = cparams->pghost;
     147         476 :         keywords[i] = "port";
     148         476 :         values[i++] = cparams->pgport;
     149         476 :         keywords[i] = "user";
     150         476 :         values[i++] = cparams->username;
     151         476 :         keywords[i] = "password";
     152         476 :         values[i++] = password;
     153         476 :         keywords[i] = "dbname";
     154         476 :         values[i++] = cparams->dbname;
     155         476 :         if (cparams->override_dbname)
     156             :         {
     157          48 :             keywords[i] = "dbname";
     158          48 :             values[i++] = cparams->override_dbname;
     159             :         }
     160         476 :         keywords[i] = "fallback_application_name";
     161         476 :         values[i++] = progname;
     162         476 :         keywords[i] = NULL;
     163         476 :         values[i++] = NULL;
     164             :         Assert(i <= lengthof(keywords));
     165             : 
     166         476 :         new_pass = false;
     167         476 :         AH->connection = PQconnectdbParams(keywords, values, true);
     168             : 
     169         476 :         if (!AH->connection)
     170           0 :             pg_fatal("could not connect to database");
     171             : 
     172         480 :         if (PQstatus(AH->connection) == CONNECTION_BAD &&
     173           4 :             PQconnectionNeedsPassword(AH->connection) &&
     174           0 :             password == NULL &&
     175             :             prompt_password != TRI_NO)
     176             :         {
     177           0 :             PQfinish(AH->connection);
     178           0 :             password = simple_prompt("Password: ", false);
     179           0 :             new_pass = true;
     180             :         }
     181         476 :     } while (new_pass);
     182             : 
     183             :     /* check to see that the backend connection was successfully made */
     184         476 :     if (PQstatus(AH->connection) == CONNECTION_BAD)
     185             :     {
     186           4 :         if (isReconnect)
     187           0 :             pg_fatal("reconnection failed: %s",
     188             :                      PQerrorMessage(AH->connection));
     189             :         else
     190           4 :             pg_fatal("%s",
     191             :                      PQerrorMessage(AH->connection));
     192             :     }
     193             : 
     194             :     /* Start strict; later phases may override this. */
     195         472 :     PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH,
     196             :                                         ALWAYS_SECURE_SEARCH_PATH_SQL));
     197             : 
     198         472 :     if (password && password != AH->savedPassword)
     199           0 :         free(password);
     200             : 
     201             :     /*
     202             :      * We want to remember connection's actual password, whether or not we got
     203             :      * it by prompting.  So we don't just store the password variable.
     204             :      */
     205         472 :     if (PQconnectionUsedPassword(AH->connection))
     206             :     {
     207           0 :         free(AH->savedPassword);
     208           0 :         AH->savedPassword = pg_strdup(PQpass(AH->connection));
     209             :     }
     210             : 
     211             :     /* check for version mismatch */
     212         472 :     _check_database_version(AH);
     213             : 
     214         472 :     PQsetNoticeProcessor(AH->connection, notice_processor, NULL);
     215             : 
     216             :     /* arrange for SIGINT to issue a query cancel on this connection */
     217         472 :     set_archive_cancel_info(AH, AH->connection);
     218         472 : }
     219             : 
     220             : /*
     221             :  * Close the connection to the database and also cancel off the query if we
     222             :  * have one running.
     223             :  */
     224             : void
     225         430 : DisconnectDatabase(Archive *AHX)
     226             : {
     227         430 :     ArchiveHandle *AH = (ArchiveHandle *) AHX;
     228             :     char        errbuf[1];
     229             : 
     230         430 :     if (!AH->connection)
     231           0 :         return;
     232             : 
     233         430 :     if (AH->connCancel)
     234             :     {
     235             :         /*
     236             :          * If we have an active query, send a cancel before closing, ignoring
     237             :          * any errors.  This is of no use for a normal exit, but might be
     238             :          * helpful during pg_fatal().
     239             :          */
     240         426 :         if (PQtransactionStatus(AH->connection) == PQTRANS_ACTIVE)
     241           0 :             (void) PQcancel(AH->connCancel, errbuf, sizeof(errbuf));
     242             : 
     243             :         /*
     244             :          * Prevent signal handler from sending a cancel after this.
     245             :          */
     246         426 :         set_archive_cancel_info(AH, NULL);
     247             :     }
     248             : 
     249         430 :     PQfinish(AH->connection);
     250         430 :     AH->connection = NULL;
     251             : }
     252             : 
     253             : PGconn *
     254        7782 : GetConnection(Archive *AHX)
     255             : {
     256        7782 :     ArchiveHandle *AH = (ArchiveHandle *) AHX;
     257             : 
     258        7782 :     return AH->connection;
     259             : }
     260             : 
     261             : static void
     262           4 : notice_processor(void *arg, const char *message)
     263             : {
     264           4 :     pg_log_info("%s", message);
     265           4 : }
     266             : 
     267             : /* Like pg_fatal(), but with a complaint about a particular query. */
     268             : static void
     269           4 : die_on_query_failure(ArchiveHandle *AH, const char *query)
     270             : {
     271           4 :     pg_log_error("query failed: %s",
     272             :                  PQerrorMessage(AH->connection));
     273           4 :     pg_log_error_detail("Query was: %s", query);
     274           4 :     exit(1);
     275             : }
     276             : 
     277             : void
     278        5908 : ExecuteSqlStatement(Archive *AHX, const char *query)
     279             : {
     280        5908 :     ArchiveHandle *AH = (ArchiveHandle *) AHX;
     281             :     PGresult   *res;
     282             : 
     283        5908 :     res = PQexec(AH->connection, query);
     284        5908 :     if (PQresultStatus(res) != PGRES_COMMAND_OK)
     285           2 :         die_on_query_failure(AH, query);
     286        5906 :     PQclear(res);
     287        5906 : }
     288             : 
     289             : PGresult *
     290       57102 : ExecuteSqlQuery(Archive *AHX, const char *query, ExecStatusType status)
     291             : {
     292       57102 :     ArchiveHandle *AH = (ArchiveHandle *) AHX;
     293             :     PGresult   *res;
     294             : 
     295       57102 :     res = PQexec(AH->connection, query);
     296       57102 :     if (PQresultStatus(res) != status)
     297           2 :         die_on_query_failure(AH, query);
     298       57100 :     return res;
     299             : }
     300             : 
     301             : /*
     302             :  * Execute an SQL query and verify that we got exactly one row back.
     303             :  */
     304             : PGresult *
     305       26028 : ExecuteSqlQueryForSingleRow(Archive *fout, const char *query)
     306             : {
     307             :     PGresult   *res;
     308             :     int         ntups;
     309             : 
     310       26028 :     res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK);
     311             : 
     312             :     /* Expecting a single result only */
     313       26028 :     ntups = PQntuples(res);
     314       26028 :     if (ntups != 1)
     315           0 :         pg_fatal(ngettext("query returned %d row instead of one: %s",
     316             :                           "query returned %d rows instead of one: %s",
     317             :                           ntups),
     318             :                  ntups, query);
     319             : 
     320       26028 :     return res;
     321             : }
     322             : 
     323             : /*
     324             :  * Convenience function to send a query.
     325             :  * Monitors result to detect COPY statements
     326             :  */
     327             : static void
     328       14094 : ExecuteSqlCommand(ArchiveHandle *AH, const char *qry, const char *desc)
     329             : {
     330       14094 :     PGconn     *conn = AH->connection;
     331             :     PGresult   *res;
     332             : 
     333             : #ifdef NOT_USED
     334             :     fprintf(stderr, "Executing: '%s'\n\n", qry);
     335             : #endif
     336       14094 :     res = PQexec(conn, qry);
     337             : 
     338       14094 :     switch (PQresultStatus(res))
     339             :     {
     340       14076 :         case PGRES_COMMAND_OK:
     341             :         case PGRES_TUPLES_OK:
     342             :         case PGRES_EMPTY_QUERY:
     343             :             /* A-OK */
     344       14076 :             break;
     345          18 :         case PGRES_COPY_IN:
     346             :             /* Assume this is an expected result */
     347          18 :             AH->pgCopyIn = true;
     348          18 :             break;
     349           0 :         default:
     350             :             /* trouble */
     351           0 :             warn_or_exit_horribly(AH, "%s: %sCommand was: %s",
     352             :                                   desc, PQerrorMessage(conn), qry);
     353           0 :             break;
     354             :     }
     355             : 
     356       14094 :     PQclear(res);
     357       14094 : }
     358             : 
     359             : 
     360             : /*
     361             :  * Process non-COPY table data (that is, INSERT commands).
     362             :  *
     363             :  * The commands have been run together as one long string for compressibility,
     364             :  * and we are receiving them in bufferloads with arbitrary boundaries, so we
     365             :  * have to locate command boundaries and save partial commands across calls.
     366             :  * All state must be kept in AH->sqlparse, not in local variables of this
     367             :  * routine.  We assume that AH->sqlparse was filled with zeroes when created.
     368             :  *
     369             :  * We have to lex the data to the extent of identifying literals and quoted
     370             :  * identifiers, so that we can recognize statement-terminating semicolons.
     371             :  * We assume that INSERT data will not contain SQL comments, E'' literals,
     372             :  * or dollar-quoted strings, so this is much simpler than a full SQL lexer.
     373             :  *
     374             :  * Note: when restoring from a pre-9.0 dump file, this code is also used to
     375             :  * process BLOB COMMENTS data, which has the same problem of containing
     376             :  * multiple SQL commands that might be split across bufferloads.  Fortunately,
     377             :  * that data won't contain anything complicated to lex either.
     378             :  */
     379             : static void
     380          74 : ExecuteSimpleCommands(ArchiveHandle *AH, const char *buf, size_t bufLen)
     381             : {
     382          74 :     const char *qry = buf;
     383          74 :     const char *eos = buf + bufLen;
     384             : 
     385             :     /* initialize command buffer if first time through */
     386          74 :     if (AH->sqlparse.curCmd == NULL)
     387           6 :         AH->sqlparse.curCmd = createPQExpBuffer();
     388             : 
     389      259460 :     for (; qry < eos; qry++)
     390             :     {
     391      259386 :         char        ch = *qry;
     392             : 
     393             :         /* For neatness, we skip any newlines between commands */
     394      259386 :         if (!(ch == '\n' && AH->sqlparse.curCmd->len == 0))
     395      253358 :             appendPQExpBufferChar(AH->sqlparse.curCmd, ch);
     396             : 
     397      259386 :         switch (AH->sqlparse.state)
     398             :         {
     399      251386 :             case SQL_SCAN:      /* Default state == 0, set in _allocAH */
     400      251386 :                 if (ch == ';')
     401             :                 {
     402             :                     /*
     403             :                      * We've found the end of a statement. Send it and reset
     404             :                      * the buffer.
     405             :                      */
     406        6000 :                     ExecuteSqlCommand(AH, AH->sqlparse.curCmd->data,
     407             :                                       "could not execute query");
     408        6000 :                     resetPQExpBuffer(AH->sqlparse.curCmd);
     409             :                 }
     410      245386 :                 else if (ch == '\'')
     411             :                 {
     412        4000 :                     AH->sqlparse.state = SQL_IN_SINGLE_QUOTE;
     413        4000 :                     AH->sqlparse.backSlash = false;
     414             :                 }
     415      241386 :                 else if (ch == '"')
     416             :                 {
     417           0 :                     AH->sqlparse.state = SQL_IN_DOUBLE_QUOTE;
     418             :                 }
     419      251386 :                 break;
     420             : 
     421        8000 :             case SQL_IN_SINGLE_QUOTE:
     422             :                 /* We needn't handle '' specially */
     423        8000 :                 if (ch == '\'' && !AH->sqlparse.backSlash)
     424        4000 :                     AH->sqlparse.state = SQL_SCAN;
     425        4000 :                 else if (ch == '\\' && !AH->public.std_strings)
     426           0 :                     AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
     427             :                 else
     428        4000 :                     AH->sqlparse.backSlash = false;
     429        8000 :                 break;
     430             : 
     431           0 :             case SQL_IN_DOUBLE_QUOTE:
     432             :                 /* We needn't handle "" specially */
     433           0 :                 if (ch == '"')
     434           0 :                     AH->sqlparse.state = SQL_SCAN;
     435           0 :                 break;
     436             :         }
     437      259386 :     }
     438          74 : }
     439             : 
     440             : 
     441             : /*
     442             :  * Implement ahwrite() for direct-to-DB restore
     443             :  */
     444             : int
     445        7952 : ExecuteSqlCommandBuf(Archive *AHX, const char *buf, size_t bufLen)
     446             : {
     447        7952 :     ArchiveHandle *AH = (ArchiveHandle *) AHX;
     448             : 
     449        7952 :     if (AH->outputKind == OUTPUT_COPYDATA)
     450             :     {
     451             :         /*
     452             :          * COPY data.
     453             :          *
     454             :          * We drop the data on the floor if libpq has failed to enter COPY
     455             :          * mode; this allows us to behave reasonably when trying to continue
     456             :          * after an error in a COPY command.
     457             :          */
     458          40 :         if (AH->pgCopyIn &&
     459          20 :             PQputCopyData(AH->connection, buf, bufLen) <= 0)
     460           0 :             pg_fatal("error returned by PQputCopyData: %s",
     461             :                      PQerrorMessage(AH->connection));
     462             :     }
     463        7932 :     else if (AH->outputKind == OUTPUT_OTHERDATA)
     464             :     {
     465             :         /*
     466             :          * Table data expressed as INSERT commands; or, in old dump files,
     467             :          * BLOB COMMENTS data (which is expressed as COMMENT ON commands).
     468             :          */
     469          74 :         ExecuteSimpleCommands(AH, buf, bufLen);
     470             :     }
     471             :     else
     472             :     {
     473             :         /*
     474             :          * General SQL commands; we assume that commands will not be split
     475             :          * across calls.
     476             :          *
     477             :          * In most cases the data passed to us will be a null-terminated
     478             :          * string, but if it's not, we have to add a trailing null.
     479             :          */
     480        7858 :         if (buf[bufLen] == '\0')
     481        7858 :             ExecuteSqlCommand(AH, buf, "could not execute query");
     482             :         else
     483             :         {
     484           0 :             char       *str = (char *) pg_malloc(bufLen + 1);
     485             : 
     486           0 :             memcpy(str, buf, bufLen);
     487           0 :             str[bufLen] = '\0';
     488           0 :             ExecuteSqlCommand(AH, str, "could not execute query");
     489           0 :             free(str);
     490             :         }
     491             :     }
     492             : 
     493        7952 :     return bufLen;
     494             : }
     495             : 
     496             : /*
     497             :  * Terminate a COPY operation during direct-to-DB restore
     498             :  */
     499             : void
     500          18 : EndDBCopyMode(Archive *AHX, const char *tocEntryTag)
     501             : {
     502          18 :     ArchiveHandle *AH = (ArchiveHandle *) AHX;
     503             : 
     504          18 :     if (AH->pgCopyIn)
     505             :     {
     506             :         PGresult   *res;
     507             : 
     508          18 :         if (PQputCopyEnd(AH->connection, NULL) <= 0)
     509           0 :             pg_fatal("error returned by PQputCopyEnd: %s",
     510             :                      PQerrorMessage(AH->connection));
     511             : 
     512             :         /* Check command status and return to normal libpq state */
     513          18 :         res = PQgetResult(AH->connection);
     514          18 :         if (PQresultStatus(res) != PGRES_COMMAND_OK)
     515           0 :             warn_or_exit_horribly(AH, "COPY failed for table \"%s\": %s",
     516           0 :                                   tocEntryTag, PQerrorMessage(AH->connection));
     517          18 :         PQclear(res);
     518             : 
     519             :         /* Do this to ensure we've pumped libpq back to idle state */
     520          18 :         if (PQgetResult(AH->connection) != NULL)
     521           0 :             pg_log_warning("unexpected extra results during COPY of table \"%s\"",
     522             :                            tocEntryTag);
     523             : 
     524          18 :         AH->pgCopyIn = false;
     525             :     }
     526          18 : }
     527             : 
     528             : void
     529         118 : StartTransaction(Archive *AHX)
     530             : {
     531         118 :     ArchiveHandle *AH = (ArchiveHandle *) AHX;
     532             : 
     533         118 :     ExecuteSqlCommand(AH, "BEGIN", "could not start database transaction");
     534         118 : }
     535             : 
     536             : void
     537         118 : CommitTransaction(Archive *AHX)
     538             : {
     539         118 :     ArchiveHandle *AH = (ArchiveHandle *) AHX;
     540             : 
     541         118 :     ExecuteSqlCommand(AH, "COMMIT", "could not commit database transaction");
     542         118 : }
     543             : 
     544             : /*
     545             :  * Issue per-blob commands for the large object(s) listed in the TocEntry
     546             :  *
     547             :  * The TocEntry's defn string is assumed to consist of large object OIDs,
     548             :  * one per line.  Wrap these in the given SQL command fragments and issue
     549             :  * the commands.  (cmdEnd need not include a semicolon.)
     550             :  */
     551             : void
     552         288 : IssueCommandPerBlob(ArchiveHandle *AH, TocEntry *te,
     553             :                     const char *cmdBegin, const char *cmdEnd)
     554             : {
     555             :     /* Make a writable copy of the command string */
     556         288 :     char       *buf = pg_strdup(te->defn);
     557         288 :     RestoreOptions *ropt = AH->public.ropt;
     558             :     char       *st;
     559             :     char       *en;
     560             : 
     561         288 :     st = buf;
     562         616 :     while ((en = strchr(st, '\n')) != NULL)
     563             :     {
     564         328 :         *en++ = '\0';
     565         328 :         ahprintf(AH, "%s%s%s;\n", cmdBegin, st, cmdEnd);
     566             : 
     567             :         /* In --transaction-size mode, count each command as an action */
     568         328 :         if (ropt && ropt->txn_size > 0)
     569             :         {
     570          12 :             if (++AH->txnCount >= ropt->txn_size)
     571             :             {
     572           0 :                 if (AH->connection)
     573             :                 {
     574           0 :                     CommitTransaction(&AH->public);
     575           0 :                     StartTransaction(&AH->public);
     576             :                 }
     577             :                 else
     578           0 :                     ahprintf(AH, "COMMIT;\nBEGIN;\n\n");
     579           0 :                 AH->txnCount = 0;
     580             :             }
     581             :         }
     582             : 
     583         328 :         st = en;
     584             :     }
     585         288 :     ahprintf(AH, "\n");
     586         288 :     pg_free(buf);
     587         288 : }
     588             : 
     589             : /*
     590             :  * Process a "LARGE OBJECTS" ACL TocEntry.
     591             :  *
     592             :  * To save space in the dump file, the TocEntry contains only one copy
     593             :  * of the required GRANT/REVOKE commands, written to apply to the first
     594             :  * blob in the group (although we do not depend on that detail here).
     595             :  * We must expand the text to generate commands for all the blobs listed
     596             :  * in the associated BLOB METADATA entry.
     597             :  */
     598             : void
     599           0 : IssueACLPerBlob(ArchiveHandle *AH, TocEntry *te)
     600             : {
     601           0 :     TocEntry   *blobte = getTocEntryByDumpId(AH, te->dependencies[0]);
     602             :     char       *buf;
     603             :     char       *st;
     604             :     char       *st2;
     605             :     char       *en;
     606             :     bool        inquotes;
     607             : 
     608           0 :     if (!blobte)
     609           0 :         pg_fatal("could not find entry for ID %d", te->dependencies[0]);
     610             :     Assert(strcmp(blobte->desc, "BLOB METADATA") == 0);
     611             : 
     612             :     /* Make a writable copy of the ACL commands string */
     613           0 :     buf = pg_strdup(te->defn);
     614             : 
     615             :     /*
     616             :      * We have to parse out the commands sufficiently to locate the blob OIDs
     617             :      * and find the command-ending semicolons.  The commands should not
     618             :      * contain anything hard to parse except for double-quoted role names,
     619             :      * which are easy to ignore.  Once we've split apart the first and second
     620             :      * halves of a command, apply IssueCommandPerBlob.  (This means the
     621             :      * updates on the blobs are interleaved if there's multiple commands, but
     622             :      * that should cause no trouble.)
     623             :      */
     624           0 :     inquotes = false;
     625           0 :     st = en = buf;
     626           0 :     st2 = NULL;
     627           0 :     while (*en)
     628             :     {
     629             :         /* Ignore double-quoted material */
     630           0 :         if (*en == '"')
     631           0 :             inquotes = !inquotes;
     632           0 :         if (inquotes)
     633             :         {
     634           0 :             en++;
     635           0 :             continue;
     636             :         }
     637             :         /* If we found "LARGE OBJECT", that's the end of the first half */
     638           0 :         if (strncmp(en, "LARGE OBJECT ", 13) == 0)
     639             :         {
     640             :             /* Terminate the first-half string */
     641           0 :             en += 13;
     642             :             Assert(isdigit((unsigned char) *en));
     643           0 :             *en++ = '\0';
     644             :             /* Skip the rest of the blob OID */
     645           0 :             while (isdigit((unsigned char) *en))
     646           0 :                 en++;
     647             :             /* Second half starts here */
     648             :             Assert(st2 == NULL);
     649           0 :             st2 = en;
     650             :         }
     651             :         /* If we found semicolon, that's the end of the second half */
     652           0 :         else if (*en == ';')
     653             :         {
     654             :             /* Terminate the second-half string */
     655           0 :             *en++ = '\0';
     656             :             Assert(st2 != NULL);
     657             :             /* Issue this command for each blob */
     658           0 :             IssueCommandPerBlob(AH, blobte, st, st2);
     659             :             /* For neatness, skip whitespace before the next command */
     660           0 :             while (isspace((unsigned char) *en))
     661           0 :                 en++;
     662             :             /* Reset for new command */
     663           0 :             st = en;
     664           0 :             st2 = NULL;
     665             :         }
     666             :         else
     667           0 :             en++;
     668             :     }
     669           0 :     pg_free(buf);
     670           0 : }
     671             : 
     672             : void
     673           0 : DropLOIfExists(ArchiveHandle *AH, Oid oid)
     674             : {
     675           0 :     ahprintf(AH,
     676             :              "SELECT pg_catalog.lo_unlink(oid) "
     677             :              "FROM pg_catalog.pg_largeobject_metadata "
     678             :              "WHERE oid = '%u';\n",
     679             :              oid);
     680           0 : }

Generated by: LCOV version 1.14