/* * SendQuery: send the query string to the backend * (and print out results) * * Note: This is the "front door" way to send a query. That is, use it to * send queries actually entered by the user. These queries will be subject to * single step mode. * To send "back door" queries (generated by slash commands, etc.) in a * controlled way, use PSQLexec(). * * Returns true if the query executed successfully, false otherwise. */ bool SendQuery(const char *query) { PGresult *results; TimevalStruct before, after; bool OK; if (!pset.db) { psql_error("You are currently not connected to a database.\n"); return false; } if (GetVariableBool(pset.vars, "SINGLESTEP")) { char buf[3]; printf(gettext("***(Single step mode: verify command)*******************************************\n" "%s\n" "***(press return to proceed or enter x and return to cancel)********************\n"), query); fflush(stdout); if (fgets(buf, sizeof(buf), stdin) != NULL) if (buf[0] == 'x') return false; } else if (VariableEquals(pset.vars, "ECHO", "queries")) { puts(query); fflush(stdout); } SetCancelConn(); if (PQtransactionStatus(pset.db) == PQTRANS_IDLE && !GetVariableBool(pset.vars, "AUTOCOMMIT") && !is_transact_command(query)) { results = PQexec(pset.db, "BEGIN"); if (PQresultStatus(results) != PGRES_COMMAND_OK) { psql_error("%s", PQerrorMessage(pset.db)); PQclear(results); ResetCancelConn(); return false; } PQclear(results); } if (pset.timing) GETTIMEOFDAY(&before); results = PQexec(pset.db, query); /* these operations are included in the timing result: */ OK = (AcceptResult(results) && ProcessCopyResult(results)); if (pset.timing) GETTIMEOFDAY(&after); /* but printing results isn't: */ if (OK) OK = PrintQueryResults(results); PQclear(results); /* Possible microtiming output */ if (OK && pset.timing) printf(gettext("Time: %.3f ms\n"), DIFF_MSEC(&after, &before)); /* check for events that may occur during query execution */ if (pset.encoding != PQclientEncoding(pset.db) && PQclientEncoding(pset.db) >= 0) { /* track effects of SET CLIENT_ENCODING */ pset.encoding = PQclientEncoding(pset.db); pset.popt.topt.encoding = pset.encoding; SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding)); } PrintNotifications(); return OK; }
/* * GetQueryResult * * Process the query result. Returns true if there's no error, false * otherwise -- but errors about trying to vacuum a missing relation are * reported and subsequently ignored. */ static bool GetQueryResult(PGconn *conn, const char *dbname, const char *progname) { PGresult *result; SetCancelConn(conn); while ((result = PQgetResult(conn)) != NULL) { /* * If errors are found, report them. Errors about a missing table are * harmless so we continue processing; but die for other errors. */ if (PQresultStatus(result) != PGRES_COMMAND_OK) { char *sqlState = PQresultErrorField(result, PG_DIAG_SQLSTATE); fprintf(stderr, _("%s: vacuuming of database \"%s\" failed: %s"), progname, dbname, PQerrorMessage(conn)); if (sqlState && strcmp(sqlState, ERRCODE_UNDEFINED_TABLE) != 0) { PQclear(result); return false; } } PQclear(result); } ResetCancelConn(); return true; }
/* * do_lo_export() * * Write a large object to a file */ bool do_lo_export(const char *loid_arg, const char *filename_arg) { int status; bool own_transaction; if (!start_lo_xact("\\lo_export", &own_transaction)) return false; SetCancelConn(); status = lo_export(pset.db, atooid(loid_arg), filename_arg); ResetCancelConn(); /* of course this status is documented nowhere :( */ if (status != 1) { fputs(PQerrorMessage(pset.db), stderr); return fail_lo_xact("\\lo_export", own_transaction); } if (!finish_lo_xact("\\lo_export", own_transaction)) return false; fprintf(pset.queryFout, "lo_export\n"); return true; }
/* * do_lo_export() * * Write a large object to a file */ bool do_lo_export(const char *loid_arg, const char *filename_arg) { int status; bool own_transaction; if (!start_lo_xact("\\lo_export", &own_transaction)) return false; SetCancelConn(); status = lo_export(pset.db, atooid(loid_arg), filename_arg); ResetCancelConn(); /* of course this status is documented nowhere :( */ if (status != 1) { psql_error("%s", PQerrorMessage(pset.db)); return fail_lo_xact("\\lo_export", own_transaction); } if (!finish_lo_xact("\\lo_export", own_transaction)) return false; print_lo_result("lo_export"); return true; }
/* * do_lo_unlink() * * removes a large object out of the database */ bool do_lo_unlink(const char *loid_arg) { int status; Oid loid = atooid(loid_arg); bool own_transaction; if (!start_lo_xact("\\lo_unlink", &own_transaction)) return false; SetCancelConn(); status = lo_unlink(pset.db, loid); ResetCancelConn(); if (status == -1) { psql_error("%s", PQerrorMessage(pset.db)); return fail_lo_xact("\\lo_unlink", own_transaction); } if (!finish_lo_xact("\\lo_unlink", own_transaction)) return false; print_lo_result("lo_unlink %u", loid); return true; }
/* * do_lo_unlink() * * removes a large object out of the database */ bool do_lo_unlink(const char *loid_arg) { int status; Oid loid = atooid(loid_arg); bool own_transaction; if (!start_lo_xact("\\lo_unlink", &own_transaction)) return false; SetCancelConn(); status = lo_unlink(pset.db, loid); ResetCancelConn(); if (status == -1) { fputs(PQerrorMessage(pset.db), stderr); return fail_lo_xact("\\lo_unlink", own_transaction); } if (!finish_lo_xact("\\lo_unlink", own_transaction)) return false; fprintf(pset.queryFout, "lo_unlink %u\n", loid); return true; }
/* * PSQLexec * * This is the way to send "backdoor" queries (those not directly entered * by the user). It is subject to -E but not -e. * * In autocommit-off mode, a new transaction block is started if start_xact * is true; nothing special is done when start_xact is false. Typically, * start_xact = false is used for SELECTs and explicit BEGIN/COMMIT commands. * * Caller is responsible for handling the ensuing processing if a COPY * command is sent. * * Note: we don't bother to check PQclientEncoding; it is assumed that no * caller uses this path to issue "SET CLIENT_ENCODING". */ PGresult * PSQLexec(const char *query, bool start_xact) { PGresult *res; if (!pset.db) { psql_error("You are currently not connected to a database.\n"); return NULL; } if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF) { printf(_("********* QUERY **********\n" "%s\n" "**************************\n\n"), query); fflush(stdout); if (pset.logfile) { fprintf(pset.logfile, _("********* QUERY **********\n" "%s\n" "**************************\n\n"), query); fflush(pset.logfile); } if (pset.echo_hidden == PSQL_ECHO_HIDDEN_NOEXEC) return NULL; } SetCancelConn(); if (start_xact && !pset.autocommit && PQtransactionStatus(pset.db) == PQTRANS_IDLE) { res = PQexec(pset.db, "BEGIN"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { psql_error("%s", PQerrorMessage(pset.db)); PQclear(res); ResetCancelConn(); return NULL; } PQclear(res); } res = PQexec(pset.db, query); ResetCancelConn(); if (!AcceptResult(res)) { PQclear(res); res = NULL; } return res; }
/* * do_lo_import() * * Copy large object from file to database */ bool do_lo_import(const char *filename_arg, const char *comment_arg) { PGresult *res; Oid loid; char oidbuf[32]; bool own_transaction; if (!start_lo_xact("\\lo_import", &own_transaction)) return false; SetCancelConn(); loid = lo_import(pset.db, filename_arg); ResetCancelConn(); if (loid == InvalidOid) { psql_error("%s", PQerrorMessage(pset.db)); return fail_lo_xact("\\lo_import", own_transaction); } /* insert description if given */ if (comment_arg) { char *cmdbuf; char *bufptr; size_t slen = strlen(comment_arg); cmdbuf = malloc(slen * 2 + 256); if (!cmdbuf) return fail_lo_xact("\\lo_import", own_transaction); sprintf(cmdbuf, "COMMENT ON LARGE OBJECT %u IS '", loid); bufptr = cmdbuf + strlen(cmdbuf); bufptr += PQescapeStringConn(pset.db, bufptr, comment_arg, slen, NULL); strcpy(bufptr, "'"); if (!(res = PSQLexec(cmdbuf, false))) { free(cmdbuf); return fail_lo_xact("\\lo_import", own_transaction); } PQclear(res); free(cmdbuf); } if (!finish_lo_xact("\\lo_import", own_transaction)) return false; print_lo_result("lo_import %u", loid); sprintf(oidbuf, "%u", loid); SetVariable(pset.vars, "LASTOID", oidbuf); return true; }
/* * ProcessCopyResult: if command was a COPY FROM STDIN/TO STDOUT, handle it * * Note: Utility function for use by SendQuery() only. * * Returns true if the query executed successfully, false otherwise. */ static bool ProcessCopyResult(PGresult *results) { bool success = false; if (!results) return false; switch (PQresultStatus(results)) { case PGRES_TUPLES_OK: case PGRES_COMMAND_OK: case PGRES_EMPTY_QUERY: /* nothing to do here */ success = true; break; case PGRES_COPY_OUT: SetCancelConn(); success = handleCopyOut(pset.db, pset.queryFout); ResetCancelConn(); break; case PGRES_COPY_IN: SetCancelConn(); success = handleCopyIn(pset.db, pset.cur_cmd_source, PQbinaryTuples(results)); ResetCancelConn(); break; default: break; } /* may need this to recover from conn loss during COPY */ if (!CheckConnection()) return false; return success; }
/* * PSQLexec * * This is the way to send "backdoor" queries (those not directly entered * by the user). It is subject to -E but not -e. * * In autocommit-off mode, a new transaction block is started if start_xact * is true; nothing special is done when start_xact is false. Typically, * start_xact = false is used for SELECTs and explicit BEGIN/COMMIT commands. * * Note: we don't bother to check PQclientEncoding; it is assumed that no * caller uses this path to issue "SET CLIENT_ENCODING". */ PGresult * PSQLexec(const char *query, bool start_xact) { PGresult *res; int echo_hidden; if (!pset.db) { psql_error("You are currently not connected to a database.\n"); return NULL; } echo_hidden = SwitchVariable(pset.vars, "ECHO_HIDDEN", "noexec", NULL); if (echo_hidden != VAR_NOTSET) { printf("********* QUERY **********\n" "%s\n" "**************************\n\n", query); fflush(stdout); if (echo_hidden == 1) /* noexec? */ return NULL; } SetCancelConn(); if (start_xact && PQtransactionStatus(pset.db) == PQTRANS_IDLE && !GetVariableBool(pset.vars, "AUTOCOMMIT")) { res = PQexec(pset.db, "BEGIN"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { psql_error("%s", PQerrorMessage(pset.db)); PQclear(res); ResetCancelConn(); return NULL; } PQclear(res); } res = PQexec(pset.db, query); if (!AcceptResult(res) && res) { PQclear(res); res = NULL; } return res; }
/* * GetQueryResult * * Pump the conn till it's dry of results; return false if any are errors. * Note that this will block if the conn is busy. */ static bool GetQueryResult(PGconn *conn, const char *progname) { bool ok = true; PGresult *result; SetCancelConn(conn); while ((result = PQgetResult(conn)) != NULL) { if (!ProcessQueryResult(conn, result, progname)) ok = false; } ResetCancelConn(); return ok; }
/* * PSQLexec * * This is the way to send "backdoor" queries (those not directly entered * by the user). It is subject to -E but not -e. * * Caller is responsible for handling the ensuing processing if a COPY * command is sent. * * Note: we don't bother to check PQclientEncoding; it is assumed that no * caller uses this path to issue "SET CLIENT_ENCODING". */ PGresult * PSQLexec(const char *query) { PGresult *res; if (!pset.db) { psql_error("You are currently not connected to a database.\n"); return NULL; } if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF) { printf(_("********* QUERY **********\n" "%s\n" "**************************\n\n"), query); fflush(stdout); if (pset.logfile) { fprintf(pset.logfile, _("********* QUERY **********\n" "%s\n" "**************************\n\n"), query); fflush(pset.logfile); } if (pset.echo_hidden == PSQL_ECHO_HIDDEN_NOEXEC) return NULL; } SetCancelConn(); res = PQexec(pset.db, query); ResetCancelConn(); if (!AcceptResult(res)) { PQclear(res); res = NULL; } return res; }
/* * As above for a SQL maintenance command (returns command success). * Command is executed with a cancel handler set, so Ctrl-C can * interrupt it. */ bool executeMaintenanceCommand(PGconn *conn, const char *query, bool echo) { PGresult *res; bool r; if (echo) printf("%s\n", query); SetCancelConn(conn); res = PQexec(conn, query); ResetCancelConn(); r = (res && PQresultStatus(res) == PGRES_COMMAND_OK); if (res) PQclear(res); return r; }
/* * AcceptResult * * Checks whether a result is valid, giving an error message if necessary; * resets cancelConn as needed, and ensures that the connection to the backend * is still up. * * Returns true for valid result, false for error state. */ static bool AcceptResult(const PGresult *result) { bool OK = true; ResetCancelConn(); if (!result) OK = false; else switch (PQresultStatus(result)) { case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: case PGRES_EMPTY_QUERY: case PGRES_COPY_IN: /* Fine, do nothing */ break; case PGRES_COPY_OUT: /* keep cancel connection for copy out state */ SetCancelConn(); break; default: OK = false; break; } if (!OK) { psql_error("%s", PQerrorMessage(pset.db)); CheckConnection(); } return OK; }
/* * GetIdleSlot * Return a connection slot that is ready to execute a command. * * We return the first slot we find that is marked isFree, if one is; * otherwise, we loop on select() until one socket becomes available. When * this happens, we read the whole set and mark as free all sockets that become * available. * * Process the slot list, if any free slot is available then return the slotid * else perform the select on all the socket's and wait until at least one slot * becomes available. * * If an error occurs, NULL is returned. */ static ParallelSlot * GetIdleSlot(ParallelSlot slots[], int numslots, const char *dbname, const char *progname) { int i; int firstFree = -1; fd_set slotset; pgsocket maxFd; for (i = 0; i < numslots; i++) if ((slots + i)->isFree) return slots + i; FD_ZERO(&slotset); maxFd = slots->sock; for (i = 0; i < numslots; i++) { FD_SET((slots + i)->sock, &slotset); if ((slots + i)->sock > maxFd) maxFd = (slots + i)->sock; } /* * No free slot found, so wait until one of the connections has finished * its task and return the available slot. */ for (firstFree = -1; firstFree < 0;) { bool aborting; SetCancelConn(slots->connection); i = select_loop(maxFd, &slotset, &aborting); ResetCancelConn(); if (aborting) { /* * We set the cancel-receiving connection to the one in the zeroth * slot above, so fetch the error from there. */ GetQueryResult(slots->connection, dbname, progname); return NULL; } Assert(i != 0); for (i = 0; i < numslots; i++) { if (!FD_ISSET((slots + i)->sock, &slotset)) continue; PQconsumeInput((slots + i)->connection); if (PQisBusy((slots + i)->connection)) continue; (slots + i)->isFree = true; if (!GetQueryResult((slots + i)->connection, dbname, progname)) return NULL; if (firstFree < 0) firstFree = i; } } return slots + firstFree; }
/* * GetIdleSlot * Return a connection slot that is ready to execute a command. * * We return the first slot we find that is marked isFree, if one is; * otherwise, we loop on select() until one socket becomes available. When * this happens, we read the whole set and mark as free all sockets that become * available. * * If an error occurs, NULL is returned. */ static ParallelSlot * GetIdleSlot(ParallelSlot slots[], int numslots, const char *progname) { int i; int firstFree = -1; /* Any connection already known free? */ for (i = 0; i < numslots; i++) { if (slots[i].isFree) return slots + i; } /* * No free slot found, so wait until one of the connections has finished * its task and return the available slot. */ while (firstFree < 0) { fd_set slotset; int maxFd = 0; bool aborting; /* We must reconstruct the fd_set for each call to select_loop */ FD_ZERO(&slotset); for (i = 0; i < numslots; i++) { int sock = PQsocket(slots[i].connection); /* * We don't really expect any connections to lose their sockets * after startup, but just in case, cope by ignoring them. */ if (sock < 0) continue; FD_SET(sock, &slotset); if (sock > maxFd) maxFd = sock; } SetCancelConn(slots->connection); i = select_loop(maxFd, &slotset, &aborting); ResetCancelConn(); if (aborting) { /* * We set the cancel-receiving connection to the one in the zeroth * slot above, so fetch the error from there. */ GetQueryResult(slots->connection, progname); return NULL; } Assert(i != 0); for (i = 0; i < numslots; i++) { int sock = PQsocket(slots[i].connection); if (sock >= 0 && FD_ISSET(sock, &slotset)) { /* select() says input is available, so consume it */ PQconsumeInput(slots[i].connection); } /* Collect result(s) as long as any are available */ while (!PQisBusy(slots[i].connection)) { PGresult *result = PQgetResult(slots[i].connection); if (result != NULL) { /* Check and discard the command result */ if (!ProcessQueryResult(slots[i].connection, result, progname)) return NULL; } else { /* This connection has become idle */ slots[i].isFree = true; if (firstFree < 0) firstFree = i; break; } } } } return slots + firstFree; }
/* * Execute a \copy command (frontend copy). We have to open a file, then * submit a COPY query to the backend and either feed it data from the * file or route its response into the file. */ bool do_copy(const char *args) { struct pqbuf query; FILE* copystream; struct copy_options *options; PGresult* result; bool success; struct stat st; /* parse options */ options = parse_slash_copy(args); if (!options) return false; /* prepare to read or write the target file */ if (options->file) canonicalize_path(options->file); if (options->from) { if (options->file) copystream = fopen(options->file, PG_BINARY_R); else if (!options->psql_inout) copystream = pset.cur_cmd_source; else copystream = stdin; } else { if (options->file) copystream = fopen(options->file, PG_BINARY_W); else if (!options->psql_inout) copystream = pset.queryFout; else copystream = stdout; } if (!copystream) { psql_error("%s: %s\n", options->file, strerror(errno)); free_copy_options(options); return false; } /* make sure the specified file is not a directory */ fstat(fileno(copystream), &st); if (S_ISDIR(st.st_mode)) { fclose(copystream); psql_error("%s: cannot copy from/to a directory\n", options->file); free_copy_options(options); return false; } /* build the command we will send to the backend */ init_pqbuf(&query); print_pqbuf(&query, "COPY "); append_pqbuf_str(&query, options->before_tofrom); if (options->from) append_pqbuf(&query, " FROM STDIN "); else append_pqbuf(&query, " TO STDOUT "); if (options->after_tofrom) append_pqbuf_str(&query, options->after_tofrom); result = PSQLexec(query.data, true); term_pqbuf(&query); switch (PQresultStatus(result)) { case PGRES_COPY_OUT: SetCancelConn(); success = handleCopyOut(pset.db, copystream); ResetCancelConn(); break; case PGRES_COPY_IN: SetCancelConn(); success = handleCopyIn(pset.db, copystream, PQbinaryTuples(result)); ResetCancelConn(); break; case PGRES_NONFATAL_ERROR: case PGRES_FATAL_ERROR: case PGRES_BAD_RESPONSE: success = false; psql_error("\\copy: %s", PQerrorMessage(pset.db)); break; default: success = false; psql_error("\\copy: unexpected response (%d)\n", PQresultStatus(result)); break; } PQclear(result); /* * Make sure we have pumped libpq dry of results; else it may still be in * ASYNC_BUSY state, leading to false readings in, eg, get_prompt(). */ while ((result = PQgetResult(pset.db)) != NULL) { success = false; psql_error("\\copy: unexpected response (%d)\n", PQresultStatus(result)); /* if still in COPY IN state, try to get out of it */ if (PQresultStatus(result) == PGRES_COPY_IN) PQputCopyEnd(pset.db, _("trying to exit copy mode")); PQclear(result); } if (options->file != NULL) { if (fclose(copystream) != 0) { psql_error("%s: %s\n", options->file, strerror(errno)); success = false; } } free_copy_options(options); return success; }
/* * SendQuery: send the query string to the backend * (and print out results) * * Note: This is the "front door" way to send a query. That is, use it to * send queries actually entered by the user. These queries will be subject to * single step mode. * To send "back door" queries (generated by slash commands, etc.) in a * controlled way, use PSQLexec(). * * Returns true if the query executed successfully, false otherwise. */ bool SendQuery(const char *query) { PGresult *results; PGTransactionStatusType transaction_status; double elapsed_msec = 0; bool OK, on_error_rollback_savepoint = false; static bool on_error_rollback_warning = false; if (!pset.db) { psql_error("You are currently not connected to a database.\n"); return false; } if (pset.singlestep) { char buf[3]; printf(_("***(Single step mode: verify command)*******************************************\n" "%s\n" "***(press return to proceed or enter x and return to cancel)********************\n"), query); fflush(stdout); if (fgets(buf, sizeof(buf), stdin) != NULL) if (buf[0] == 'x') return false; } else if (pset.echo == PSQL_ECHO_QUERIES) { puts(query); fflush(stdout); } if (pset.logfile) { fprintf(pset.logfile, _("********* QUERY **********\n" "%s\n" "**************************\n\n"), query); fflush(pset.logfile); } SetCancelConn(); transaction_status = PQtransactionStatus(pset.db); if (transaction_status == PQTRANS_IDLE && !pset.autocommit && !command_no_begin(query)) { results = PQexec(pset.db, "BEGIN"); if (PQresultStatus(results) != PGRES_COMMAND_OK) { psql_error("%s", PQerrorMessage(pset.db)); PQclear(results); ResetCancelConn(); return false; } PQclear(results); transaction_status = PQtransactionStatus(pset.db); } if (transaction_status == PQTRANS_INTRANS && pset.on_error_rollback != PSQL_ERROR_ROLLBACK_OFF && (pset.cur_cmd_interactive || pset.on_error_rollback == PSQL_ERROR_ROLLBACK_ON)) { if (on_error_rollback_warning == false && pset.sversion < 80000) { fprintf(stderr, _("The server (version %d.%d) does not support savepoints for ON_ERROR_ROLLBACK.\n"), pset.sversion / 10000, (pset.sversion / 100) % 100); on_error_rollback_warning = true; } else { results = PQexec(pset.db, "SAVEPOINT pg_psql_temporary_savepoint"); if (PQresultStatus(results) != PGRES_COMMAND_OK) { psql_error("%s", PQerrorMessage(pset.db)); PQclear(results); ResetCancelConn(); return false; } PQclear(results); on_error_rollback_savepoint = true; } } if (pset.fetch_count <= 0 || !is_select_command(query)) { /* Default fetch-it-all-and-print mode */ instr_time before, after; if (pset.timing) INSTR_TIME_SET_CURRENT(before); results = PQexec(pset.db, query); /* these operations are included in the timing result: */ ResetCancelConn(); OK = (AcceptResult(results) && ProcessCopyResult(results)); if (pset.timing) { INSTR_TIME_SET_CURRENT(after); INSTR_TIME_SUBTRACT(after, before); elapsed_msec = INSTR_TIME_GET_MILLISEC(after); } /* but printing results isn't: */ if (OK) OK = PrintQueryResults(results); } else { /* Fetch-in-segments mode */ OK = ExecQueryUsingCursor(query, &elapsed_msec); ResetCancelConn(); results = NULL; /* PQclear(NULL) does nothing */ } /* If we made a temporary savepoint, possibly release/rollback */ if (on_error_rollback_savepoint) { const char *svptcmd; transaction_status = PQtransactionStatus(pset.db); if (transaction_status == PQTRANS_INERROR) { /* We always rollback on an error */ svptcmd = "ROLLBACK TO pg_psql_temporary_savepoint"; } else if (transaction_status != PQTRANS_INTRANS) { /* If they are no longer in a transaction, then do nothing */ svptcmd = NULL; } else { /* * Do nothing if they are messing with savepoints themselves: If * the user did RELEASE or ROLLBACK, our savepoint is gone. If * they issued a SAVEPOINT, releasing ours would remove theirs. */ if (results && (strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 || strcmp(PQcmdStatus(results), "RELEASE") == 0 || strcmp(PQcmdStatus(results), "ROLLBACK") == 0)) svptcmd = NULL; else svptcmd = "RELEASE pg_psql_temporary_savepoint"; } if (svptcmd) { PGresult *svptres; svptres = PQexec(pset.db, svptcmd); if (PQresultStatus(svptres) != PGRES_COMMAND_OK) { psql_error("%s", PQerrorMessage(pset.db)); PQclear(svptres); PQclear(results); ResetCancelConn(); return false; } PQclear(svptres); } } PQclear(results); /* Possible microtiming output */ if (OK && pset.timing) printf(_("Time: %.3f ms\n"), elapsed_msec); /* check for events that may occur during query execution */ if (pset.encoding != PQclientEncoding(pset.db) && PQclientEncoding(pset.db) >= 0) { /* track effects of SET CLIENT_ENCODING */ pset.encoding = PQclientEncoding(pset.db); pset.popt.topt.encoding = pset.encoding; SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding)); } PrintNotifications(); return OK; }
/* * SendQuery: send the query string to the backend * (and print out results) * * Note: This is the "front door" way to send a query. That is, use it to * send queries actually entered by the user. These queries will be subject to * single step mode. * To send "back door" queries (generated by slash commands, etc.) in a * controlled way, use PSQLexec(). * * Returns true if the query executed successfully, false otherwise. */ bool SendQuery(const char *query) { PGresult *results; PGTransactionStatusType transaction_status; double elapsed_msec = 0; bool OK = false; int i; bool on_error_rollback_savepoint = false; static bool on_error_rollback_warning = false; if (!pset.db) { psql_error("You are currently not connected to a database.\n"); goto sendquery_cleanup; } if (pset.singlestep) { char buf[3]; fflush(stderr); printf(_("***(Single step mode: verify command)*******************************************\n" "%s\n" "***(press return to proceed or enter x and return to cancel)********************\n"), query); fflush(stdout); if (fgets(buf, sizeof(buf), stdin) != NULL) if (buf[0] == 'x') goto sendquery_cleanup; if (cancel_pressed) goto sendquery_cleanup; } else if (pset.echo == PSQL_ECHO_QUERIES) { puts(query); fflush(stdout); } if (pset.logfile) { fprintf(pset.logfile, _("********* QUERY **********\n" "%s\n" "**************************\n\n"), query); fflush(pset.logfile); } SetCancelConn(); transaction_status = PQtransactionStatus(pset.db); if (transaction_status == PQTRANS_IDLE && !pset.autocommit && !command_no_begin(query)) { results = PQexec(pset.db, "BEGIN"); if (PQresultStatus(results) != PGRES_COMMAND_OK) { psql_error("%s", PQerrorMessage(pset.db)); ClearOrSaveResult(results); ResetCancelConn(); goto sendquery_cleanup; } ClearOrSaveResult(results); transaction_status = PQtransactionStatus(pset.db); } if (transaction_status == PQTRANS_INTRANS && pset.on_error_rollback != PSQL_ERROR_ROLLBACK_OFF && (pset.cur_cmd_interactive || pset.on_error_rollback == PSQL_ERROR_ROLLBACK_ON)) { if (on_error_rollback_warning == false && pset.sversion < 80000) { char sverbuf[32]; psql_error("The server (version %s) does not support savepoints for ON_ERROR_ROLLBACK.\n", formatPGVersionNumber(pset.sversion, false, sverbuf, sizeof(sverbuf))); on_error_rollback_warning = true; } else { results = PQexec(pset.db, "SAVEPOINT pg_psql_temporary_savepoint"); if (PQresultStatus(results) != PGRES_COMMAND_OK) { psql_error("%s", PQerrorMessage(pset.db)); ClearOrSaveResult(results); ResetCancelConn(); goto sendquery_cleanup; } ClearOrSaveResult(results); on_error_rollback_savepoint = true; } } if (pset.fetch_count <= 0 || pset.gexec_flag || pset.crosstab_flag || !is_select_command(query)) { /* Default fetch-it-all-and-print mode */ instr_time before, after; if (pset.timing) INSTR_TIME_SET_CURRENT(before); results = PQexec(pset.db, query); /* these operations are included in the timing result: */ ResetCancelConn(); OK = ProcessResult(&results); if (pset.timing) { INSTR_TIME_SET_CURRENT(after); INSTR_TIME_SUBTRACT(after, before); elapsed_msec = INSTR_TIME_GET_MILLISEC(after); } /* but printing results isn't: */ if (OK && results) OK = PrintQueryResults(results); } else { /* Fetch-in-segments mode */ OK = ExecQueryUsingCursor(query, &elapsed_msec); ResetCancelConn(); results = NULL; /* PQclear(NULL) does nothing */ } if (!OK && pset.echo == PSQL_ECHO_ERRORS) psql_error("STATEMENT: %s\n", query); /* If we made a temporary savepoint, possibly release/rollback */ if (on_error_rollback_savepoint) { const char *svptcmd = NULL; transaction_status = PQtransactionStatus(pset.db); switch (transaction_status) { case PQTRANS_INERROR: /* We always rollback on an error */ svptcmd = "ROLLBACK TO pg_psql_temporary_savepoint"; break; case PQTRANS_IDLE: /* If they are no longer in a transaction, then do nothing */ break; case PQTRANS_INTRANS: /* * Do nothing if they are messing with savepoints themselves: * If the user did RELEASE or ROLLBACK, our savepoint is gone. * If they issued a SAVEPOINT, releasing ours would remove * theirs. */ if (results && (strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 || strcmp(PQcmdStatus(results), "RELEASE") == 0 || strcmp(PQcmdStatus(results), "ROLLBACK") == 0)) svptcmd = NULL; else svptcmd = "RELEASE pg_psql_temporary_savepoint"; break; case PQTRANS_ACTIVE: case PQTRANS_UNKNOWN: default: OK = false; /* PQTRANS_UNKNOWN is expected given a broken connection. */ if (transaction_status != PQTRANS_UNKNOWN || ConnectionUp()) psql_error("unexpected transaction status (%d)\n", transaction_status); break; } if (svptcmd) { PGresult *svptres; svptres = PQexec(pset.db, svptcmd); if (PQresultStatus(svptres) != PGRES_COMMAND_OK) { psql_error("%s", PQerrorMessage(pset.db)); ClearOrSaveResult(svptres); OK = false; PQclear(results); ResetCancelConn(); goto sendquery_cleanup; } PQclear(svptres); } } ClearOrSaveResult(results); /* Possible microtiming output */ if (pset.timing) printf(_("Time: %.3f ms\n"), elapsed_msec); /* check for events that may occur during query execution */ if (pset.encoding != PQclientEncoding(pset.db) && PQclientEncoding(pset.db) >= 0) { /* track effects of SET CLIENT_ENCODING */ pset.encoding = PQclientEncoding(pset.db); pset.popt.topt.encoding = pset.encoding; SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding)); } PrintNotifications(); /* perform cleanup that should occur after any attempted query */ sendquery_cleanup: /* reset \g's output-to-filename trigger */ if (pset.gfname) { free(pset.gfname); pset.gfname = NULL; } /* reset \gset trigger */ if (pset.gset_prefix) { free(pset.gset_prefix); pset.gset_prefix = NULL; } /* reset \gexec trigger */ pset.gexec_flag = false; /* reset \crosstabview trigger */ pset.crosstab_flag = false; for (i = 0; i < lengthof(pset.ctv_args); i++) { pg_free(pset.ctv_args[i]); pset.ctv_args[i] = NULL; } return OK; }
/* * Execute a \copy command (frontend copy). We have to open a file, then * submit a COPY query to the backend and either feed it data from the * file or route its response into the file. */ bool do_copy(const char *args) { PQExpBufferData query; FILE *copystream; struct copy_options *options; PGresult *result; bool success; struct stat st; /* parse options */ options = parse_slash_copy(args); if (!options) return false; initPQExpBuffer(&query); printfPQExpBuffer(&query, "COPY "); appendPQExpBuffer(&query, "%s ", options->table); if (options->column_list) appendPQExpBuffer(&query, "%s ", options->column_list); if (options->from) appendPQExpBuffer(&query, "FROM STDIN"); else appendPQExpBuffer(&query, "TO STDOUT"); if (options->binary) appendPQExpBuffer(&query, " BINARY "); if (options->oids) appendPQExpBuffer(&query, " OIDS "); if (options->delim) emit_copy_option(&query, " DELIMITER ", options->delim); if (options->null) emit_copy_option(&query, " NULL AS ", options->null); if (options->csv_mode) appendPQExpBuffer(&query, " CSV"); if (options->header) appendPQExpBuffer(&query, " HEADER"); if (options->quote) emit_copy_option(&query, " QUOTE AS ", options->quote); if (options->escape) emit_copy_option(&query, " ESCAPE AS ", options->escape); if (options->force_quote_list) appendPQExpBuffer(&query, " FORCE QUOTE %s", options->force_quote_list); if (options->force_notnull_list) appendPQExpBuffer(&query, " FORCE NOT NULL %s", options->force_notnull_list); if (options->file) canonicalize_path(options->file); if (options->from) { if (options->file) copystream = fopen(options->file, PG_BINARY_R); else if (!options->psql_inout) copystream = pset.cur_cmd_source; else copystream = stdin; } else { if (options->file) copystream = fopen(options->file, options->binary ? PG_BINARY_W : "w"); else if (!options->psql_inout) copystream = pset.queryFout; else copystream = stdout; } if (!copystream) { psql_error("%s: %s\n", options->file, strerror(errno)); free_copy_options(options); return false; } /* make sure the specified file is not a directory */ fstat(fileno(copystream), &st); if (S_ISDIR(st.st_mode)) { fclose(copystream); psql_error("%s: cannot copy from/to a directory\n", options->file); free_copy_options(options); return false; } result = PSQLexec(query.data, true); termPQExpBuffer(&query); switch (PQresultStatus(result)) { case PGRES_COPY_OUT: SetCancelConn(); success = handleCopyOut(pset.db, copystream); ResetCancelConn(); break; case PGRES_COPY_IN: SetCancelConn(); success = handleCopyIn(pset.db, copystream, PQbinaryTuples(result)); ResetCancelConn(); break; case PGRES_NONFATAL_ERROR: case PGRES_FATAL_ERROR: case PGRES_BAD_RESPONSE: success = false; psql_error("\\copy: %s", PQerrorMessage(pset.db)); break; default: success = false; psql_error("\\copy: unexpected response (%d)\n", PQresultStatus(result)); break; } PQclear(result); /* * Make sure we have pumped libpq dry of results; else it may still be * in ASYNC_BUSY state, leading to false readings in, eg, get_prompt(). */ while ((result = PQgetResult(pset.db)) != NULL) { success = false; psql_error("\\copy: unexpected response (%d)\n", PQresultStatus(result)); /* if still in COPY IN state, try to get out of it */ if (PQresultStatus(result) == PGRES_COPY_IN) PQputCopyEnd(pset.db, _("trying to exit copy mode")); PQclear(result); } if (options->file != NULL) { if (fclose(copystream) != 0) { psql_error("%s: %s\n", options->file, strerror(errno)); success = false; } } free_copy_options(options); return success; }
/* * ProcessResult: utility function for use by SendQuery() only * * When our command string contained a COPY FROM STDIN or COPY TO STDOUT, * PQexec() has stopped at the PGresult associated with the first such * command. In that event, we'll marshal data for the COPY and then cycle * through any subsequent PGresult objects. * * When the command string contained no affected COPY command, this function * degenerates to an AcceptResult() call. * * Changes its argument to point to the last PGresult of the command string, * or NULL if that result was for a COPY FROM STDIN or COPY TO STDOUT. * * Returns true on complete success, false otherwise. Possible failure modes * include purely client-side problems; check the transaction status for the * server-side opinion. */ static bool ProcessResult(PGresult **results) { PGresult *next_result; bool success = true; bool first_cycle = true; do { ExecStatusType result_status; bool is_copy; if (!AcceptResult(*results)) { /* * Failure at this point is always a server-side failure or a * failure to submit the command string. Either way, we're * finished with this command string. */ success = false; break; } result_status = PQresultStatus(*results); switch (result_status) { case PGRES_EMPTY_QUERY: case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: is_copy = false; break; case PGRES_COPY_OUT: case PGRES_COPY_IN: is_copy = true; break; default: /* AcceptResult() should have caught anything else. */ is_copy = false; psql_error("unexpected PQresultStatus: %d\n", result_status); break; } if (is_copy) { /* * Marshal the COPY data. Either subroutine will get the * connection out of its COPY state, then call PQresultStatus() * once and report any error. */ SetCancelConn(); if (result_status == PGRES_COPY_OUT) success = handleCopyOut(pset.db, pset.queryFout) && success; else success = handleCopyIn(pset.db, pset.cur_cmd_source, PQbinaryTuples(*results)) && success; ResetCancelConn(); /* * Call PQgetResult() once more. In the typical case of a * single-command string, it will return NULL. Otherwise, we'll * have other results to process that may include other COPYs. */ PQclear(*results); *results = next_result = PQgetResult(pset.db); } else if (first_cycle) /* fast path: no COPY commands; PQexec visited all results */ break; else if ((next_result = PQgetResult(pset.db))) { /* non-COPY command(s) after a COPY: keep the last one */ PQclear(*results); *results = next_result; } first_cycle = false; } while (next_result); /* may need this to recover from conn loss during COPY */ if (!first_cycle && !CheckConnection()) return false; return success; }
/* * PSQLexecWatch * * This function is used for \watch command to send the query to * the server and print out the results. * * Returns 1 if the query executed successfully, 0 if it cannot be repeated, * e.g., because of the interrupt, -1 on error. */ int PSQLexecWatch(const char *query, const printQueryOpt *opt) { PGresult *res; double elapsed_msec = 0; instr_time before; instr_time after; if (!pset.db) { psql_error("You are currently not connected to a database.\n"); return 0; } SetCancelConn(); if (pset.timing) INSTR_TIME_SET_CURRENT(before); res = PQexec(pset.db, query); ResetCancelConn(); if (!AcceptResult(res)) { PQclear(res); return 0; } if (pset.timing) { INSTR_TIME_SET_CURRENT(after); INSTR_TIME_SUBTRACT(after, before); elapsed_msec = INSTR_TIME_GET_MILLISEC(after); } /* * If SIGINT is sent while the query is processing, the interrupt * will be consumed. The user's intention, though, is to cancel * the entire watch process, so detect a sent cancellation request and * exit in this case. */ if (cancel_pressed) { PQclear(res); return 0; } switch (PQresultStatus(res)) { case PGRES_TUPLES_OK: printQuery(res, opt, pset.queryFout, pset.logfile); break; case PGRES_COMMAND_OK: fprintf(pset.queryFout, "%s\n%s\n\n", opt->title, PQcmdStatus(res)); break; case PGRES_EMPTY_QUERY: psql_error(_("\\watch cannot be used with an empty query\n")); PQclear(res); return -1; case PGRES_COPY_OUT: case PGRES_COPY_IN: case PGRES_COPY_BOTH: psql_error(_("\\watch cannot be used with COPY\n")); PQclear(res); return -1; default: psql_error(_("unexpected result status for \\watch\n")); PQclear(res); return -1; } PQclear(res); fflush(pset.queryFout); /* Possible microtiming output */ if (pset.timing) printf(_("Time: %.3f ms\n"), elapsed_msec); return 1; }
/* * ProcessResult: utility function for use by SendQuery() only * * When our command string contained a COPY FROM STDIN or COPY TO STDOUT, * PQexec() has stopped at the PGresult associated with the first such * command. In that event, we'll marshal data for the COPY and then cycle * through any subsequent PGresult objects. * * When the command string contained no such COPY command, this function * degenerates to an AcceptResult() call. * * Changes its argument to point to the last PGresult of the command string, * or NULL if that result was for a COPY TO STDOUT. (Returning NULL prevents * the command status from being printed, which we want in that case so that * the status line doesn't get taken as part of the COPY data.) * * Returns true on complete success, false otherwise. Possible failure modes * include purely client-side problems; check the transaction status for the * server-side opinion. */ static bool ProcessResult(PGresult **results) { bool success = true; bool first_cycle = true; for (;;) { ExecStatusType result_status; bool is_copy; PGresult *next_result; if (!AcceptResult(*results)) { /* * Failure at this point is always a server-side failure or a * failure to submit the command string. Either way, we're * finished with this command string. */ success = false; break; } result_status = PQresultStatus(*results); switch (result_status) { case PGRES_EMPTY_QUERY: case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: is_copy = false; break; case PGRES_COPY_OUT: case PGRES_COPY_IN: is_copy = true; break; default: /* AcceptResult() should have caught anything else. */ is_copy = false; psql_error("unexpected PQresultStatus: %d\n", result_status); break; } if (is_copy) { /* * Marshal the COPY data. Either subroutine will get the * connection out of its COPY state, then call PQresultStatus() * once and report any error. * * If pset.copyStream is set, use that as data source/sink, * otherwise use queryFout or cur_cmd_source as appropriate. */ FILE *copystream = pset.copyStream; PGresult *copy_result; SetCancelConn(); if (result_status == PGRES_COPY_OUT) { if (!copystream) copystream = pset.queryFout; success = handleCopyOut(pset.db, copystream, ©_result) && success; /* * Suppress status printing if the report would go to the same * place as the COPY data just went. Note this doesn't * prevent error reporting, since handleCopyOut did that. */ if (copystream == pset.queryFout) { PQclear(copy_result); copy_result = NULL; } } else { if (!copystream) copystream = pset.cur_cmd_source; success = handleCopyIn(pset.db, copystream, PQbinaryTuples(*results), ©_result) && success; } ResetCancelConn(); /* * Replace the PGRES_COPY_OUT/IN result with COPY command's exit * status, or with NULL if we want to suppress printing anything. */ PQclear(*results); *results = copy_result; } else if (first_cycle) { /* fast path: no COPY commands; PQexec visited all results */ break; } /* * Check PQgetResult() again. In the typical case of a single-command * string, it will return NULL. Otherwise, we'll have other results * to process that may include other COPYs. We keep the last result. */ next_result = PQgetResult(pset.db); if (!next_result) break; PQclear(*results); *results = next_result; first_cycle = false; } /* may need this to recover from conn loss during COPY */ if (!first_cycle && !CheckConnection()) return false; return success; }