1 /*-------------------------------------------------------------------------
5 * Implements the basic DB functions used by the archiver.
8 * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.64 2005/07/27 05:14:12 neilc Exp $
10 *-------------------------------------------------------------------------
13 #include "pg_backup.h"
14 #include "pg_backup_archiver.h"
15 #include "pg_backup_db.h"
16 #include "dumputils.h"
26 #include "libpq/libpq-fs.h"
31 static const char *modulename = gettext_noop("archiver (db)");
33 static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion);
34 static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, const char *newUser);
35 static void notice_processor(void *arg, const char *message);
36 static char *_sendSQLLine(ArchiveHandle *AH, char *qry, char *eos);
37 static char *_sendCopyLine(ArchiveHandle *AH, char *qry, char *eos);
39 static int _isIdentChar(unsigned char c);
40 static int _isDQChar(unsigned char c, int atStart);
42 #define DB_MAX_ERR_STMT 128
45 _parse_version(ArchiveHandle *AH, const char *versionString)
49 v = parse_version(versionString);
51 die_horribly(AH, modulename, "could not parse version string \"%s\"\n", versionString);
57 _check_database_version(ArchiveHandle *AH, bool ignoreVersion)
60 const char *remoteversion_str;
63 myversion = _parse_version(AH, PG_VERSION);
65 remoteversion_str = PQparameterStatus(AH->connection, "server_version");
66 if (!remoteversion_str)
67 die_horribly(AH, modulename, "could not get server_version from libpq\n");
69 remoteversion = _parse_version(AH, remoteversion_str);
71 AH->public.remoteVersionStr = strdup(remoteversion_str);
72 AH->public.remoteVersion = remoteversion;
74 if (myversion != remoteversion
75 && (remoteversion < AH->public.minRemoteVersion ||
76 remoteversion > AH->public.maxRemoteVersion))
78 write_msg(NULL, "server version: %s; %s version: %s\n",
79 remoteversion_str, progname, PG_VERSION);
81 write_msg(NULL, "proceeding despite version mismatch\n");
83 die_horribly(AH, NULL, "aborting because of version mismatch (Use the -i option to proceed anyway.)\n");
88 * Reconnect to the server. If dbname is not NULL, use that database,
89 * else the one associated with the archive handle. If username is
90 * not NULL, use that user name, else the one from the handle. If
91 * both the database and the user match the existing connection already,
92 * nothing will be done.
94 * Returns 1 in any case.
97 ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username)
100 const char *newdbname;
101 const char *newusername;
104 newdbname = PQdb(AH->connection);
109 newusername = PQuser(AH->connection);
111 newusername = username;
113 /* Let's see if the request is already satisfied */
114 if (strcmp(newdbname, PQdb(AH->connection)) == 0 &&
115 strcmp(newusername, PQuser(AH->connection)) == 0)
118 newConn = _connectDB(AH, newdbname, newusername);
120 PQfinish(AH->connection);
121 AH->connection = newConn;
127 * Connect to the db again.
130 _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
134 char *password = NULL;
141 newdb = PQdb(AH->connection);
143 newdb = (char *) reqdb;
145 if (!requser || (strlen(requser) == 0))
146 newuser = PQuser(AH->connection);
148 newuser = (char *) requser;
150 ahlog(AH, 1, "connecting to database \"%s\" as user \"%s\"\n", newdb, newuser);
152 if (AH->requirePassword)
154 password = simple_prompt("Password: ", 100, false);
155 if (password == NULL)
156 die_horribly(AH, modulename, "out of memory\n");
162 newConn = PQsetdbLogin(PQhost(AH->connection), PQport(AH->connection),
166 die_horribly(AH, modulename, "failed to reconnect to database\n");
168 if (PQstatus(newConn) == CONNECTION_BAD)
170 noPwd = (strcmp(PQerrorMessage(newConn),
171 PQnoPasswordSupplied) == 0);
172 badPwd = (strncmp(PQerrorMessage(newConn),
173 "Password authentication failed for user", 39) == 0);
178 fprintf(stderr, "Password incorrect\n");
180 fprintf(stderr, "Connecting to %s as %s\n",
186 password = simple_prompt("Password: ", 100, false);
189 die_horribly(AH, modulename, "could not reconnect to database: %s",
190 PQerrorMessage(newConn));
198 /* check for version mismatch */
199 _check_database_version(AH, true);
201 PQsetNoticeProcessor(newConn, notice_processor, NULL);
208 * Make a database connection with the given parameters. The
209 * connection handle is returned, the parameters are stored in AHX.
210 * An interactive password prompt is automatically issued if required.
213 ConnectDatabase(Archive *AHX,
217 const char *username,
219 const int ignoreVersion)
221 ArchiveHandle *AH = (ArchiveHandle *) AHX;
222 char *password = NULL;
223 bool need_pass = false;
226 die_horribly(AH, modulename, "already connected to a database\n");
230 password = simple_prompt("Password: ", 100, false);
231 if (password == NULL)
232 die_horribly(AH, modulename, "out of memory\n");
233 AH->requirePassword = true;
236 AH->requirePassword = false;
239 * Start the connection. Loop until we have a password if requested
245 AH->connection = PQsetdbLogin(pghost, pgport, NULL, NULL,
246 dbname, username, password);
249 die_horribly(AH, modulename, "failed to connect to database\n");
251 if (PQstatus(AH->connection) == CONNECTION_BAD &&
252 strcmp(PQerrorMessage(AH->connection), PQnoPasswordSupplied) == 0 &&
255 PQfinish(AH->connection);
259 password = simple_prompt("Password: ", 100, false);
266 /* check to see that the backend connection was successfully made */
267 if (PQstatus(AH->connection) == CONNECTION_BAD)
268 die_horribly(AH, modulename, "connection to database \"%s\" failed: %s",
269 PQdb(AH->connection), PQerrorMessage(AH->connection));
271 /* check for version mismatch */
272 _check_database_version(AH, ignoreVersion);
274 PQsetNoticeProcessor(AH->connection, notice_processor, NULL);
276 return AH->connection;
281 notice_processor(void *arg, const char *message)
283 write_msg(NULL, "%s", message);
287 /* Public interface */
288 /* Convenience function to send a query. Monitors result to handle COPY statements */
290 ExecuteSqlCommand(ArchiveHandle *AH, PQExpBuffer qry, char *desc)
292 PGconn *conn = AH->connection;
294 char errStmt[DB_MAX_ERR_STMT];
296 /* fprintf(stderr, "Executing: '%s'\n\n", qry->data); */
297 res = PQexec(conn, qry->data);
299 die_horribly(AH, modulename, "%s: no result from server\n", desc);
301 if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
303 if (PQresultStatus(res) == PGRES_COPY_IN)
309 strncpy(errStmt, qry->data, DB_MAX_ERR_STMT);
310 if (errStmt[DB_MAX_ERR_STMT - 1] != '\0')
312 errStmt[DB_MAX_ERR_STMT - 4] = '.';
313 errStmt[DB_MAX_ERR_STMT - 3] = '.';
314 errStmt[DB_MAX_ERR_STMT - 2] = '.';
315 errStmt[DB_MAX_ERR_STMT - 1] = '\0';
317 warn_or_die_horribly(AH, modulename, "%s: %s Command was: %s\n",
318 desc, PQerrorMessage(AH->connection),
325 return strlen(qry->data);
329 * Used by ExecuteSqlCommandBuf to send one buffered line when running a COPY command.
332 _sendCopyLine(ArchiveHandle *AH, char *qry, char *eos)
334 size_t loc; /* Location of next newline */
335 int pos = 0; /* Current position */
336 int sPos = 0; /* Last pos of a slash char */
339 /* loop to find unquoted newline ending the line of COPY data */
342 loc = strcspn(&qry[pos], "\n") + pos;
344 /* If no match, then wait */
345 if (loc >= (eos - qry)) /* None found */
347 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
352 * fprintf(stderr, "Found cr at %d, prev char was %c, next was
353 * %c\n", loc, qry[loc-1], qry[loc+1]);
356 /* Count the number of preceding slashes */
358 while (sPos > 0 && qry[sPos - 1] == '\\')
364 * If an odd number of preceding slashes, then \n was escaped so
365 * set the next search pos, and loop (if any left).
369 /* fprintf(stderr, "cr was escaped\n"); */
371 if (pos >= (eos - qry))
373 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
381 /* We found an unquoted newline */
383 appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
384 isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
387 * fprintf(stderr, "Sending '%s' via
388 * COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd);
392 if (PQputline(AH->connection, AH->pgCopyBuf->data) != 0)
393 die_horribly(AH, modulename, "error returned by PQputline\n");
395 resetPQExpBuffer(AH->pgCopyBuf);
398 * fprintf(stderr, "Buffer is '%s'\n", AH->pgCopyBuf->data);
403 if (PQendcopy(AH->connection) != 0)
404 die_horribly(AH, modulename, "error returned by PQendcopy\n");
409 return qry + loc + 1;
413 * Used by ExecuteSqlCommandBuf to send one buffered line of SQL (not data for the copy command).
416 _sendSQLLine(ArchiveHandle *AH, char *qry, char *eos)
418 int pos = 0; /* Current position */
424 * The following is a mini state machine to assess the end of an SQL
425 * statement. It really only needs to parse good SQL, or at least
426 * that's the theory... End-of-statement is assumed to be an unquoted,
427 * un commented semi-colon.
431 * fprintf(stderr, "Buffer at start is: '%s'\n\n", AH->sqlBuf->data);
434 for (pos = 0; pos < (eos - qry); pos++)
436 appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
437 /* fprintf(stderr, " %c",qry[pos]); */
439 /* Loop until character consumed */
443 * If a character needs to be scanned in a different state,
444 * consumed can be set to 0 to avoid advancing. Care must be
445 * taken to ensure internal state is not damaged.
449 switch (AH->sqlparse.state)
452 case SQL_SCAN: /* Default state == 0, set in _allocAH */
453 if (qry[pos] == ';' && AH->sqlparse.braceDepth == 0)
456 * We've got the end of a statement. Send It &
461 * fprintf(stderr, " sending: '%s'\n\n",
464 ExecuteSqlCommand(AH, AH->sqlBuf, "could not execute query");
465 resetPQExpBuffer(AH->sqlBuf);
466 AH->sqlparse.lastChar = '\0';
469 * Remove any following newlines - so that
470 * embedded COPY commands don't get a starting
474 for (; pos < (eos - qry) && qry[pos] == '\n'; pos++);
476 /* We've got our line, so exit */
482 * Look for normal boring quote chars, or
483 * dollar-quotes. We make the assumption that
484 * $-quotes will not have an ident character
485 * before them in all pg_dump output.
489 || (qry[pos] == '$' && _isIdentChar(AH->sqlparse.lastChar) == 0)
492 /* fprintf(stderr,"[startquote]\n"); */
493 AH->sqlparse.state = SQL_IN_QUOTE;
494 AH->sqlparse.quoteChar = qry[pos];
495 AH->sqlparse.backSlash = 0;
498 /* override the state */
499 AH->sqlparse.state = SQL_IN_DOLLARTAG;
500 /* Used for checking first char of tag */
502 /* We store the tag for later comparison. */
503 AH->sqlparse.tagBuf = createPQExpBuffer();
505 appendPQExpBufferChar(AH->sqlparse.tagBuf, qry[pos]);
508 else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
509 AH->sqlparse.state = SQL_IN_SQL_COMMENT;
510 else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
511 AH->sqlparse.state = SQL_IN_EXT_COMMENT;
512 else if (qry[pos] == '(')
513 AH->sqlparse.braceDepth++;
514 else if (qry[pos] == ')')
515 AH->sqlparse.braceDepth--;
517 AH->sqlparse.lastChar = qry[pos];
521 case SQL_IN_DOLLARTAG:
524 * Like a quote, we look for a closing char *but* we
525 * only allow a very limited set of contained chars,
526 * and no escape chars. If invalid chars are found, we
527 * abort tag processing.
532 /* fprintf(stderr,"[endquote]\n"); */
534 appendPQExpBufferChar(AH->sqlparse.tagBuf, qry[pos]);
535 AH->sqlparse.state = SQL_IN_DOLLARQUOTE;
539 if (_isDQChar(qry[pos], startDT))
542 appendPQExpBufferChar(AH->sqlparse.tagBuf, qry[pos]);
547 * Jump back to 'scan' state, we're not really
548 * in a tag, and valid tag chars do not
549 * include the various chars we look for in
550 * this state machine, so it's safe to just
551 * jump from this state back to SCAN. We set
552 * consumed = 0 so that this char gets
553 * rescanned in new state.
555 destroyPQExpBuffer(AH->sqlparse.tagBuf);
556 AH->sqlparse.state = SQL_SCAN;
564 case SQL_IN_DOLLARQUOTE:
567 * Comparing the entire string backwards each time is
568 * NOT efficient, but dollar quotes in pg_dump are
569 * small and the code is a lot simpler.
571 sqlPtr = AH->sqlBuf->data + AH->sqlBuf->len - AH->sqlparse.tagBuf->len;
573 if (strncmp(AH->sqlparse.tagBuf->data, sqlPtr, AH->sqlparse.tagBuf->len) == 0)
576 AH->sqlparse.state = SQL_SCAN;
577 destroyPQExpBuffer(AH->sqlparse.tagBuf);
581 case SQL_IN_SQL_COMMENT:
582 if (qry[pos] == '\n')
583 AH->sqlparse.state = SQL_SCAN;
586 case SQL_IN_EXT_COMMENT:
587 if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
588 AH->sqlparse.state = SQL_SCAN;
593 if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
595 /* fprintf(stderr,"[endquote]\n"); */
596 AH->sqlparse.state = SQL_SCAN;
600 if (qry[pos] == '\\')
602 if (AH->sqlparse.lastChar == '\\')
603 AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
605 AH->sqlparse.backSlash = 1;
608 AH->sqlparse.backSlash = 0;
614 } while (consumed == 0);
616 AH->sqlparse.lastChar = qry[pos];
617 /* fprintf(stderr, "\n"); */
621 * If we get here, we've processed entire string with no complete SQL
628 /* Convenience function to send one or more queries. Monitors result to handle COPY statements */
630 ExecuteSqlCommandBuf(ArchiveHandle *AH, void *qryv, size_t bufLen)
632 char *qry = (char *) qryv;
633 char *eos = qry + bufLen;
636 * fprintf(stderr, "\n\n*****\n
637 * Buffer:\n\n%s\n*******************\n\n", qry);
640 /* Could switch between command and COPY IN mode at each line */
644 qry = _sendCopyLine(AH, qry, eos);
646 qry = _sendSQLLine(AH, qry, eos);
653 StartTransaction(ArchiveHandle *AH)
655 PQExpBuffer qry = createPQExpBuffer();
657 appendPQExpBuffer(qry, "BEGIN");
659 ExecuteSqlCommand(AH, qry, "could not start database transaction");
661 destroyPQExpBuffer(qry);
665 CommitTransaction(ArchiveHandle *AH)
667 PQExpBuffer qry = createPQExpBuffer();
669 appendPQExpBuffer(qry, "COMMIT");
671 ExecuteSqlCommand(AH, qry, "could not commit database transaction");
673 destroyPQExpBuffer(qry);
677 _isIdentChar(unsigned char c)
679 if ((c >= 'a' && c <= 'z')
680 || (c >= 'A' && c <= 'Z')
681 || (c >= '0' && c <= '9')
684 || (c >= (unsigned char) '\200') /* no need to check <=
693 _isDQChar(unsigned char c, int atStart)
695 if ((c >= 'a' && c <= 'z')
696 || (c >= 'A' && c <= 'Z')
698 || (atStart == 0 && c >= '0' && c <= '9')
699 || (c >= (unsigned char) '\200') /* no need to check <=