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.15 2001/03/19 02:35:28 pjw 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 *-------------------------------------------------------------------------
24 #include "pg_backup.h"
25 #include "pg_backup_archiver.h"
26 #include "pg_backup_db.h"
28 #include <unistd.h> /* for getopt() */
36 #include "libpq/libpq-fs.h"
41 static const char *progname = "Archiver(db)";
43 static void _prompt_for_password(char *username, char *password);
44 static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion);
45 static PGconn* _connectDB(ArchiveHandle *AH, const char* newdbname, char *newUser);
46 static int _executeSqlCommand(ArchiveHandle* AH, PGconn *conn, PQExpBuffer qry, char *desc);
50 _prompt_for_password(char *username, char *password)
57 struct termios t_orig,
62 * Allow for forcing a specific username
64 if (strlen(username) == 0)
66 fprintf(stderr, "Username: ");
68 if (fgets(username, 100, stdin) == NULL)
70 length = strlen(username);
71 if (length > 0 && username[length - 1] != '\n')
73 /* eat rest of the line */
76 if (fgets(buf, sizeof(buf), stdin) == NULL)
79 } while (buflen > 0 && buf[buflen - 1] != '\n');
81 if (length > 0 && username[length - 1] == '\n')
82 username[length - 1] = '\0';
89 tcsetattr(0, TCSADRAIN, &t);
91 fprintf(stderr, "Password: ");
93 if (fgets(password, 100, stdin) == NULL)
96 tcsetattr(0, TCSADRAIN, &t_orig);
99 length = strlen(password);
100 if (length > 0 && password[length - 1] != '\n')
102 /* eat rest of the line */
105 if (fgets(buf, sizeof(buf), stdin) == NULL)
107 buflen = strlen(buf);
108 } while (buflen > 0 && buf[buflen - 1] != '\n');
110 if (length > 0 && password[length - 1] == '\n')
111 password[length - 1] = '\0';
113 fprintf(stderr, "\n\n");
118 _check_database_version(ArchiveHandle *AH, bool ignoreVersion)
122 const char *remoteversion_str;
123 double remoteversion;
124 PGconn *conn = AH->connection;
126 myversion = strtod(PG_VERSION, NULL);
127 res = PQexec(conn, "SELECT version()");
129 PQresultStatus(res) != PGRES_TUPLES_OK ||
132 die_horribly(AH, "check_database_version(): command failed. "
133 "Explanation from backend: '%s'.\n", PQerrorMessage(conn));
135 remoteversion_str = PQgetvalue(res, 0, 0);
136 remoteversion = strtod(remoteversion_str + 11, NULL);
137 if (myversion != remoteversion)
139 fprintf(stderr, "Database version: %s\n%s version: %s\n",
140 remoteversion_str, progname, PG_VERSION);
142 fprintf(stderr, "Proceeding despite version mismatch.\n");
144 die_horribly(AH, "Aborting because of version mismatch.\n"
145 "Use --ignore-version if you think it's safe to proceed anyway.\n");
151 * Check if a given user is a superuser.
153 int UserIsSuperuser(ArchiveHandle *AH, char* user)
155 PQExpBuffer qry = createPQExpBuffer();
161 /* Get the superuser setting */
162 appendPQExpBuffer(qry, "select usesuper from pg_user where usename = '%s'", user);
163 res = PQexec(AH->connection, qry->data);
166 die_horribly(AH, "%s: null result checking superuser status of %s.\n",
169 if (PQresultStatus(res) != PGRES_TUPLES_OK)
170 die_horribly(AH, "%s: Could not check superuser status of %s. Explanation from backend: %s\n",
171 progname, user, PQerrorMessage(AH->connection));
173 ntups = PQntuples(res);
179 i_usesuper = PQfnumber(res, "usesuper");
180 isSuper = (strcmp(PQgetvalue(res, 0, i_usesuper), "t") == 0);
187 int ConnectedUserIsSuperuser(ArchiveHandle *AH)
189 return UserIsSuperuser(AH, PQuser(AH->connection));
192 char* ConnectedUser(ArchiveHandle *AH)
194 return PQuser(AH->connection);
198 * Reconnect the DB associated with the archive handle
200 int ReconnectDatabase(ArchiveHandle *AH, const char* newdbname, char *newUser)
205 if (!newdbname || (strcmp(newdbname, "-") == 0) )
206 dbname = PQdb(AH->connection);
208 dbname = (char*)newdbname;
210 /* Let's see if the request is already satisfied */
211 if (strcmp(PQuser(AH->connection), newUser) == 0 && strcmp(newdbname, PQdb(AH->connection)) == 0)
214 newConn = _connectDB(AH, dbname, newUser);
216 PQfinish(AH->connection);
217 AH->connection = newConn;
218 strcpy(AH->username, newUser);
224 * Connect to the db again.
226 static PGconn* _connectDB(ArchiveHandle *AH, const char* reqdb, char *requser)
231 char *pwparam = NULL;
237 if (!reqdb || (strcmp(reqdb, "-") == 0) )
238 newdb = PQdb(AH->connection);
240 newdb = (char*)reqdb;
242 if (!requser || (strlen(requser) == 0))
243 newuser = PQuser(AH->connection);
245 newuser = (char*)requser;
247 ahlog(AH, 1, "Connecting to %s as %s\n", newdb, newuser);
252 newConn = PQsetdbLogin(PQhost(AH->connection), PQport(AH->connection),
256 die_horribly(AH, "%s: Failed to reconnect (PQsetdbLogin failed).\n", progname);
258 if (PQstatus(newConn) == CONNECTION_BAD)
260 noPwd = (strcmp(PQerrorMessage(newConn), "fe_sendauth: no password supplied\n") == 0);
261 badPwd = (strncmp(PQerrorMessage(newConn), "Password authentication failed for user", 39)
268 fprintf(stderr, "Password incorrect\n");
270 fprintf(stderr, "Connecting to %s as %s\n", PQdb(AH->connection), newuser);
273 _prompt_for_password(newuser, password);
277 die_horribly(AH, "%s: Could not reconnect. %s\n", progname, PQerrorMessage(newConn));
286 PGconn* ConnectDatabase(Archive *AHX,
291 const int ignoreVersion)
293 ArchiveHandle *AH = (ArchiveHandle*)AHX;
294 char connect_string[512] = "";
295 char tmp_string[128];
299 die_horribly(AH, "%s: already connected to database\n", progname);
301 if (!dbname && !(dbname = getenv("PGDATABASE")) )
302 die_horribly(AH, "%s: no database name specified\n", progname);
304 AH->dbname = strdup(dbname);
308 AH->pghost = strdup(pghost);
309 sprintf(tmp_string, "host=%s ", AH->pghost);
310 strcat(connect_string, tmp_string);
317 AH->pgport = strdup(pgport);
318 sprintf(tmp_string, "port=%s ", AH->pgport);
319 strcat(connect_string, tmp_string);
324 sprintf(tmp_string, "dbname=%s ", AH->dbname);
325 strcat(connect_string, tmp_string);
329 AH->username[0] = '\0';
330 _prompt_for_password(AH->username, password);
331 strcat(connect_string, "authtype=password ");
332 sprintf(tmp_string, "user=%s ", AH->username);
333 strcat(connect_string, tmp_string);
334 sprintf(tmp_string, "password=%s ", password);
335 strcat(connect_string, tmp_string);
336 MemSet(tmp_string, 0, sizeof(tmp_string));
337 MemSet(password, 0, sizeof(password));
339 AH->connection = PQconnectdb(connect_string);
340 MemSet(connect_string, 0, sizeof(connect_string));
342 /* check to see that the backend connection was successfully made */
343 if (PQstatus(AH->connection) == CONNECTION_BAD)
344 die_horribly(AH, "Connection to database '%s' failed.\n%s\n",
345 AH->dbname, PQerrorMessage(AH->connection));
347 /* check for version mismatch */
348 _check_database_version(AH, ignoreVersion);
351 * AH->currUser = PQuser(AH->connection);
353 * Removed because it prevented an initial \connect
354 * when dumping to SQL in pg_dump.
357 return AH->connection;
360 /* Public interface */
361 /* Convenience function to send a query. Monitors result to handle COPY statements */
362 int ExecuteSqlCommand(ArchiveHandle* AH, PQExpBuffer qry, char *desc)
364 return _executeSqlCommand(AH, AH->connection, qry, desc);
368 * Handle command execution. This is used to execute a command on more than one connection,
369 * but the 'pgCopyIn' setting assumes the COPY commands are ONLY executed on the primary
370 * setting...an error will be raised otherwise.
372 static int _executeSqlCommand(ArchiveHandle* AH, PGconn *conn, PQExpBuffer qry, char *desc)
376 /* fprintf(stderr, "Executing: '%s'\n\n", qry->data); */
377 res = PQexec(conn, qry->data);
379 die_horribly(AH, "%s: %s. No result from backend.\n", progname, desc);
381 if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
383 if (PQresultStatus(res) == PGRES_COPY_IN)
385 if (conn != AH->connection)
386 die_horribly(AH, "%s: COPY command execute in non-primary connection.\n", progname);
391 die_horribly(AH, "%s: %s. Code = %d. Explanation from backend: '%s'.\n",
392 progname, desc, PQresultStatus(res), PQerrorMessage(AH->connection));
397 return strlen(qry->data);
400 /* Convenience function to send one or more queries. Monitors result to handle COPY statements */
401 int ExecuteSqlCommandBuf(ArchiveHandle* AH, void *qryv, int bufLen)
406 char *qry = (char*)qryv;
408 char *eos = qry + bufLen;
410 /* fprintf(stderr, "\n\n*****\n Buffer:\n\n%s\n*******************\n\n", qry); */
412 /* If we're in COPY IN mode, then just break it into lines and send... */
417 loc = strcspn(&qry[pos], "\n") + pos;
420 /* If no match, then wait */
421 if (loc >= (eos - qry)) /* None found */
423 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
427 /* fprintf(stderr, "Found cr at %d, prev char was %c, next was %c\n", loc, qry[loc-1], qry[loc+1]); */
429 /* Count the number of preceding slashes */
431 while (sPos > 0 && qry[sPos-1] == '\\')
436 /* If an odd number of preceding slashes, then \n was escaped
437 * so set the next search pos, and restart (if any left).
441 /* fprintf(stderr, "cr was escaped\n"); */
443 if (pos >= (eos - qry))
445 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
451 /* We got a good cr */
453 appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
455 isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
457 /* fprintf(stderr, "Sending '%s' via COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd); */
459 if (PQputline(AH->connection, AH->pgCopyBuf->data) != 0)
460 die_horribly(AH, "%s: error returned by PQputline\n", progname);
462 resetPQExpBuffer(AH->pgCopyBuf);
464 /* fprintf(stderr, "Buffer is '%s'\n", AH->pgCopyBuf->data); */
467 if (PQendcopy(AH->connection) != 0)
468 die_horribly(AH, "%s: error returned by PQendcopy\n", progname);
476 /* Make sure we're not past the original buffer end */
483 /* We may have finished Copy In, and have a non-empty buffer */
487 * The following is a mini state machine to assess then of of an SQL statement.
488 * It really only needs to parse good SQL, or at least that's the theory...
489 * End-of-statement is assumed to be an unquoted, un commented semi-colon.
492 /* fprintf(stderr, "Buffer at start is: '%s'\n\n", AH->sqlBuf->data); */
494 for(pos=0; pos < (eos - qry); pos++)
496 appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
497 /* fprintf(stderr, " %c",qry[pos]); */
499 switch (AH->sqlparse.state) {
501 case SQL_SCAN: /* Default state == 0, set in _allocAH */
503 if (qry[pos] == ';' && AH->sqlparse.braceDepth == 0)
505 /* Send It & reset the buffer */
506 /* fprintf(stderr, " sending: '%s'\n\n", AH->sqlBuf->data); */
507 ExecuteSqlCommand(AH, AH->sqlBuf, "Could not execute query");
508 resetPQExpBuffer(AH->sqlBuf);
509 AH->sqlparse.lastChar = '\0';
513 if (qry[pos] == '"' || qry[pos] == '\'')
515 /* fprintf(stderr,"[startquote]\n"); */
516 AH->sqlparse.state = SQL_IN_QUOTE;
517 AH->sqlparse.quoteChar = qry[pos];
518 AH->sqlparse.backSlash = 0;
520 else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
522 AH->sqlparse.state = SQL_IN_SQL_COMMENT;
524 else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
526 AH->sqlparse.state = SQL_IN_EXT_COMMENT;
528 else if ( qry[pos] == '(' )
530 AH->sqlparse.braceDepth++;
532 else if (qry[pos] == ')')
534 AH->sqlparse.braceDepth--;
537 AH->sqlparse.lastChar = qry[pos];
542 case SQL_IN_SQL_COMMENT:
544 if (qry[pos] == '\n')
545 AH->sqlparse.state = SQL_SCAN;
548 case SQL_IN_EXT_COMMENT:
550 if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
551 AH->sqlparse.state = SQL_SCAN;
556 if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
558 /* fprintf(stderr,"[endquote]\n"); */
559 AH->sqlparse.state = SQL_SCAN;
564 if (qry[pos] == '\\')
566 if (AH->sqlparse.lastChar == '\\')
567 AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
569 AH->sqlparse.backSlash = 1;
571 AH->sqlparse.backSlash = 0;
577 AH->sqlparse.lastChar = qry[pos];
578 /* fprintf(stderr, "\n"); */
586 void FixupBlobRefs(ArchiveHandle *AH, char *tablename)
588 PQExpBuffer tblQry = createPQExpBuffer();
589 PGresult *res, *uRes;
593 if (strcmp(tablename, BLOB_XREF_TABLE) == 0)
596 appendPQExpBuffer(tblQry, "SELECT a.attname FROM pg_class c, pg_attribute a, pg_type t "
597 " WHERE a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid "
598 " AND t.typname = 'oid' AND c.relname = '%s';", tablename);
600 res = PQexec(AH->blobConnection, tblQry->data);
602 die_horribly(AH, "%s: could not find OID attrs of %s. Explanation from backend '%s'\n",
603 progname, tablename, PQerrorMessage(AH->connection));
605 if ((n = PQntuples(res)) == 0) {
607 ahlog(AH, 1, "No OID attributes in table %s\n", tablename);
612 for (i = 0 ; i < n ; i++)
614 attr = PQgetvalue(res, i, 0);
616 ahlog(AH, 1, " - %s.%s\n", tablename, attr);
618 resetPQExpBuffer(tblQry);
621 * We should use coalesce here (rather than 'exists'), but it seems to
622 * be broken in 7.0.2 (weird optimizer strategy)
624 appendPQExpBuffer(tblQry, "UPDATE \"%s\" SET \"%s\" = ",tablename, attr);
625 appendPQExpBuffer(tblQry, " (SELECT x.newOid FROM \"%s\" x WHERE x.oldOid = \"%s\".\"%s\")",
626 BLOB_XREF_TABLE, tablename, attr);
627 appendPQExpBuffer(tblQry, " where exists"
628 "(select * from %s x where x.oldOid = \"%s\".\"%s\");",
629 BLOB_XREF_TABLE, tablename, attr);
631 ahlog(AH, 10, " - sql:\n%s\n", tblQry->data);
633 uRes = PQexec(AH->blobConnection, tblQry->data);
635 die_horribly(AH, "%s: could not update attr %s of table %s. Explanation from backend '%s'\n",
636 progname, attr, tablename, PQerrorMessage(AH->blobConnection));
638 if ( PQresultStatus(uRes) != PGRES_COMMAND_OK )
639 die_horribly(AH, "%s: error while updating attr %s of table %s (result = %d)."
640 " Explanation from backend '%s'\n",
641 progname, attr, tablename, PQresultStatus(uRes),
642 PQerrorMessage(AH->blobConnection));
652 * Convenient SQL calls
654 void CreateBlobXrefTable(ArchiveHandle* AH)
656 PQExpBuffer qry = createPQExpBuffer();
658 /* IF we don't have a BLOB connection, then create one */
659 if (!AH->blobConnection)
661 AH->blobConnection = _connectDB(AH, NULL, NULL);
664 ahlog(AH, 1, "Creating table for BLOBS xrefs\n");
666 appendPQExpBuffer(qry, "Create Temporary Table %s(oldOid oid, newOid oid);", BLOB_XREF_TABLE);
668 _executeSqlCommand(AH, AH->blobConnection, qry, "can not create BLOB xref table '" BLOB_XREF_TABLE "'");
670 resetPQExpBuffer(qry);
672 appendPQExpBuffer(qry, "Create Unique Index %s_ix on %s(oldOid)", BLOB_XREF_TABLE, BLOB_XREF_TABLE);
673 _executeSqlCommand(AH, AH->blobConnection, qry, "can not create index on BLOB xref table '" BLOB_XREF_TABLE "'");
676 void InsertBlobXref(ArchiveHandle* AH, int old, int new)
678 PQExpBuffer qry = createPQExpBuffer();
680 appendPQExpBuffer(qry, "Insert Into %s(oldOid, newOid) Values (%d, %d);", BLOB_XREF_TABLE, old, new);
682 _executeSqlCommand(AH, AH->blobConnection, qry, "can not create BLOB xref entry");
685 void StartTransaction(ArchiveHandle* AH)
687 PQExpBuffer qry = createPQExpBuffer();
689 appendPQExpBuffer(qry, "Begin;");
691 ExecuteSqlCommand(AH, qry, "can not start database transaction");
695 void StartTransactionXref(ArchiveHandle* AH)
697 PQExpBuffer qry = createPQExpBuffer();
699 appendPQExpBuffer(qry, "Begin;");
701 _executeSqlCommand(AH, AH->blobConnection, qry, "can not start BLOB xref transaction");
702 AH->blobTxActive = true;
705 void CommitTransaction(ArchiveHandle* AH)
707 PQExpBuffer qry = createPQExpBuffer();
709 appendPQExpBuffer(qry, "Commit;");
711 ExecuteSqlCommand(AH, qry, "can not commit database transaction");
712 AH->txActive = false;
715 void CommitTransactionXref(ArchiveHandle* AH)
717 PQExpBuffer qry = createPQExpBuffer();
719 appendPQExpBuffer(qry, "Commit;");
721 _executeSqlCommand(AH, AH->blobConnection, qry, "can not commit BLOB xref transaction");
722 AH->blobTxActive = false;