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