aboutsummaryrefslogtreecommitdiff
path: root/src/bin/pg_dump/pg_backup_db.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/pg_dump/pg_backup_db.c')
-rw-r--r--src/bin/pg_dump/pg_backup_db.c497
1 files changed, 497 insertions, 0 deletions
diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c
new file mode 100644
index 00000000000..c98e938d4f2
--- /dev/null
+++ b/src/bin/pg_dump/pg_backup_db.c
@@ -0,0 +1,497 @@
+/*-------------------------------------------------------------------------
+ *
+ *
+*-------------------------------------------------------------------------
+ */
+
+#include <unistd.h> /* for getopt() */
+#include <ctype.h>
+
+#include "postgres.h"
+
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+
+#include "access/attnum.h"
+#include "access/htup.h"
+#include "catalog/pg_index.h"
+#include "catalog/pg_language.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+
+#include "libpq-fe.h"
+#include <libpq/libpq-fs.h>
+#ifndef HAVE_STRDUP
+#include "strdup.h"
+#endif
+
+#include "pg_dump.h"
+#include "pg_backup.h"
+#include "pg_backup_archiver.h"
+#include "pg_backup_db.h"
+
+static const char *progname = "Archiver(db)";
+
+static void _prompt_for_password(char *username, char *password);
+static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion);
+
+
+static void
+_prompt_for_password(char *username, char *password)
+{
+ char buf[512];
+ int length;
+
+#ifdef HAVE_TERMIOS_H
+ struct termios t_orig,
+ t;
+#endif
+
+ fprintf(stderr, "Username: ");
+ fflush(stderr);
+ fgets(username, 100, stdin);
+ length = strlen(username);
+ /* skip rest of the line */
+ if (length > 0 && username[length - 1] != '\n')
+ {
+ do
+ {
+ fgets(buf, 512, stdin);
+ } while (buf[strlen(buf) - 1] != '\n');
+ }
+ if (length > 0 && username[length - 1] == '\n')
+ username[length - 1] = '\0';
+
+#ifdef HAVE_TERMIOS_H
+ tcgetattr(0, &t);
+ t_orig = t;
+ t.c_lflag &= ~ECHO;
+ tcsetattr(0, TCSADRAIN, &t);
+#endif
+ fprintf(stderr, "Password: ");
+ fflush(stderr);
+ fgets(password, 100, stdin);
+#ifdef HAVE_TERMIOS_H
+ tcsetattr(0, TCSADRAIN, &t_orig);
+#endif
+
+ length = strlen(password);
+ /* skip rest of the line */
+ if (length > 0 && password[length - 1] != '\n')
+ {
+ do
+ {
+ fgets(buf, 512, stdin);
+ } while (buf[strlen(buf) - 1] != '\n');
+ }
+ if (length > 0 && password[length - 1] == '\n')
+ password[length - 1] = '\0';
+
+ fprintf(stderr, "\n\n");
+}
+
+
+static void
+_check_database_version(ArchiveHandle *AH, bool ignoreVersion)
+{
+ PGresult *res;
+ double myversion;
+ const char *remoteversion_str;
+ double remoteversion;
+ PGconn *conn = AH->connection;
+
+ myversion = strtod(PG_VERSION, NULL);
+ res = PQexec(conn, "SELECT version()");
+ if (!res ||
+ PQresultStatus(res) != PGRES_TUPLES_OK ||
+ PQntuples(res) != 1)
+
+ die_horribly(AH, "check_database_version(): command failed. "
+ "Explanation from backend: '%s'.\n", PQerrorMessage(conn));
+
+ remoteversion_str = PQgetvalue(res, 0, 0);
+ remoteversion = strtod(remoteversion_str + 11, NULL);
+ if (myversion != remoteversion)
+ {
+ fprintf(stderr, "Database version: %s\n%s version: %s\n",
+ progname, remoteversion_str, PG_VERSION);
+ if (ignoreVersion)
+ fprintf(stderr, "Proceeding despite version mismatch.\n");
+ else
+ die_horribly(AH, "Aborting because of version mismatch.\n"
+ "Use --ignore-version if you think it's safe to proceed anyway.\n");
+ }
+ PQclear(res);
+}
+
+PGconn* ConnectDatabase(Archive *AHX,
+ const char* dbname,
+ const char* pghost,
+ const char* pgport,
+ const int reqPwd,
+ const int ignoreVersion)
+{
+ ArchiveHandle *AH = (ArchiveHandle*)AHX;
+ char connect_string[512] = "";
+ char tmp_string[128];
+ char password[100];
+
+ if (AH->connection)
+ die_horribly(AH, "%s: already connected to database\n", progname);
+
+ if (!dbname && !(dbname = getenv("PGDATABASE")) )
+ die_horribly(AH, "%s: no database name specified\n", progname);
+
+ AH->dbname = strdup(dbname);
+
+ if (pghost != NULL)
+ {
+ AH->pghost = strdup(pghost);
+ sprintf(tmp_string, "host=%s ", AH->pghost);
+ strcat(connect_string, tmp_string);
+ }
+ else
+ AH->pghost = NULL;
+
+ if (pgport != NULL)
+ {
+ AH->pgport = strdup(pgport);
+ sprintf(tmp_string, "port=%s ", AH->pgport);
+ strcat(connect_string, tmp_string);
+ }
+ else
+ AH->pgport = NULL;
+
+ sprintf(tmp_string, "dbname=%s ", AH->dbname);
+ strcat(connect_string, tmp_string);
+
+ if (reqPwd)
+ {
+ _prompt_for_password(AH->username, password);
+ strcat(connect_string, "authtype=password ");
+ sprintf(tmp_string, "user=%s ", AH->username);
+ strcat(connect_string, tmp_string);
+ sprintf(tmp_string, "password=%s ", password);
+ strcat(connect_string, tmp_string);
+ MemSet(tmp_string, 0, sizeof(tmp_string));
+ MemSet(password, 0, sizeof(password));
+ }
+ AH->connection = PQconnectdb(connect_string);
+ MemSet(connect_string, 0, sizeof(connect_string));
+
+ /* check to see that the backend connection was successfully made */
+ if (PQstatus(AH->connection) == CONNECTION_BAD)
+ die_horribly(AH, "Connection to database '%s' failed.\n%s\n",
+ AH->dbname, PQerrorMessage(AH->connection));
+
+ /* check for version mismatch */
+ _check_database_version(AH, ignoreVersion);
+
+ return AH->connection;
+}
+
+/* Convenience function to send a query. Monitors result to handle COPY statements */
+int ExecuteSqlCommand(ArchiveHandle* AH, PQExpBuffer qry, char *desc)
+{
+ PGresult *res;
+
+ /* fprintf(stderr, "Executing: '%s'\n\n", qry->data); */
+ res = PQexec(AH->connection, qry->data);
+ if (!res)
+ die_horribly(AH, "%s: %s. No result from backend.\n", progname, desc);
+
+ if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ if (PQresultStatus(res) == PGRES_COPY_IN)
+ AH->pgCopyIn = 1;
+ else
+ die_horribly(AH, "%s: %s. Code = %d. Explanation from backend: '%s'.\n",
+ progname, desc, PQresultStatus(res), PQerrorMessage(AH->connection));
+ }
+
+ PQclear(res);
+
+ return strlen(qry->data);
+}
+
+/* Convenience function to send one or more queries. Monitors result to handle COPY statements */
+int ExecuteSqlCommandBuf(ArchiveHandle* AH, void *qryv, int bufLen)
+{
+ int loc;
+ int pos = 0;
+ int sPos = 0;
+ char *qry = (char*)qryv;
+ int isEnd = 0;
+ char *eos = qry + bufLen;
+
+ /* fprintf(stderr, "\n\n*****\n Buffer:\n\n%s\n*******************\n\n", qry); */
+
+ /* If we're in COPY IN mode, then just break it into lines and send... */
+ if (AH->pgCopyIn) {
+ for(;;) {
+
+ /* Find a lf */
+ loc = strcspn(&qry[pos], "\n") + pos;
+ pos = 0;
+
+ /* If no match, then wait */
+ if (loc >= (eos - qry)) /* None found */
+ {
+ appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
+ break;
+ };
+
+ /* fprintf(stderr, "Found cr at %d, prev char was %c, next was %c\n", loc, qry[loc-1], qry[loc+1]); */
+
+ /* Count the number of preceding slashes */
+ sPos = loc;
+ while (sPos > 0 && qry[sPos-1] == '\\')
+ sPos--;
+
+ sPos = loc - sPos;
+
+ /* If an odd number of preceding slashes, then \n was escaped
+ * so set the next search pos, and restart (if any left).
+ */
+ if ((sPos & 1) == 1)
+ {
+ /* fprintf(stderr, "cr was escaped\n"); */
+ pos = loc + 1;
+ if (pos >= (eos - qry))
+ {
+ appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
+ break;
+ }
+ }
+ else
+ {
+ /* We got a good cr */
+ qry[loc] = '\0';
+ appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
+ qry += loc + 1;
+ isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
+
+ /* fprintf(stderr, "Sending '%s' via COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd); */
+
+ PQputline(AH->connection, AH->pgCopyBuf->data);
+
+ resetPQExpBuffer(AH->pgCopyBuf);
+
+ /* fprintf(stderr, "Buffer is '%s'\n", AH->pgCopyBuf->data); */
+
+ if(isEnd) {
+ PQendcopy(AH->connection);
+ AH->pgCopyIn = 0;
+ break;
+ }
+
+ }
+
+ /* Make sure we're not past the original buffer end */
+ if (qry >= eos)
+ break;
+
+ }
+ }
+
+ /* We may have finished Copy In, and have a non-empty buffer */
+ if (!AH->pgCopyIn) {
+
+ /*
+ * The following is a mini state machine to assess then of of an SQL statement.
+ * It really only needs to parse good SQL, or at least that's the theory...
+ * End-of-statement is assumed to be an unquoted, un commented semi-colon.
+ */
+
+ /* fprintf(stderr, "Buffer at start is: '%s'\n\n", AH->sqlBuf->data); */
+
+ for(pos=0; pos < (eos - qry); pos++)
+ {
+ appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
+ /* fprintf(stderr, " %c",qry[pos]); */
+
+ switch (AH->sqlparse.state) {
+
+ case SQL_SCAN: /* Default state == 0, set in _allocAH */
+
+ if (qry[pos] == ';')
+ {
+ /* Send It & reset the buffer */
+ /* fprintf(stderr, " sending: '%s'\n\n", AH->sqlBuf->data); */
+ ExecuteSqlCommand(AH, AH->sqlBuf, "Could not execute query");
+ resetPQExpBuffer(AH->sqlBuf);
+ AH->sqlparse.lastChar = '\0';
+ }
+ else
+ {
+ if (qry[pos] == '"' || qry[pos] == '\'')
+ {
+ /* fprintf(stderr,"[startquote]\n"); */
+ AH->sqlparse.state = SQL_IN_QUOTE;
+ AH->sqlparse.quoteChar = qry[pos];
+ AH->sqlparse.backSlash = 0;
+ }
+ else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
+ {
+ AH->sqlparse.state = SQL_IN_SQL_COMMENT;
+ }
+ else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
+ {
+ AH->sqlparse.state = SQL_IN_EXT_COMMENT;
+ }
+ AH->sqlparse.lastChar = qry[pos];
+ }
+
+ break;
+
+ case SQL_IN_SQL_COMMENT:
+
+ if (qry[pos] == '\n')
+ AH->sqlparse.state = SQL_SCAN;
+ break;
+
+ case SQL_IN_EXT_COMMENT:
+
+ if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
+ AH->sqlparse.state = SQL_SCAN;
+ break;
+
+ case SQL_IN_QUOTE:
+
+ if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
+ {
+ /* fprintf(stderr,"[endquote]\n"); */
+ AH->sqlparse.state = SQL_SCAN;
+ }
+ else
+ {
+
+ if (qry[pos] == '\\')
+ {
+ if (AH->sqlparse.lastChar == '\\')
+ AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
+ else
+ AH->sqlparse.backSlash = 1;
+ } else {
+ AH->sqlparse.backSlash = 0;
+ }
+ }
+ break;
+
+ }
+ AH->sqlparse.lastChar = qry[pos];
+ /* fprintf(stderr, "\n"); */
+ }
+
+ }
+
+ return 1;
+}
+
+void FixupBlobRefs(ArchiveHandle *AH, char *tablename)
+{
+ PQExpBuffer tblQry = createPQExpBuffer();
+ PGresult *res, *uRes;
+ int i, n;
+ char *attr;
+
+ for(i=0 ; i < strlen(tablename) ; i++)
+ tablename[i] = tolower(tablename[i]);
+
+ if (strcmp(tablename, BLOB_XREF_TABLE) == 0)
+ return;
+
+ appendPQExpBuffer(tblQry, "SELECT a.attname FROM pg_class c, pg_attribute a, pg_type t "
+ " WHERE a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid "
+ " AND t.typname = 'oid' AND c.relname = '%s';", tablename);
+
+ res = PQexec(AH->connection, tblQry->data);
+ if (!res)
+ die_horribly(AH, "%s: could not find OID attrs of %s. Explanation from backend '%s'\n",
+ progname, tablename, PQerrorMessage(AH->connection));
+
+ if ((n = PQntuples(res)) == 0) {
+ /* We're done */
+ ahlog(AH, 1, "No OID attributes in table %s\n", tablename);
+ PQclear(res);
+ return;
+ }
+
+ for (i = 0 ; i < n ; i++)
+ {
+ attr = PQgetvalue(res, i, 0);
+
+ ahlog(AH, 1, " - %s.%s\n", tablename, attr);
+
+ resetPQExpBuffer(tblQry);
+ appendPQExpBuffer(tblQry, "Update \"%s\" Set \"%s\" = x.newOid From %s x "
+ "Where x.oldOid = \"%s\".\"%s\";",
+
+ tablename, attr, BLOB_XREF_TABLE, tablename, attr);
+
+ ahlog(AH, 10, " - sql = %s\n", tblQry->data);
+
+ uRes = PQexec(AH->connection, tblQry->data);
+ if (!uRes)
+ die_horribly(AH, "%s: could not update attr %s of table %s. Explanation from backend '%s'\n",
+ progname, attr, tablename, PQerrorMessage(AH->connection));
+
+ if ( PQresultStatus(uRes) != PGRES_COMMAND_OK )
+ die_horribly(AH, "%s: error while updating attr %s of table %s. Explanation from backend '%s'\n",
+ progname, attr, tablename, PQerrorMessage(AH->connection));
+
+ PQclear(uRes);
+ }
+
+ PQclear(res);
+
+}
+
+/**********
+ * Convenient SQL calls
+ **********/
+void CreateBlobXrefTable(ArchiveHandle* AH)
+{
+ PQExpBuffer qry = createPQExpBuffer();
+
+ ahlog(AH, 1, "Creating table for BLOBS xrefs\n");
+
+ appendPQExpBuffer(qry, "Create Temporary Table %s(oldOid oid, newOid oid);", BLOB_XREF_TABLE);
+
+ ExecuteSqlCommand(AH, qry, "can not create BLOB xref table '" BLOB_XREF_TABLE "'");
+
+ resetPQExpBuffer(qry);
+
+ appendPQExpBuffer(qry, "Create Unique Index %s_ix on %s(oldOid)", BLOB_XREF_TABLE, BLOB_XREF_TABLE);
+ ExecuteSqlCommand(AH, qry, "can not create index on BLOB xref table '" BLOB_XREF_TABLE "'");
+}
+
+void InsertBlobXref(ArchiveHandle* AH, int old, int new)
+{
+ PQExpBuffer qry = createPQExpBuffer();
+
+ appendPQExpBuffer(qry, "Insert Into %s(oldOid, newOid) Values (%d, %d);", BLOB_XREF_TABLE, old, new);
+
+ ExecuteSqlCommand(AH, qry, "can not create BLOB xref entry");
+}
+
+void StartTransaction(ArchiveHandle* AH)
+{
+ PQExpBuffer qry = createPQExpBuffer();
+
+ appendPQExpBuffer(qry, "Begin;");
+
+ ExecuteSqlCommand(AH, qry, "can not start database transaction");
+}
+
+void CommitTransaction(ArchiveHandle* AH)
+{
+ PQExpBuffer qry = createPQExpBuffer();
+
+ appendPQExpBuffer(qry, "Commit;");
+
+ ExecuteSqlCommand(AH, qry, "can not commit database transaction");
+}
+
+