1 /*-------------------------------------------------------------------------
5 * Implements the basic DB functions used by the archiver.
8 * $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.22 2001/08/03 19:43:05 tgl Exp $
12 * Modifications - 04-Jan-2001 - pjw@rhyme.com.au
14 * - Check results of PQ routines more carefully.
16 * Modifications - 19-Mar-2001 - pjw@rhyme.com.au
18 * - Avoid forcing table name to lower case in FixupBlobXrefs!
20 *-------------------------------------------------------------------------
23 #include "pg_backup.h"
24 #include "pg_backup_archiver.h"
25 #include "pg_backup_db.h"
27 #include <unistd.h> /* for getopt() */
35 #include "libpq/libpq-fs.h"
40 static const char *modulename = gettext_noop("archiver (db)");
42 static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion);
43 static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, char *newUser);
44 static int _executeSqlCommand(ArchiveHandle *AH, PGconn *conn, PQExpBuffer qry, char *desc);
50 * Generalized function especially intended for reading in usernames and
51 * password interactively. Reads from stdin.
53 * prompt: The prompt to print
54 * maxlen: How many characters to accept
55 * echo: Set to false if you want to hide what is entered (for passwords)
57 * Returns a malloc()'ed string with the input (w/o trailing newline).
60 simple_prompt(const char *prompt, int maxlen, bool echo)
66 struct termios t_orig,
71 destination = (char *) malloc(maxlen + 2);
75 fputs(gettext(prompt), stderr);
83 tcsetattr(0, TCSADRAIN, &t);
87 if (fgets(destination, maxlen, stdin) == NULL)
88 destination[0] = '\0';
93 tcsetattr(0, TCSADRAIN, &t_orig);
98 length = strlen(destination);
99 if (length > 0 && destination[length - 1] != '\n')
101 /* eat rest of the line */
107 if (fgets(buf, sizeof(buf), stdin) == NULL)
109 buflen = strlen(buf);
110 } while (buflen > 0 && buf[buflen - 1] != '\n');
112 if (length > 0 && destination[length - 1] == '\n')
113 /* remove trailing newline */
114 destination[length - 1] = '\0';
121 _parse_version(ArchiveHandle *AH, const char* versionString)
124 int vmaj, vmin, vrev;
126 cnt = sscanf(versionString, "%d.%d.%d", &vmaj, &vmin, &vrev);
130 die_horribly(AH, modulename, "unable to parse version string \"%s\"\n", versionString);
136 return (100 * vmaj + vmin) * 100 + vrev;
140 _check_database_version(ArchiveHandle *AH, bool ignoreVersion)
144 const char *remoteversion_str;
146 PGconn *conn = AH->connection;
148 myversion = _parse_version(AH, PG_VERSION);
150 res = PQexec(conn, "SELECT version();");
152 PQresultStatus(res) != PGRES_TUPLES_OK ||
155 die_horribly(AH, modulename, "could not get version from server: %s", PQerrorMessage(conn));
157 remoteversion_str = PQgetvalue(res, 0, 0);
158 remoteversion = _parse_version(AH, remoteversion_str + 11);
162 AH->public.remoteVersion = remoteversion;
164 if (myversion != remoteversion
165 && (remoteversion < AH->public.minRemoteVersion || remoteversion > AH->public.maxRemoteVersion) )
167 write_msg(NULL, "server version: %s, %s version: %s\n",
168 remoteversion_str, progname, PG_VERSION);
170 write_msg(NULL, "proceeding despite version mismatch\n");
172 die_horribly(AH, NULL, "aborting because of version mismatch (Use the -i option to proceed anyway.)\n");
177 * Check if a given user is a superuser.
180 UserIsSuperuser(ArchiveHandle *AH, char *user)
182 PQExpBuffer qry = createPQExpBuffer();
188 /* Get the superuser setting */
189 appendPQExpBuffer(qry, "select usesuper from pg_user where usename = '%s'", user);
190 res = PQexec(AH->connection, qry->data);
193 die_horribly(AH, modulename, "null result checking superuser status of %s\n", user);
195 if (PQresultStatus(res) != PGRES_TUPLES_OK)
196 die_horribly(AH, modulename, "could not check superuser status of %s: %s",
197 user, PQerrorMessage(AH->connection));
199 ntups = PQntuples(res);
205 i_usesuper = PQfnumber(res, "usesuper");
206 isSuper = (strcmp(PQgetvalue(res, 0, i_usesuper), "t") == 0);
210 destroyPQExpBuffer(qry);
216 ConnectedUserIsSuperuser(ArchiveHandle *AH)
218 return UserIsSuperuser(AH, PQuser(AH->connection));
222 ConnectedUser(ArchiveHandle *AH)
224 return PQuser(AH->connection);
228 * Reconnect the DB associated with the archive handle
231 ReconnectDatabase(ArchiveHandle *AH, const char *newdbname, char *newUser)
236 if (!newdbname || (strcmp(newdbname, "-") == 0))
237 dbname = PQdb(AH->connection);
239 dbname = (char *) newdbname;
241 /* Let's see if the request is already satisfied */
242 if (strcmp(PQuser(AH->connection), newUser) == 0 && strcmp(newdbname, PQdb(AH->connection)) == 0)
245 newConn = _connectDB(AH, dbname, newUser);
247 PQfinish(AH->connection);
248 AH->connection = newConn;
250 AH->username = strdup(newUser);
256 * Connect to the db again.
259 _connectDB(ArchiveHandle *AH, const char *reqdb, char *requser)
263 char *password = NULL;
269 if (!reqdb || (strcmp(reqdb, "-") == 0))
270 newdb = PQdb(AH->connection);
272 newdb = (char *) reqdb;
274 if (!requser || (strlen(requser) == 0))
275 newuser = PQuser(AH->connection);
277 newuser = (char *) requser;
279 ahlog(AH, 1, "connecting to database %s as user %s\n", newdb, newuser);
281 if (AH->requirePassword)
283 password = simple_prompt("Password: ", 100, false);
284 if (password == NULL)
285 die_horribly(AH, modulename, "out of memory\n");
291 newConn = PQsetdbLogin(PQhost(AH->connection), PQport(AH->connection),
295 die_horribly(AH, modulename, "failed to reconnect to database\n");
297 if (PQstatus(newConn) == CONNECTION_BAD)
299 noPwd = (strcmp(PQerrorMessage(newConn),
300 "fe_sendauth: no password supplied\n") == 0);
301 badPwd = (strncmp(PQerrorMessage(newConn),
302 "Password authentication failed for user", 39) == 0);
308 fprintf(stderr, "Password incorrect\n");
310 fprintf(stderr, "Connecting to %s as %s\n",
311 PQdb(AH->connection), newuser);
316 password = simple_prompt("Password: ", 100, false);
319 die_horribly(AH, modulename, "could not reconnect to database: %s",
320 PQerrorMessage(newConn));
333 * Make a database connection with the given parameters. The
334 * connection handle is returned, the parameters are stored in AHX.
335 * An interactive password prompt is automatically issued if required.
338 ConnectDatabase(Archive *AHX,
342 const char *username,
344 const int ignoreVersion)
346 ArchiveHandle *AH = (ArchiveHandle *) AHX;
347 char *password = NULL;
348 bool need_pass = false;
351 die_horribly(AH, modulename, "already connected to a database\n");
353 if (!dbname && !(dbname = getenv("PGDATABASE")))
354 die_horribly(AH, modulename, "no database name specified\n");
356 AH->dbname = strdup(dbname);
359 AH->pghost = strdup(pghost);
364 AH->pgport = strdup(pgport);
368 if (username != NULL)
369 AH->username = strdup(username);
375 password = simple_prompt("Password: ", 100, false);
376 if (password == NULL)
377 die_horribly(AH, modulename, "out of memory\n");
378 AH->requirePassword = true;
381 AH->requirePassword = false;
384 * Start the connection. Loop until we have a password if
385 * requested by backend.
390 AH->connection = PQsetdbLogin(AH->pghost, AH->pgport, NULL, NULL,
391 AH->dbname, AH->username, password);
394 die_horribly(AH, modulename, "failed to connect to database\n");
396 if (PQstatus(AH->connection) == CONNECTION_BAD &&
397 strcmp(PQerrorMessage(AH->connection), "fe_sendauth: no password supplied\n") == 0 &&
400 PQfinish(AH->connection);
404 password = simple_prompt("Password: ", 100, false);
411 /* check to see that the backend connection was successfully made */
412 if (PQstatus(AH->connection) == CONNECTION_BAD)
413 die_horribly(AH, modulename, "connection to database \"%s\" failed: %s",
414 AH->dbname, PQerrorMessage(AH->connection));
416 /* check for version mismatch */
417 _check_database_version(AH, ignoreVersion);
420 * AH->currUser = PQuser(AH->connection);
422 * Removed because it prevented an initial \connect when dumping to SQL
426 return AH->connection;
429 /* Public interface */
430 /* Convenience function to send a query. Monitors result to handle COPY statements */
432 ExecuteSqlCommand(ArchiveHandle *AH, PQExpBuffer qry, char *desc, bool use_blob)
435 return _executeSqlCommand(AH, AH->blobConnection, qry, desc);
437 return _executeSqlCommand(AH, AH->connection, qry, desc);
441 * Handle command execution. This is used to execute a command on more than one connection,
442 * but the 'pgCopyIn' setting assumes the COPY commands are ONLY executed on the primary
443 * setting...an error will be raised otherwise.
446 _executeSqlCommand(ArchiveHandle *AH, PGconn *conn, PQExpBuffer qry, char *desc)
450 /* fprintf(stderr, "Executing: '%s'\n\n", qry->data); */
451 res = PQexec(conn, qry->data);
453 die_horribly(AH, modulename, "%s: no result from server\n", desc);
455 if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
457 if (PQresultStatus(res) == PGRES_COPY_IN)
459 if (conn != AH->connection)
460 die_horribly(AH, modulename, "COPY command executed in non-primary connection\n");
465 die_horribly(AH, modulename, "%s: %s",
466 desc, PQerrorMessage(AH->connection));
471 return strlen(qry->data);
474 /* Convenience function to send one or more queries. Monitors result to handle COPY statements */
476 ExecuteSqlCommandBuf(ArchiveHandle *AH, void *qryv, int bufLen)
481 char *qry = (char *) qryv;
483 char *eos = qry + bufLen;
486 * fprintf(stderr, "\n\n*****\n
487 * Buffer:\n\n%s\n*******************\n\n", qry);
490 /* If we're in COPY IN mode, then just break it into lines and send... */
497 loc = strcspn(&qry[pos], "\n") + pos;
500 /* If no match, then wait */
501 if (loc >= (eos - qry)) /* None found */
503 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
508 * fprintf(stderr, "Found cr at %d, prev char was %c, next was
509 * %c\n", loc, qry[loc-1], qry[loc+1]);
512 /* Count the number of preceding slashes */
514 while (sPos > 0 && qry[sPos - 1] == '\\')
520 * If an odd number of preceding slashes, then \n was escaped
521 * so set the next search pos, and restart (if any left).
525 /* fprintf(stderr, "cr was escaped\n"); */
527 if (pos >= (eos - qry))
529 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
535 /* We got a good cr */
537 appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
539 isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
542 * fprintf(stderr, "Sending '%s' via
543 * COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd);
547 if (PQputline(AH->connection, AH->pgCopyBuf->data) != 0)
548 die_horribly(AH, modulename, "error returned by PQputline\n");
550 resetPQExpBuffer(AH->pgCopyBuf);
553 * fprintf(stderr, "Buffer is '%s'\n",
554 * AH->pgCopyBuf->data);
559 if (PQendcopy(AH->connection) != 0)
560 die_horribly(AH, modulename, "error returned by PQendcopy\n");
568 /* Make sure we're not past the original buffer end */
575 /* We may have finished Copy In, and have a non-empty buffer */
580 * The following is a mini state machine to assess then of of an
581 * SQL statement. It really only needs to parse good SQL, or at
582 * least that's the theory... End-of-statement is assumed to be an
583 * unquoted, un commented semi-colon.
587 * fprintf(stderr, "Buffer at start is: '%s'\n\n",
591 for (pos = 0; pos < (eos - qry); pos++)
593 appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
594 /* fprintf(stderr, " %c",qry[pos]); */
596 switch (AH->sqlparse.state)
599 case SQL_SCAN: /* Default state == 0, set in _allocAH */
601 if (qry[pos] == ';' && AH->sqlparse.braceDepth == 0)
603 /* Send It & reset the buffer */
606 * fprintf(stderr, " sending: '%s'\n\n",
609 ExecuteSqlCommand(AH, AH->sqlBuf, "could not execute query", false);
610 resetPQExpBuffer(AH->sqlBuf);
611 AH->sqlparse.lastChar = '\0';
615 if (qry[pos] == '"' || qry[pos] == '\'')
617 /* fprintf(stderr,"[startquote]\n"); */
618 AH->sqlparse.state = SQL_IN_QUOTE;
619 AH->sqlparse.quoteChar = qry[pos];
620 AH->sqlparse.backSlash = 0;
622 else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
623 AH->sqlparse.state = SQL_IN_SQL_COMMENT;
624 else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
625 AH->sqlparse.state = SQL_IN_EXT_COMMENT;
626 else if (qry[pos] == '(')
627 AH->sqlparse.braceDepth++;
628 else if (qry[pos] == ')')
629 AH->sqlparse.braceDepth--;
631 AH->sqlparse.lastChar = qry[pos];
636 case SQL_IN_SQL_COMMENT:
638 if (qry[pos] == '\n')
639 AH->sqlparse.state = SQL_SCAN;
642 case SQL_IN_EXT_COMMENT:
644 if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
645 AH->sqlparse.state = SQL_SCAN;
650 if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
652 /* fprintf(stderr,"[endquote]\n"); */
653 AH->sqlparse.state = SQL_SCAN;
658 if (qry[pos] == '\\')
660 if (AH->sqlparse.lastChar == '\\')
661 AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
663 AH->sqlparse.backSlash = 1;
666 AH->sqlparse.backSlash = 0;
671 AH->sqlparse.lastChar = qry[pos];
672 /* fprintf(stderr, "\n"); */
681 FixupBlobRefs(ArchiveHandle *AH, char *tablename)
690 if (strcmp(tablename, BLOB_XREF_TABLE) == 0)
693 tblQry = createPQExpBuffer();
695 appendPQExpBuffer(tblQry, "SELECT a.attname FROM pg_class c, pg_attribute a, pg_type t "
696 " WHERE a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid "
697 " AND t.typname = 'oid' AND c.relname = '%s';", tablename);
699 res = PQexec(AH->blobConnection, tblQry->data);
701 die_horribly(AH, modulename, "could not find oid columns of table \"%s\": %s",
702 tablename, PQerrorMessage(AH->connection));
704 if ((n = PQntuples(res)) == 0)
707 ahlog(AH, 1, "no OID type columns in table %s\n", tablename);
710 for (i = 0; i < n; i++)
712 attr = PQgetvalue(res, i, 0);
714 ahlog(AH, 1, "fixing BLOB cross-references for %s.%s\n", tablename, attr);
716 resetPQExpBuffer(tblQry);
719 * We should use coalesce here (rather than 'exists'), but it
720 * seems to be broken in 7.0.2 (weird optimizer strategy)
722 appendPQExpBuffer(tblQry, "UPDATE \"%s\" SET \"%s\" = ", tablename, attr);
723 appendPQExpBuffer(tblQry, " (SELECT x.newOid FROM \"%s\" x WHERE x.oldOid = \"%s\".\"%s\")",
724 BLOB_XREF_TABLE, tablename, attr);
725 appendPQExpBuffer(tblQry, " where exists"
726 "(select * from %s x where x.oldOid = \"%s\".\"%s\");",
727 BLOB_XREF_TABLE, tablename, attr);
729 ahlog(AH, 10, "SQL: %s\n", tblQry->data);
731 uRes = PQexec(AH->blobConnection, tblQry->data);
733 die_horribly(AH, modulename,
734 "could not update column \"%s\" of table \"%s\": %s",
735 attr, tablename, PQerrorMessage(AH->blobConnection));
737 if (PQresultStatus(uRes) != PGRES_COMMAND_OK)
738 die_horribly(AH, modulename,
739 "error while updating column \"%s\" of table \"%s\": %s",
740 attr, tablename, PQerrorMessage(AH->blobConnection));
746 destroyPQExpBuffer(tblQry);
750 * Convenient SQL calls
753 CreateBlobXrefTable(ArchiveHandle *AH)
755 PQExpBuffer qry = createPQExpBuffer();
757 /* IF we don't have a BLOB connection, then create one */
758 if (!AH->blobConnection)
759 AH->blobConnection = _connectDB(AH, NULL, NULL);
761 ahlog(AH, 1, "creating table for BLOB cross-references\n");
763 appendPQExpBuffer(qry, "Create Temporary Table %s(oldOid oid, newOid oid);", BLOB_XREF_TABLE);
765 ExecuteSqlCommand(AH, qry, "could not create BLOB cross reference table", true);
767 resetPQExpBuffer(qry);
769 appendPQExpBuffer(qry, "Create Unique Index %s_ix on %s(oldOid)", BLOB_XREF_TABLE, BLOB_XREF_TABLE);
770 ExecuteSqlCommand(AH, qry, "could not create index on BLOB cross reference table", true);
772 destroyPQExpBuffer(qry);
776 InsertBlobXref(ArchiveHandle *AH, int old, int new)
778 PQExpBuffer qry = createPQExpBuffer();
780 appendPQExpBuffer(qry, "Insert Into %s(oldOid, newOid) Values (%d, %d);", BLOB_XREF_TABLE, old, new);
782 ExecuteSqlCommand(AH, qry, "could not create BLOB cross reference entry", true);
784 destroyPQExpBuffer(qry);
788 StartTransaction(ArchiveHandle *AH)
790 PQExpBuffer qry = createPQExpBuffer();
792 appendPQExpBuffer(qry, "Begin;");
794 ExecuteSqlCommand(AH, qry, "could not start database transaction", false);
797 destroyPQExpBuffer(qry);
801 StartTransactionXref(ArchiveHandle *AH)
803 PQExpBuffer qry = createPQExpBuffer();
805 appendPQExpBuffer(qry, "Begin;");
807 ExecuteSqlCommand(AH, qry,
808 "could not start transaction for BLOB cross references", true);
809 AH->blobTxActive = true;
811 destroyPQExpBuffer(qry);
815 CommitTransaction(ArchiveHandle *AH)
817 PQExpBuffer qry = createPQExpBuffer();
819 appendPQExpBuffer(qry, "Commit;");
821 ExecuteSqlCommand(AH, qry, "could not commit database transaction", false);
822 AH->txActive = false;
824 destroyPQExpBuffer(qry);
828 CommitTransactionXref(ArchiveHandle *AH)
830 PQExpBuffer qry = createPQExpBuffer();
832 appendPQExpBuffer(qry, "Commit;");
834 ExecuteSqlCommand(AH, qry, "could not commit transaction for BLOB cross references", true);
835 AH->blobTxActive = false;
837 destroyPQExpBuffer(qry);