Age Owner Branch data TLA 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
5845 tgl@sss.pgh.pa.us 33 :CBC 239 : _check_database_version(ArchiveHandle *AH)
34 : : {
35 : : const char *remoteversion_str;
36 : : int remoteversion;
37 : : PGresult *res;
38 : :
7602 39 : 239 : remoteversion_str = PQparameterStatus(AH->connection, "server_version");
4037 heikki.linnakangas@i 40 : 239 : remoteversion = PQserverVersion(AH->connection);
41 [ + - - + ]: 239 : if (remoteversion == 0 || !remoteversion_str)
737 tgl@sss.pgh.pa.us 42 :UBC 0 : pg_fatal("could not get server_version from libpq");
43 : :
4524 bruce@momjian.us 44 :CBC 239 : AH->public.remoteVersionStr = pg_strdup(remoteversion_str);
8390 pjw@rhyme.com.au 45 : 239 : AH->public.remoteVersion = remoteversion;
5163 tgl@sss.pgh.pa.us 46 [ + + ]: 239 : if (!AH->archiveRemoteVersion)
47 : 172 : AH->archiveRemoteVersion = AH->public.remoteVersionStr;
48 : :
4037 heikki.linnakangas@i 49 [ - + ]: 239 : if (remoteversion != PG_VERSION_NUM
7602 tgl@sss.pgh.pa.us 50 [ # # ]:UBC 0 : && (remoteversion < AH->public.minRemoteVersion ||
51 [ # # ]: 0 : remoteversion > AH->public.maxRemoteVersion))
52 : : {
737 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 : : */
850 tgl@sss.pgh.pa.us 63 :CBC 239 : res = ExecuteSqlQueryForSingleRow((Archive *) AH,
64 : : "SELECT pg_catalog.pg_is_in_recovery()");
65 : 239 : AH->public.isStandby = (strcmp(PQgetvalue(res, 0, 0), "t") == 0);
66 : 239 : PQclear(res);
8668 pjw@rhyme.com.au 67 : 239 : }
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
1298 tgl@sss.pgh.pa.us 74 : 21 : ReconnectToServer(ArchiveHandle *AH, const char *dbname)
75 : : {
76 : 21 : PGconn *oldConn = AH->connection;
77 : 21 : 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 [ + - ]: 21 : if (dbname)
84 : 21 : 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 : 21 : AH->connection = NULL; /* dodge error check in ConnectDatabase */
92 : :
93 : 21 : ConnectDatabase((Archive *) AH, &ropt->cparams, true);
94 : :
95 : 21 : PQfinish(oldConn);
8665 pjw@rhyme.com.au 96 : 21 : }
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
8424 bruce@momjian.us 110 : 241 : ConnectDatabase(Archive *AHX,
111 : : const ConnParams *cparams,
112 : : bool isReconnect)
113 : : {
114 : 241 : ArchiveHandle *AH = (ArchiveHandle *) AHX;
115 : : trivalue prompt_password;
116 : : char *password;
117 : : bool new_pass;
118 : :
8668 pjw@rhyme.com.au 119 [ - + ]: 241 : if (AH->connection)
737 tgl@sss.pgh.pa.us 120 :UBC 0 : pg_fatal("already connected to a database");
121 : :
122 : : /* Never prompt for a password during a reconnection */
1298 tgl@sss.pgh.pa.us 123 [ + + ]:CBC 241 : prompt_password = isReconnect ? TRI_NO : cparams->promptPassword;
124 : :
2784 125 : 241 : password = AH->savedPassword;
126 : :
5526 peter_e@gmx.net 127 [ - + - - ]: 241 : if (prompt_password == TRI_YES && password == NULL)
1319 tgl@sss.pgh.pa.us 128 :UBC 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];
1298 tgl@sss.pgh.pa.us 138 :CBC 241 : 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 : 241 : keywords[i] = "host";
146 : 241 : values[i++] = cparams->pghost;
147 : 241 : keywords[i] = "port";
148 : 241 : values[i++] = cparams->pgport;
149 : 241 : keywords[i] = "user";
150 : 241 : values[i++] = cparams->username;
151 : 241 : keywords[i] = "password";
152 : 241 : values[i++] = password;
153 : 241 : keywords[i] = "dbname";
154 : 241 : values[i++] = cparams->dbname;
155 [ + + ]: 241 : if (cparams->override_dbname)
156 : : {
157 : 24 : keywords[i] = "dbname";
158 : 24 : values[i++] = cparams->override_dbname;
159 : : }
160 : 241 : keywords[i] = "fallback_application_name";
161 : 241 : values[i++] = progname;
162 : 241 : keywords[i] = NULL;
163 : 241 : values[i++] = NULL;
164 [ - + ]: 241 : Assert(i <= lengthof(keywords));
165 : :
6125 166 : 241 : new_pass = false;
5182 mail@joeconway.com 167 : 241 : AH->connection = PQconnectdbParams(keywords, values, true);
168 : :
8368 peter_e@gmx.net 169 [ - + ]: 241 : if (!AH->connection)
737 tgl@sss.pgh.pa.us 170 :UBC 0 : pg_fatal("could not connect to database");
171 : :
8368 peter_e@gmx.net 172 [ + + - + ]:CBC 243 : if (PQstatus(AH->connection) == CONNECTION_BAD &&
5971 tgl@sss.pgh.pa.us 173 [ - - ]: 2 : PQconnectionNeedsPassword(AH->connection) &&
5526 peter_e@gmx.net 174 [ # # ]:UBC 0 : password == NULL &&
175 : : prompt_password != TRI_NO)
176 : : {
8368 177 : 0 : PQfinish(AH->connection);
1319 tgl@sss.pgh.pa.us 178 : 0 : password = simple_prompt("Password: ", false);
6125 179 : 0 : new_pass = true;
180 : : }
6125 tgl@sss.pgh.pa.us 181 [ - + ]:CBC 241 : } while (new_pass);
182 : :
183 : : /* check to see that the backend connection was successfully made */
8668 pjw@rhyme.com.au 184 [ + + ]: 241 : if (PQstatus(AH->connection) == CONNECTION_BAD)
185 : : {
1298 tgl@sss.pgh.pa.us 186 [ - + ]: 2 : if (isReconnect)
737 tgl@sss.pgh.pa.us 187 :UBC 0 : pg_fatal("reconnection failed: %s",
188 : : PQerrorMessage(AH->connection));
189 : : else
737 tgl@sss.pgh.pa.us 190 :CBC 2 : pg_fatal("%s",
191 : : PQerrorMessage(AH->connection));
192 : : }
193 : :
194 : : /* Start strict; later phases may override this. */
2239 noah@leadboat.com 195 : 239 : PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH,
196 : : ALWAYS_SECURE_SEARCH_PATH_SQL));
197 : :
1319 tgl@sss.pgh.pa.us 198 [ - + - - ]: 239 : if (password && password != AH->savedPassword)
1319 tgl@sss.pgh.pa.us 199 :UBC 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 : : */
3035 tgl@sss.pgh.pa.us 205 [ - + ]:CBC 239 : if (PQconnectionUsedPassword(AH->connection))
206 : : {
668 peter@eisentraut.org 207 :UBC 0 : free(AH->savedPassword);
3035 tgl@sss.pgh.pa.us 208 : 0 : AH->savedPassword = pg_strdup(PQpass(AH->connection));
209 : : }
210 : :
211 : : /* check for version mismatch */
5845 tgl@sss.pgh.pa.us 212 :CBC 239 : _check_database_version(AH);
213 : :
8281 peter_e@gmx.net 214 : 239 : PQsetNoticeProcessor(AH->connection, notice_processor, NULL);
215 : :
216 : : /* arrange for SIGINT to issue a query cancel on this connection */
2873 tgl@sss.pgh.pa.us 217 : 239 : set_archive_cancel_info(AH, AH->connection);
8668 pjw@rhyme.com.au 218 : 239 : }
219 : :
220 : : /*
221 : : * Close the connection to the database and also cancel off the query if we
222 : : * have one running.
223 : : */
224 : : void
4441 rhaas@postgresql.org 225 : 218 : DisconnectDatabase(Archive *AHX)
226 : : {
227 : 218 : ArchiveHandle *AH = (ArchiveHandle *) AHX;
228 : : char errbuf[1];
229 : :
4039 andrew@dunslane.net 230 [ - + ]: 218 : if (!AH->connection)
4039 andrew@dunslane.net 231 :UBC 0 : return;
232 : :
2873 tgl@sss.pgh.pa.us 233 [ + + ]:CBC 218 : 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 [ - + ]: 216 : if (PQtransactionStatus(AH->connection) == PQTRANS_ACTIVE)
2532 alvherre@alvh.no-ip. 241 :UBC 0 : (void) PQcancel(AH->connCancel, errbuf, sizeof(errbuf));
242 : :
243 : : /*
244 : : * Prevent signal handler from sending a cancel after this.
245 : : */
2873 tgl@sss.pgh.pa.us 246 :CBC 216 : set_archive_cancel_info(AH, NULL);
247 : : }
248 : :
4039 andrew@dunslane.net 249 : 218 : PQfinish(AH->connection);
4441 rhaas@postgresql.org 250 : 218 : AH->connection = NULL;
251 : : }
252 : :
253 : : PGconn *
254 : 3936 : GetConnection(Archive *AHX)
255 : : {
256 : 3936 : ArchiveHandle *AH = (ArchiveHandle *) AHX;
257 : :
258 : 3936 : return AH->connection;
259 : : }
260 : :
261 : : static void
8207 bruce@momjian.us 262 :GBC 2 : notice_processor(void *arg, const char *message)
263 : : {
737 tgl@sss.pgh.pa.us 264 : 2 : pg_log_info("%s", message);
8281 peter_e@gmx.net 265 : 2 : }
266 : :
267 : : /* Like pg_fatal(), but with a complaint about a particular query. */
268 : : static void
1840 peter@eisentraut.org 269 :CBC 2 : die_on_query_failure(ArchiveHandle *AH, const char *query)
270 : : {
271 : 2 : pg_log_error("query failed: %s",
272 : : PQerrorMessage(AH->connection));
737 tgl@sss.pgh.pa.us 273 : 2 : pg_log_error_detail("Query was: %s", query);
274 : 2 : exit(1);
275 : : }
276 : :
277 : : void
4450 rhaas@postgresql.org 278 : 3013 : ExecuteSqlStatement(Archive *AHX, const char *query)
279 : : {
4326 bruce@momjian.us 280 : 3013 : ArchiveHandle *AH = (ArchiveHandle *) AHX;
281 : : PGresult *res;
282 : :
4450 rhaas@postgresql.org 283 : 3013 : res = PQexec(AH->connection, query);
284 [ + + ]: 3013 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
1840 peter@eisentraut.org 285 : 1 : die_on_query_failure(AH, query);
4450 rhaas@postgresql.org 286 : 3012 : PQclear(res);
287 : 3012 : }
288 : :
289 : : PGresult *
290 : 25958 : ExecuteSqlQuery(Archive *AHX, const char *query, ExecStatusType status)
291 : : {
4326 bruce@momjian.us 292 : 25958 : ArchiveHandle *AH = (ArchiveHandle *) AHX;
293 : : PGresult *res;
294 : :
4450 rhaas@postgresql.org 295 : 25958 : res = PQexec(AH->connection, query);
296 [ + + ]: 25958 : if (PQresultStatus(res) != status)
1840 peter@eisentraut.org 297 : 1 : die_on_query_failure(AH, query);
4450 rhaas@postgresql.org 298 : 25957 : return res;
299 : : }
300 : :
301 : : /*
302 : : * Execute an SQL query and verify that we got exactly one row back.
303 : : */
304 : : PGresult *
2357 peter_e@gmx.net 305 : 11372 : ExecuteSqlQueryForSingleRow(Archive *fout, const char *query)
306 : : {
307 : : PGresult *res;
308 : : int ntups;
309 : :
2880 magnus@hagander.net 310 : 11372 : res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK);
311 : :
312 : : /* Expecting a single result only */
313 : 11372 : ntups = PQntuples(res);
314 [ - + ]: 11372 : if (ntups != 1)
737 tgl@sss.pgh.pa.us 315 :UBC 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 : :
2880 magnus@hagander.net 320 :CBC 11372 : return res;
321 : : }
322 : :
323 : : /*
324 : : * Convenience function to send a query.
325 : : * Monitors result to detect COPY statements
326 : : */
327 : : static void
5720 tgl@sss.pgh.pa.us 328 : 7089 : ExecuteSqlCommand(ArchiveHandle *AH, const char *qry, const char *desc)
329 : : {
6872 330 : 7089 : PGconn *conn = AH->connection;
331 : : PGresult *res;
332 : :
333 : : #ifdef NOT_USED
334 : : fprintf(stderr, "Executing: '%s'\n\n", qry);
335 : : #endif
5720 336 : 7089 : res = PQexec(conn, qry);
337 : :
338 [ + + - ]: 7089 : switch (PQresultStatus(res))
339 : : {
340 : 7080 : case PGRES_COMMAND_OK:
341 : : case PGRES_TUPLES_OK:
342 : : case PGRES_EMPTY_QUERY:
343 : : /* A-OK */
344 : 7080 : break;
345 : 9 : case PGRES_COPY_IN:
346 : : /* Assume this is an expected result */
6643 347 : 9 : AH->pgCopyIn = true;
5720 348 : 9 : break;
5720 tgl@sss.pgh.pa.us 349 :UBC 0 : default:
350 : : /* trouble */
1840 peter@eisentraut.org 351 : 0 : warn_or_exit_horribly(AH, "%s: %sCommand was: %s",
352 : : desc, PQerrorMessage(conn), qry);
5720 tgl@sss.pgh.pa.us 353 : 0 : break;
354 : : }
355 : :
8668 pjw@rhyme.com.au 356 :CBC 7089 : PQclear(res);
357 : 7089 : }
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
3594 tgl@sss.pgh.pa.us 380 : 37 : ExecuteSimpleCommands(ArchiveHandle *AH, const char *buf, size_t bufLen)
381 : : {
4482 382 : 37 : const char *qry = buf;
383 : 37 : const char *eos = buf + bufLen;
384 : :
385 : : /* initialize command buffer if first time through */
386 [ + + ]: 37 : if (AH->sqlparse.curCmd == NULL)
387 : 3 : AH->sqlparse.curCmd = createPQExpBuffer();
388 : :
389 [ + + ]: 129730 : for (; qry < eos; qry++)
390 : : {
4326 bruce@momjian.us 391 : 129693 : char ch = *qry;
392 : :
393 : : /* For neatness, we skip any newlines between commands */
4482 tgl@sss.pgh.pa.us 394 [ + + - + ]: 129693 : if (!(ch == '\n' && AH->sqlparse.curCmd->len == 0))
395 : 126679 : appendPQExpBufferChar(AH->sqlparse.curCmd, ch);
396 : :
397 [ + + - - ]: 129693 : switch (AH->sqlparse.state)
398 : : {
399 : 125693 : case SQL_SCAN: /* Default state == 0, set in _allocAH */
400 [ + + ]: 125693 : if (ch == ';')
401 : : {
402 : : /*
403 : : * We've found the end of a statement. Send it and reset
404 : : * the buffer.
405 : : */
406 : 3000 : ExecuteSqlCommand(AH, AH->sqlparse.curCmd->data,
407 : : "could not execute query");
408 : 3000 : resetPQExpBuffer(AH->sqlparse.curCmd);
409 : : }
410 [ + + ]: 122693 : else if (ch == '\'')
411 : : {
412 : 2000 : AH->sqlparse.state = SQL_IN_SINGLE_QUOTE;
413 : 2000 : AH->sqlparse.backSlash = false;
414 : : }
415 [ - + ]: 120693 : else if (ch == '"')
416 : : {
4482 tgl@sss.pgh.pa.us 417 :UBC 0 : AH->sqlparse.state = SQL_IN_DOUBLE_QUOTE;
418 : : }
4482 tgl@sss.pgh.pa.us 419 :CBC 125693 : break;
420 : :
421 : 4000 : case SQL_IN_SINGLE_QUOTE:
422 : : /* We needn't handle '' specially */
423 [ + + + - ]: 4000 : if (ch == '\'' && !AH->sqlparse.backSlash)
424 : 2000 : AH->sqlparse.state = SQL_SCAN;
425 [ - + - - ]: 2000 : else if (ch == '\\' && !AH->public.std_strings)
4482 tgl@sss.pgh.pa.us 426 :UBC 0 : AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
427 : : else
4482 tgl@sss.pgh.pa.us 428 :CBC 2000 : AH->sqlparse.backSlash = false;
429 : 4000 : break;
430 : :
4482 tgl@sss.pgh.pa.us 431 :UBC 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 : : }
4482 tgl@sss.pgh.pa.us 438 :CBC 37 : }
439 : :
440 : :
441 : : /*
442 : : * Implement ahwrite() for direct-to-DB restore
443 : : */
444 : : int
3470 alvherre@alvh.no-ip. 445 : 4030 : ExecuteSqlCommandBuf(Archive *AHX, const char *buf, size_t bufLen)
446 : : {
447 : 4030 : ArchiveHandle *AH = (ArchiveHandle *) AHX;
448 : :
4482 tgl@sss.pgh.pa.us 449 [ + + ]: 4030 : 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 : : */
4644 458 [ + - - + ]: 20 : if (AH->pgCopyIn &&
459 : 10 : PQputCopyData(AH->connection, buf, bufLen) <= 0)
737 tgl@sss.pgh.pa.us 460 :UBC 0 : pg_fatal("error returned by PQputCopyData: %s",
461 : : PQerrorMessage(AH->connection));
462 : : }
4482 tgl@sss.pgh.pa.us 463 [ + + ]:CBC 4020 : 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 : : */
3594 469 : 37 : 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 : : */
4644 480 [ + - ]: 3983 : if (buf[bufLen] == '\0')
481 : 3983 : ExecuteSqlCommand(AH, buf, "could not execute query");
482 : : else
483 : : {
4326 bruce@momjian.us 484 :UBC 0 : char *str = (char *) pg_malloc(bufLen + 1);
485 : :
4644 tgl@sss.pgh.pa.us 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 : :
3632 bruce@momjian.us 493 :CBC 4030 : return bufLen;
494 : : }
495 : :
496 : : /*
497 : : * Terminate a COPY operation during direct-to-DB restore
498 : : */
499 : : void
3470 alvherre@alvh.no-ip. 500 : 9 : EndDBCopyMode(Archive *AHX, const char *tocEntryTag)
501 : : {
502 : 9 : ArchiveHandle *AH = (ArchiveHandle *) AHX;
503 : :
4644 tgl@sss.pgh.pa.us 504 [ + - ]: 9 : if (AH->pgCopyIn)
505 : : {
506 : : PGresult *res;
507 : :
6617 508 [ - + ]: 9 : if (PQputCopyEnd(AH->connection, NULL) <= 0)
737 tgl@sss.pgh.pa.us 509 :UBC 0 : pg_fatal("error returned by PQputCopyEnd: %s",
510 : : PQerrorMessage(AH->connection));
511 : :
512 : : /* Check command status and return to normal libpq state */
6617 tgl@sss.pgh.pa.us 513 :CBC 9 : res = PQgetResult(AH->connection);
514 [ - + ]: 9 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
1840 peter@eisentraut.org 515 :UBC 0 : warn_or_exit_horribly(AH, "COPY failed for table \"%s\": %s",
2489 tgl@sss.pgh.pa.us 516 : 0 : tocEntryTag, PQerrorMessage(AH->connection));
6617 tgl@sss.pgh.pa.us 517 :CBC 9 : PQclear(res);
518 : :
519 : : /* Do this to ensure we've pumped libpq back to idle state */
2873 520 [ - + ]: 9 : if (PQgetResult(AH->connection) != NULL)
1840 peter@eisentraut.org 521 :UBC 0 : pg_log_warning("unexpected extra results during COPY of table \"%s\"",
522 : : tocEntryTag);
523 : :
6643 tgl@sss.pgh.pa.us 524 :CBC 9 : AH->pgCopyIn = false;
525 : : }
8668 pjw@rhyme.com.au 526 : 9 : }
527 : :
528 : : void
3470 alvherre@alvh.no-ip. 529 : 53 : StartTransaction(Archive *AHX)
530 : : {
531 : 53 : ArchiveHandle *AH = (ArchiveHandle *) AHX;
532 : :
5720 tgl@sss.pgh.pa.us 533 : 53 : ExecuteSqlCommand(AH, "BEGIN", "could not start database transaction");
8668 pjw@rhyme.com.au 534 : 53 : }
535 : :
536 : : void
3470 alvherre@alvh.no-ip. 537 : 53 : CommitTransaction(Archive *AHX)
538 : : {
539 : 53 : ArchiveHandle *AH = (ArchiveHandle *) AHX;
540 : :
5720 tgl@sss.pgh.pa.us 541 : 53 : ExecuteSqlCommand(AH, "COMMIT", "could not commit database transaction");
8566 pjw@rhyme.com.au 542 : 53 : }
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
13 tgl@sss.pgh.pa.us 552 :GNC 156 : IssueCommandPerBlob(ArchiveHandle *AH, TocEntry *te,
553 : : const char *cmdBegin, const char *cmdEnd)
554 : : {
555 : : /* Make a writable copy of the command string */
556 : 156 : char *buf = pg_strdup(te->defn);
557 : 156 : RestoreOptions *ropt = AH->public.ropt;
558 : : char *st;
559 : : char *en;
560 : :
561 : 156 : st = buf;
562 [ + + ]: 332 : while ((en = strchr(st, '\n')) != NULL)
563 : : {
564 : 176 : *en++ = '\0';
565 : 176 : ahprintf(AH, "%s%s%s;\n", cmdBegin, st, cmdEnd);
566 : :
567 : : /* In --transaction-size mode, count each command as an action */
568 [ + - + + ]: 176 : if (ropt && ropt->txn_size > 0)
569 : : {
570 [ - + ]: 6 : if (++AH->txnCount >= ropt->txn_size)
571 : : {
13 tgl@sss.pgh.pa.us 572 [ # # ]:UNC 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 : :
13 tgl@sss.pgh.pa.us 583 :GNC 176 : st = en;
584 : : }
585 : 156 : ahprintf(AH, "\n");
586 : 156 : pg_free(buf);
587 : 156 : }
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
13 tgl@sss.pgh.pa.us 599 :UNC 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 [ # # ]: 0 : 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 [ # # ]: 0 : 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 [ # # ]: 0 : 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 [ # # ]: 0 : 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
13 tgl@sss.pgh.pa.us 673 :LBC (2) : DropLOIfExists(ArchiveHandle *AH, Oid oid)
674 : : {
13 tgl@sss.pgh.pa.us 675 :UNC 0 : ahprintf(AH,
676 : : "SELECT pg_catalog.lo_unlink(oid) "
677 : : "FROM pg_catalog.pg_largeobject_metadata "
678 : : "WHERE oid = '%u';\n",
679 : : oid);
5235 itagaki.takahiro@gma 680 :LBC (2) : }
|