/*------------------------------------------------------------------------- * * pg_backup_db.c * * Implements the basic DB functions used by the archiver. * * IDENTIFICATION * src/bin/pg_dump/pg_backup_db.c * *------------------------------------------------------------------------- */ #include "postgres_fe.h" #include #include #ifdef HAVE_TERMIOS_H #include #endif #include "common/connect.h" #include "common/string.h" #include "dumputils.h" #include "fe_utils/string_utils.h" #include "parallel.h" #include "pg_backup_archiver.h" #include "pg_backup_db.h" #include "pg_backup_utils.h" static void _check_database_version(ArchiveHandle *AH); static void notice_processor(void *arg, const char *message); static void _check_database_version(ArchiveHandle *AH) { const char *remoteversion_str; int remoteversion; PGresult *res; remoteversion_str = PQparameterStatus(AH->connection, "server_version"); remoteversion = PQserverVersion(AH->connection); if (remoteversion == 0 || !remoteversion_str) pg_fatal("could not get server_version from libpq"); AH->public.remoteVersionStr = pg_strdup(remoteversion_str); AH->public.remoteVersion = remoteversion; if (!AH->archiveRemoteVersion) AH->archiveRemoteVersion = AH->public.remoteVersionStr; if (remoteversion != PG_VERSION_NUM && (remoteversion < AH->public.minRemoteVersion || remoteversion > AH->public.maxRemoteVersion)) { pg_log_error("aborting because of server version mismatch"); pg_log_error_detail("server version: %s; %s version: %s", remoteversion_str, progname, PG_VERSION); exit(1); } /* * Check if server is in recovery mode, which means we are on a hot * standby. */ res = ExecuteSqlQueryForSingleRow((Archive *) AH, "SELECT pg_catalog.pg_is_in_recovery()"); AH->public.isStandby = (strcmp(PQgetvalue(res, 0, 0), "t") == 0); PQclear(res); } /* * Reconnect to the server. If dbname is not NULL, use that database, * else the one associated with the archive handle. */ void ReconnectToServer(ArchiveHandle *AH, const char *dbname) { PGconn *oldConn = AH->connection; RestoreOptions *ropt = AH->public.ropt; /* * Save the dbname, if given, in override_dbname so that it will also * affect any later reconnection attempt. */ if (dbname) ropt->cparams.override_dbname = pg_strdup(dbname); /* * Note: we want to establish the new connection, and in particular update * ArchiveHandle's connCancel, before closing old connection. Otherwise * an ill-timed SIGINT could try to access a dead connection. */ AH->connection = NULL; /* dodge error check in ConnectDatabase */ ConnectDatabase((Archive *) AH, &ropt->cparams, true); PQfinish(oldConn); } /* * Make, or remake, a database connection with the given parameters. * * The resulting connection handle is stored in AHX->connection. * * An interactive password prompt is automatically issued if required. * We store the results of that in AHX->savedPassword. * Note: it's not really all that sensible to use a single-entry password * cache if the username keeps changing. In current usage, however, the * username never does change, so one savedPassword is sufficient. */ void ConnectDatabase(Archive *AHX, const ConnParams *cparams, bool isReconnect) { ArchiveHandle *AH = (ArchiveHandle *) AHX; trivalue prompt_password; char *password; bool new_pass; if (AH->connection) pg_fatal("already connected to a database"); /* Never prompt for a password during a reconnection */ prompt_password = isReconnect ? TRI_NO : cparams->promptPassword; password = AH->savedPassword; if (prompt_password == TRI_YES && password == NULL) password = simple_prompt("Password: ", false); /* * Start the connection. Loop until we have a password if requested by * backend. */ do { const char *keywords[8]; const char *values[8]; int i = 0; /* * If dbname is a connstring, its entries can override the other * values obtained from cparams; but in turn, override_dbname can * override the dbname component of it. */ keywords[i] = "host"; values[i++] = cparams->pghost; keywords[i] = "port"; values[i++] = cparams->pgport; keywords[i] = "user"; values[i++] = cparams->username; keywords[i] = "password"; values[i++] = password; keywords[i] = "dbname"; values[i++] = cparams->dbname; if (cparams->override_dbname) { keywords[i] = "dbname"; values[i++] = cparams->override_dbname; } keywords[i] = "fallback_application_name"; values[i++] = progname; keywords[i] = NULL; values[i++] = NULL; Assert(i <= lengthof(keywords)); new_pass = false; AH->connection = PQconnectdbParams(keywords, values, true); if (!AH->connection) pg_fatal("could not connect to database"); if (PQstatus(AH->connection) == CONNECTION_BAD && PQconnectionNeedsPassword(AH->connection) && password == NULL && prompt_password != TRI_NO) { PQfinish(AH->connection); password = simple_prompt("Password: ", false); new_pass = true; } } while (new_pass); /* check to see that the backend connection was successfully made */ if (PQstatus(AH->connection) == CONNECTION_BAD) { if (isReconnect) pg_fatal("reconnection failed: %s", PQerrorMessage(AH->connection)); else pg_fatal("%s", PQerrorMessage(AH->connection)); } /* Start strict; later phases may override this. */ PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH, ALWAYS_SECURE_SEARCH_PATH_SQL)); if (password && password != AH->savedPassword) free(password); /* * We want to remember connection's actual password, whether or not we got * it by prompting. So we don't just store the password variable. */ if (PQconnectionUsedPassword(AH->connection)) { if (AH->savedPassword) free(AH->savedPassword); AH->savedPassword = pg_strdup(PQpass(AH->connection)); } /* check for version mismatch */ _check_database_version(AH); PQsetNoticeProcessor(AH->connection, notice_processor, NULL); /* arrange for SIGINT to issue a query cancel on this connection */ set_archive_cancel_info(AH, AH->connection); } /* * Close the connection to the database and also cancel off the query if we * have one running. */ void DisconnectDatabase(Archive *AHX) { ArchiveHandle *AH = (ArchiveHandle *) AHX; char errbuf[1]; if (!AH->connection) return; if (AH->connCancel) { /* * If we have an active query, send a cancel before closing, ignoring * any errors. This is of no use for a normal exit, but might be * helpful during pg_fatal(). */ if (PQtransactionStatus(AH->connection) == PQTRANS_ACTIVE) (void) PQcancel(AH->connCancel, errbuf, sizeof(errbuf)); /* * Prevent signal handler from sending a cancel after this. */ set_archive_cancel_info(AH, NULL); } PQfinish(AH->connection); AH->connection = NULL; } PGconn * GetConnection(Archive *AHX) { ArchiveHandle *AH = (ArchiveHandle *) AHX; return AH->connection; } static void notice_processor(void *arg, const char *message) { pg_log_info("%s", message); } /* Like pg_fatal(), but with a complaint about a particular query. */ static void die_on_query_failure(ArchiveHandle *AH, const char *query) { pg_log_error("query failed: %s", PQerrorMessage(AH->connection)); pg_log_error_detail("Query was: %s", query); exit(1); } void ExecuteSqlStatement(Archive *AHX, const char *query) { ArchiveHandle *AH = (ArchiveHandle *) AHX; PGresult *res; res = PQexec(AH->connection, query); if (PQresultStatus(res) != PGRES_COMMAND_OK) die_on_query_failure(AH, query); PQclear(res); } PGresult * ExecuteSqlQuery(Archive *AHX, const char *query, ExecStatusType status) { ArchiveHandle *AH = (ArchiveHandle *) AHX; PGresult *res; res = PQexec(AH->connection, query); if (PQresultStatus(res) != status) die_on_query_failure(AH, query); return res; } /* * Execute an SQL query and verify that we got exactly one row back. */ PGresult * ExecuteSqlQueryForSingleRow(Archive *fout, const char *query) { PGresult *res; int ntups; res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK); /* Expecting a single result only */ ntups = PQntuples(res); if (ntups != 1) pg_fatal(ngettext("query returned %d row instead of one: %s", "query returned %d rows instead of one: %s", ntups), ntups, query); return res; } /* * Convenience function to send a query. * Monitors result to detect COPY statements */ static void ExecuteSqlCommand(ArchiveHandle *AH, const char *qry, const char *desc) { PGconn *conn = AH->connection; PGresult *res; #ifdef NOT_USED fprintf(stderr, "Executing: '%s'\n\n", qry); #endif res = PQexec(conn, qry); switch (PQresultStatus(res)) { case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: case PGRES_EMPTY_QUERY: /* A-OK */ break; case PGRES_COPY_IN: /* Assume this is an expected result */ AH->pgCopyIn = true; break; default: /* trouble */ warn_or_exit_horribly(AH, "%s: %sCommand was: %s", desc, PQerrorMessage(conn), qry); break; } PQclear(res); } /* * Process non-COPY table data (that is, INSERT commands). * * The commands have been run together as one long string for compressibility, * and we are receiving them in bufferloads with arbitrary boundaries, so we * have to locate command boundaries and save partial commands across calls. * All state must be kept in AH->sqlparse, not in local variables of this * routine. We assume that AH->sqlparse was filled with zeroes when created. * * We have to lex the data to the extent of identifying literals and quoted * identifiers, so that we can recognize statement-terminating semicolons. * We assume that INSERT data will not contain SQL comments, E'' literals, * or dollar-quoted strings, so this is much simpler than a full SQL lexer. * * Note: when restoring from a pre-9.0 dump file, this code is also used to * process BLOB COMMENTS data, which has the same problem of containing * multiple SQL commands that might be split across bufferloads. Fortunately, * that data won't contain anything complicated to lex either. */ static void ExecuteSimpleCommands(ArchiveHandle *AH, const char *buf, size_t bufLen) { const char *qry = buf; const char *eos = buf + bufLen; /* initialize command buffer if first time through */ if (AH->sqlparse.curCmd == NULL) AH->sqlparse.curCmd = createPQExpBuffer(); for (; qry < eos; qry++) { char ch = *qry; /* For neatness, we skip any newlines between commands */ if (!(ch == '\n' && AH->sqlparse.curCmd->len == 0)) appendPQExpBufferChar(AH->sqlparse.curCmd, ch); switch (AH->sqlparse.state) { case SQL_SCAN: /* Default state == 0, set in _allocAH */ if (ch == ';') { /* * We've found the end of a statement. Send it and reset * the buffer. */ ExecuteSqlCommand(AH, AH->sqlparse.curCmd->data, "could not execute query"); resetPQExpBuffer(AH->sqlparse.curCmd); } else if (ch == '\'') { AH->sqlparse.state = SQL_IN_SINGLE_QUOTE; AH->sqlparse.backSlash = false; } else if (ch == '"') { AH->sqlparse.state = SQL_IN_DOUBLE_QUOTE; } break; case SQL_IN_SINGLE_QUOTE: /* We needn't handle '' specially */ if (ch == '\'' && !AH->sqlparse.backSlash) AH->sqlparse.state = SQL_SCAN; else if (ch == '\\' && !AH->public.std_strings) AH->sqlparse.backSlash = !AH->sqlparse.backSlash; else AH->sqlparse.backSlash = false; break; case SQL_IN_DOUBLE_QUOTE: /* We needn't handle "" specially */ if (ch == '"') AH->sqlparse.state = SQL_SCAN; break; } } } /* * Implement ahwrite() for direct-to-DB restore */ int ExecuteSqlCommandBuf(Archive *AHX, const char *buf, size_t bufLen) { ArchiveHandle *AH = (ArchiveHandle *) AHX; if (AH->outputKind == OUTPUT_COPYDATA) { /* * COPY data. * * We drop the data on the floor if libpq has failed to enter COPY * mode; this allows us to behave reasonably when trying to continue * after an error in a COPY command. */ if (AH->pgCopyIn && PQputCopyData(AH->connection, buf, bufLen) <= 0) pg_fatal("error returned by PQputCopyData: %s", PQerrorMessage(AH->connection)); } else if (AH->outputKind == OUTPUT_OTHERDATA) { /* * Table data expressed as INSERT commands; or, in old dump files, * BLOB COMMENTS data (which is expressed as COMMENT ON commands). */ ExecuteSimpleCommands(AH, buf, bufLen); } else { /* * General SQL commands; we assume that commands will not be split * across calls. * * In most cases the data passed to us will be a null-terminated * string, but if it's not, we have to add a trailing null. */ if (buf[bufLen] == '\0') ExecuteSqlCommand(AH, buf, "could not execute query"); else { char *str = (char *) pg_malloc(bufLen + 1); memcpy(str, buf, bufLen); str[bufLen] = '\0'; ExecuteSqlCommand(AH, str, "could not execute query"); free(str); } } return bufLen; } /* * Terminate a COPY operation during direct-to-DB restore */ void EndDBCopyMode(Archive *AHX, const char *tocEntryTag) { ArchiveHandle *AH = (ArchiveHandle *) AHX; if (AH->pgCopyIn) { PGresult *res; if (PQputCopyEnd(AH->connection, NULL) <= 0) pg_fatal("error returned by PQputCopyEnd: %s", PQerrorMessage(AH->connection)); /* Check command status and return to normal libpq state */ res = PQgetResult(AH->connection); if (PQresultStatus(res) != PGRES_COMMAND_OK) warn_or_exit_horribly(AH, "COPY failed for table \"%s\": %s", tocEntryTag, PQerrorMessage(AH->connection)); PQclear(res); /* Do this to ensure we've pumped libpq back to idle state */ if (PQgetResult(AH->connection) != NULL) pg_log_warning("unexpected extra results during COPY of table \"%s\"", tocEntryTag); AH->pgCopyIn = false; } } void StartTransaction(Archive *AHX) { ArchiveHandle *AH = (ArchiveHandle *) AHX; ExecuteSqlCommand(AH, "BEGIN", "could not start database transaction"); } void CommitTransaction(Archive *AHX) { ArchiveHandle *AH = (ArchiveHandle *) AHX; ExecuteSqlCommand(AH, "COMMIT", "could not commit database transaction"); } void DropBlobIfExists(ArchiveHandle *AH, Oid oid) { /* * If we are not restoring to a direct database connection, we have to * guess about how to detect whether the blob exists. Assume new-style. */ if (AH->connection == NULL || PQserverVersion(AH->connection) >= 90000) { ahprintf(AH, "SELECT pg_catalog.lo_unlink(oid) " "FROM pg_catalog.pg_largeobject_metadata " "WHERE oid = '%u';\n", oid); } else { /* Restoring to pre-9.0 server, so do it the old way */ ahprintf(AH, "SELECT CASE WHEN EXISTS(" "SELECT 1 FROM pg_catalog.pg_largeobject WHERE loid = '%u'" ") THEN pg_catalog.lo_unlink('%u') END;\n", oid, oid); } }