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.29 2001/10/25 05:49:52 momjian 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, const char *newUser);
44 static int _executeSqlCommand(ArchiveHandle *AH, PGconn *conn, PQExpBuffer qry, char *desc);
45 static void notice_processor(void *arg, const char *message);
49 * simple_prompt --- borrowed from psql
51 * Generalized function especially intended for reading in usernames and
52 * password interactively. Reads from /dev/tty or stdin/stderr.
54 * prompt: The prompt to print
55 * maxlen: How many characters to accept
56 * echo: Set to false if you want to hide what is entered (for passwords)
58 * Returns a malloc()'ed string with the input (w/o trailing newline).
60 static bool prompt_state = false;
63 simple_prompt(const char *prompt, int maxlen, bool echo)
71 struct termios t_orig,
75 destination = (char *) malloc(maxlen + 2);
79 prompt_state = true; /* disable SIGINT */
82 * Do not try to collapse these into one "w+" mode file. Doesn't work
83 * on some platforms (eg, HPUX 10.20).
85 termin = fopen("/dev/tty", "r");
86 termout = fopen("/dev/tty", "w");
87 if (!termin || !termout)
100 tcgetattr(fileno(termin), &t);
103 tcsetattr(fileno(termin), TCSAFLUSH, &t);
109 fputs(gettext(prompt), termout);
113 if (fgets(destination, maxlen, termin) == NULL)
114 destination[0] = '\0';
116 length = strlen(destination);
117 if (length > 0 && destination[length - 1] != '\n')
119 /* eat rest of the line */
125 if (fgets(buf, sizeof(buf), termin) == NULL)
127 buflen = strlen(buf);
128 } while (buflen > 0 && buf[buflen - 1] != '\n');
131 if (length > 0 && destination[length - 1] == '\n')
132 /* remove trailing newline */
133 destination[length - 1] = '\0';
135 #ifdef HAVE_TERMIOS_H
138 tcsetattr(fileno(termin), TCSAFLUSH, &t_orig);
139 fputs("\n", termout);
150 prompt_state = false; /* SIGINT okay again */
157 _parse_version(ArchiveHandle *AH, const char *versionString)
164 cnt = sscanf(versionString, "%d.%d.%d", &vmaj, &vmin, &vrev);
167 die_horribly(AH, modulename, "unable to parse version string \"%s\"\n", versionString);
172 return (100 * vmaj + vmin) * 100 + vrev;
176 _check_database_version(ArchiveHandle *AH, bool ignoreVersion)
180 const char *remoteversion_str;
182 PGconn *conn = AH->connection;
184 myversion = _parse_version(AH, PG_VERSION);
186 res = PQexec(conn, "SELECT version();");
188 PQresultStatus(res) != PGRES_TUPLES_OK ||
191 die_horribly(AH, modulename, "could not get version from server: %s", PQerrorMessage(conn));
193 remoteversion_str = PQgetvalue(res, 0, 0);
194 remoteversion = _parse_version(AH, remoteversion_str + 11);
198 AH->public.remoteVersion = remoteversion;
200 if (myversion != remoteversion
201 && (remoteversion < AH->public.minRemoteVersion || remoteversion > AH->public.maxRemoteVersion))
203 write_msg(NULL, "server version: %s; %s version: %s\n",
204 remoteversion_str, progname, PG_VERSION);
206 write_msg(NULL, "proceeding despite version mismatch\n");
208 die_horribly(AH, NULL, "aborting because of version mismatch (Use the -i option to proceed anyway.)\n");
213 * Check if a given user is a superuser.
216 UserIsSuperuser(ArchiveHandle *AH, char *user)
218 PQExpBuffer qry = createPQExpBuffer();
224 /* Get the superuser setting */
225 appendPQExpBuffer(qry, "select usesuper from pg_user where usename = '%s'", user);
226 res = PQexec(AH->connection, qry->data);
229 die_horribly(AH, modulename, "null result checking superuser status of %s\n", user);
231 if (PQresultStatus(res) != PGRES_TUPLES_OK)
232 die_horribly(AH, modulename, "could not check superuser status of %s: %s",
233 user, PQerrorMessage(AH->connection));
235 ntups = PQntuples(res);
241 i_usesuper = PQfnumber(res, "usesuper");
242 isSuper = (strcmp(PQgetvalue(res, 0, i_usesuper), "t") == 0);
246 destroyPQExpBuffer(qry);
252 ConnectedUserIsSuperuser(ArchiveHandle *AH)
254 return UserIsSuperuser(AH, PQuser(AH->connection));
258 ConnectedUser(ArchiveHandle *AH)
260 return PQuser(AH->connection);
264 * Reconnect to the server. If dbname is not NULL, use that database,
265 * else the one associated with the archive handle. If username is
266 * not NULL, use that user name, else the one from the handle. If
267 * both the database and the user and match the existing connection
268 * already, nothing will be done.
270 * Returns 1 in any case.
273 ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username)
276 const char *newdbname;
277 const char *newusername;
280 newdbname = PQdb(AH->connection);
285 newusername = PQuser(AH->connection);
287 newusername = username;
289 /* Let's see if the request is already satisfied */
290 if (strcmp(newusername, PQuser(AH->connection)) == 0
291 && strcmp(newdbname, PQdb(AH->connection)) == 0)
294 newConn = _connectDB(AH, newdbname, newusername);
296 PQfinish(AH->connection);
297 AH->connection = newConn;
300 AH->username = strdup(newusername);
301 /* XXX Why don't we update AH->dbname? */
307 * Connect to the db again.
310 _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
314 char *password = NULL;
321 newdb = PQdb(AH->connection);
323 newdb = (char *) reqdb;
325 if (!requser || (strlen(requser) == 0))
326 newuser = PQuser(AH->connection);
328 newuser = (char *) requser;
330 ahlog(AH, 1, "connecting to database %s as user %s\n", newdb, newuser);
332 if (AH->requirePassword)
334 password = simple_prompt("Password: ", 100, false);
335 if (password == NULL)
336 die_horribly(AH, modulename, "out of memory\n");
342 newConn = PQsetdbLogin(PQhost(AH->connection), PQport(AH->connection),
346 die_horribly(AH, modulename, "failed to reconnect to database\n");
348 if (PQstatus(newConn) == CONNECTION_BAD)
350 noPwd = (strcmp(PQerrorMessage(newConn),
351 "fe_sendauth: no password supplied\n") == 0);
352 badPwd = (strncmp(PQerrorMessage(newConn),
353 "Password authentication failed for user", 39) == 0);
359 fprintf(stderr, "Password incorrect\n");
361 fprintf(stderr, "Connecting to %s as %s\n",
362 PQdb(AH->connection), newuser);
367 password = simple_prompt("Password: ", 100, false);
370 die_horribly(AH, modulename, "could not reconnect to database: %s",
371 PQerrorMessage(newConn));
379 PQsetNoticeProcessor(newConn, notice_processor, NULL);
386 * Make a database connection with the given parameters. The
387 * connection handle is returned, the parameters are stored in AHX.
388 * An interactive password prompt is automatically issued if required.
391 ConnectDatabase(Archive *AHX,
395 const char *username,
397 const int ignoreVersion)
399 ArchiveHandle *AH = (ArchiveHandle *) AHX;
400 char *password = NULL;
401 bool need_pass = false;
404 die_horribly(AH, modulename, "already connected to a database\n");
406 if (!dbname && !(dbname = getenv("PGDATABASE")))
407 die_horribly(AH, modulename, "no database name specified\n");
409 AH->dbname = strdup(dbname);
412 AH->pghost = strdup(pghost);
417 AH->pgport = strdup(pgport);
421 if (username != NULL)
422 AH->username = strdup(username);
428 password = simple_prompt("Password: ", 100, false);
429 if (password == NULL)
430 die_horribly(AH, modulename, "out of memory\n");
431 AH->requirePassword = true;
434 AH->requirePassword = false;
437 * Start the connection. Loop until we have a password if requested
443 AH->connection = PQsetdbLogin(AH->pghost, AH->pgport, NULL, NULL,
444 AH->dbname, AH->username, password);
447 die_horribly(AH, modulename, "failed to connect to database\n");
449 if (PQstatus(AH->connection) == CONNECTION_BAD &&
450 strcmp(PQerrorMessage(AH->connection), "fe_sendauth: no password supplied\n") == 0 &&
453 PQfinish(AH->connection);
457 password = simple_prompt("Password: ", 100, false);
464 /* check to see that the backend connection was successfully made */
465 if (PQstatus(AH->connection) == CONNECTION_BAD)
466 die_horribly(AH, modulename, "connection to database \"%s\" failed: %s",
467 AH->dbname, PQerrorMessage(AH->connection));
469 /* check for version mismatch */
470 _check_database_version(AH, ignoreVersion);
472 PQsetNoticeProcessor(AH->connection, notice_processor, NULL);
475 * AH->currUser = PQuser(AH->connection);
477 * Removed because it prevented an initial \connect when dumping to SQL
481 return AH->connection;
486 notice_processor(void *arg, const char *message)
488 write_msg(NULL, "%s", message);
492 /* Public interface */
493 /* Convenience function to send a query. Monitors result to handle COPY statements */
495 ExecuteSqlCommand(ArchiveHandle *AH, PQExpBuffer qry, char *desc, bool use_blob)
498 return _executeSqlCommand(AH, AH->blobConnection, qry, desc);
500 return _executeSqlCommand(AH, AH->connection, qry, desc);
504 * Handle command execution. This is used to execute a command on more than one connection,
505 * but the 'pgCopyIn' setting assumes the COPY commands are ONLY executed on the primary
506 * setting...an error will be raised otherwise.
509 _executeSqlCommand(ArchiveHandle *AH, PGconn *conn, PQExpBuffer qry, char *desc)
513 /* fprintf(stderr, "Executing: '%s'\n\n", qry->data); */
514 res = PQexec(conn, qry->data);
516 die_horribly(AH, modulename, "%s: no result from server\n", desc);
518 if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
520 if (PQresultStatus(res) == PGRES_COPY_IN)
522 if (conn != AH->connection)
523 die_horribly(AH, modulename, "COPY command executed in non-primary connection\n");
528 die_horribly(AH, modulename, "%s: %s",
529 desc, PQerrorMessage(AH->connection));
534 return strlen(qry->data);
537 /* Convenience function to send one or more queries. Monitors result to handle COPY statements */
539 ExecuteSqlCommandBuf(ArchiveHandle *AH, void *qryv, int bufLen)
544 char *qry = (char *) qryv;
546 char *eos = qry + bufLen;
549 * fprintf(stderr, "\n\n*****\n
550 * Buffer:\n\n%s\n*******************\n\n", qry);
553 /* If we're in COPY IN mode, then just break it into lines and send... */
560 loc = strcspn(&qry[pos], "\n") + pos;
563 /* If no match, then wait */
564 if (loc >= (eos - qry)) /* None found */
566 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
571 * fprintf(stderr, "Found cr at %d, prev char was %c, next was
572 * %c\n", loc, qry[loc-1], qry[loc+1]);
575 /* Count the number of preceding slashes */
577 while (sPos > 0 && qry[sPos - 1] == '\\')
583 * If an odd number of preceding slashes, then \n was escaped
584 * so set the next search pos, and restart (if any left).
588 /* fprintf(stderr, "cr was escaped\n"); */
590 if (pos >= (eos - qry))
592 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
598 /* We got a good cr */
600 appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
602 isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
605 * fprintf(stderr, "Sending '%s' via
606 * COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd);
610 if (PQputline(AH->connection, AH->pgCopyBuf->data) != 0)
611 die_horribly(AH, modulename, "error returned by PQputline\n");
613 resetPQExpBuffer(AH->pgCopyBuf);
616 * fprintf(stderr, "Buffer is '%s'\n",
617 * AH->pgCopyBuf->data);
622 if (PQendcopy(AH->connection) != 0)
623 die_horribly(AH, modulename, "error returned by PQendcopy\n");
631 /* Make sure we're not past the original buffer end */
638 /* We may have finished Copy In, and have a non-empty buffer */
642 * The following is a mini state machine to assess then of of an
643 * SQL statement. It really only needs to parse good SQL, or at
644 * least that's the theory... End-of-statement is assumed to be an
645 * unquoted, un commented semi-colon.
649 * fprintf(stderr, "Buffer at start is: '%s'\n\n",
653 for (pos = 0; pos < (eos - qry); pos++)
655 appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
656 /* fprintf(stderr, " %c",qry[pos]); */
658 switch (AH->sqlparse.state)
661 case SQL_SCAN: /* Default state == 0, set in _allocAH */
663 if (qry[pos] == ';' && AH->sqlparse.braceDepth == 0)
665 /* Send It & reset the buffer */
668 * fprintf(stderr, " sending: '%s'\n\n",
671 ExecuteSqlCommand(AH, AH->sqlBuf, "could not execute query", false);
672 resetPQExpBuffer(AH->sqlBuf);
673 AH->sqlparse.lastChar = '\0';
677 if (qry[pos] == '"' || qry[pos] == '\'')
679 /* fprintf(stderr,"[startquote]\n"); */
680 AH->sqlparse.state = SQL_IN_QUOTE;
681 AH->sqlparse.quoteChar = qry[pos];
682 AH->sqlparse.backSlash = 0;
684 else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
685 AH->sqlparse.state = SQL_IN_SQL_COMMENT;
686 else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
687 AH->sqlparse.state = SQL_IN_EXT_COMMENT;
688 else if (qry[pos] == '(')
689 AH->sqlparse.braceDepth++;
690 else if (qry[pos] == ')')
691 AH->sqlparse.braceDepth--;
693 AH->sqlparse.lastChar = qry[pos];
698 case SQL_IN_SQL_COMMENT:
700 if (qry[pos] == '\n')
701 AH->sqlparse.state = SQL_SCAN;
704 case SQL_IN_EXT_COMMENT:
706 if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
707 AH->sqlparse.state = SQL_SCAN;
712 if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
714 /* fprintf(stderr,"[endquote]\n"); */
715 AH->sqlparse.state = SQL_SCAN;
720 if (qry[pos] == '\\')
722 if (AH->sqlparse.lastChar == '\\')
723 AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
725 AH->sqlparse.backSlash = 1;
728 AH->sqlparse.backSlash = 0;
733 AH->sqlparse.lastChar = qry[pos];
734 /* fprintf(stderr, "\n"); */
743 FixupBlobRefs(ArchiveHandle *AH, char *tablename)
752 if (strcmp(tablename, BLOB_XREF_TABLE) == 0)
755 tblQry = createPQExpBuffer();
757 appendPQExpBuffer(tblQry, "SELECT a.attname FROM pg_class c, pg_attribute a, pg_type t "
758 " WHERE a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid "
759 " AND t.typname in ('oid', 'lo') AND c.relname = '%s';", tablename);
761 res = PQexec(AH->blobConnection, tblQry->data);
763 die_horribly(AH, modulename, "could not find oid columns of table \"%s\": %s",
764 tablename, PQerrorMessage(AH->connection));
766 if ((n = PQntuples(res)) == 0)
769 ahlog(AH, 1, "no OID type columns in table %s\n", tablename);
772 for (i = 0; i < n; i++)
774 attr = PQgetvalue(res, i, 0);
776 ahlog(AH, 1, "fixing large object cross-references for %s.%s\n", tablename, attr);
778 resetPQExpBuffer(tblQry);
781 * We should use coalesce here (rather than 'exists'), but it
782 * seems to be broken in 7.0.2 (weird optimizer strategy)
784 appendPQExpBuffer(tblQry, "UPDATE \"%s\" SET \"%s\" = ", tablename, attr);
785 appendPQExpBuffer(tblQry, " (SELECT x.newOid FROM \"%s\" x WHERE x.oldOid = \"%s\".\"%s\")",
786 BLOB_XREF_TABLE, tablename, attr);
787 appendPQExpBuffer(tblQry, " where exists"
788 "(select * from %s x where x.oldOid = \"%s\".\"%s\");",
789 BLOB_XREF_TABLE, tablename, attr);
791 ahlog(AH, 10, "SQL: %s\n", tblQry->data);
793 uRes = PQexec(AH->blobConnection, tblQry->data);
795 die_horribly(AH, modulename,
796 "could not update column \"%s\" of table \"%s\": %s",
797 attr, tablename, PQerrorMessage(AH->blobConnection));
799 if (PQresultStatus(uRes) != PGRES_COMMAND_OK)
800 die_horribly(AH, modulename,
801 "error while updating column \"%s\" of table \"%s\": %s",
802 attr, tablename, PQerrorMessage(AH->blobConnection));
808 destroyPQExpBuffer(tblQry);
812 * Convenient SQL calls
815 CreateBlobXrefTable(ArchiveHandle *AH)
817 PQExpBuffer qry = createPQExpBuffer();
819 /* IF we don't have a BLOB connection, then create one */
820 if (!AH->blobConnection)
821 AH->blobConnection = _connectDB(AH, NULL, NULL);
823 ahlog(AH, 1, "creating table for large object cross-references\n");
825 appendPQExpBuffer(qry, "Create Temporary Table %s(oldOid oid, newOid oid);", BLOB_XREF_TABLE);
827 ExecuteSqlCommand(AH, qry, "could not create large object cross-reference table", true);
829 resetPQExpBuffer(qry);
831 appendPQExpBuffer(qry, "Create Unique Index %s_ix on %s(oldOid)", BLOB_XREF_TABLE, BLOB_XREF_TABLE);
832 ExecuteSqlCommand(AH, qry, "could not create index on large object cross-reference table", true);
834 destroyPQExpBuffer(qry);
838 InsertBlobXref(ArchiveHandle *AH, int old, int new)
840 PQExpBuffer qry = createPQExpBuffer();
842 appendPQExpBuffer(qry, "Insert Into %s(oldOid, newOid) Values (%d, %d);", BLOB_XREF_TABLE, old, new);
844 ExecuteSqlCommand(AH, qry, "could not create large object cross-reference entry", true);
846 destroyPQExpBuffer(qry);
850 StartTransaction(ArchiveHandle *AH)
852 PQExpBuffer qry = createPQExpBuffer();
854 appendPQExpBuffer(qry, "Begin;");
856 ExecuteSqlCommand(AH, qry, "could not start database transaction", false);
859 destroyPQExpBuffer(qry);
863 StartTransactionXref(ArchiveHandle *AH)
865 PQExpBuffer qry = createPQExpBuffer();
867 appendPQExpBuffer(qry, "Begin;");
869 ExecuteSqlCommand(AH, qry,
870 "could not start transaction for large object cross-references", true);
871 AH->blobTxActive = true;
873 destroyPQExpBuffer(qry);
877 CommitTransaction(ArchiveHandle *AH)
879 PQExpBuffer qry = createPQExpBuffer();
881 appendPQExpBuffer(qry, "Commit;");
883 ExecuteSqlCommand(AH, qry, "could not commit database transaction", false);
884 AH->txActive = false;
886 destroyPQExpBuffer(qry);
890 CommitTransactionXref(ArchiveHandle *AH)
892 PQExpBuffer qry = createPQExpBuffer();
894 appendPQExpBuffer(qry, "Commit;");
896 ExecuteSqlCommand(AH, qry, "could not commit transaction for large object cross-references", true);
897 AH->blobTxActive = false;
899 destroyPQExpBuffer(qry);