aboutsummaryrefslogtreecommitdiff
path: root/src/bin/psql/common.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/psql/common.c')
-rw-r--r--src/bin/psql/common.c298
1 files changed, 274 insertions, 24 deletions
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 3e9d23d1956..87e5ebc1fdd 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -3,7 +3,7 @@
*
* Copyright (c) 2000-2006, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.126 2006/08/29 15:19:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.127 2006/08/29 22:25:07 tgl Exp $
*/
#include "postgres_fe.h"
#include "common.h"
@@ -28,6 +28,7 @@
#include "command.h"
#include "copy.h"
#include "mb/pg_wchar.h"
+#include "mbprint.h"
/* Workarounds for Windows */
@@ -53,7 +54,9 @@ typedef struct _timeb TimevalStruct;
#endif
+static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
static bool command_no_begin(const char *query);
+static bool is_select_command(const char *query);
/*
* "Safe" wrapper around strdup()
@@ -450,18 +453,15 @@ ResetCancelConn(void)
* 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.
+ * 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, const char *query)
+AcceptResult(const PGresult *result)
{
bool OK = true;
- ResetCancelConn();
-
if (!result)
OK = false;
else
@@ -560,7 +560,9 @@ PSQLexec(const char *query, bool start_xact)
res = PQexec(pset.db, query);
- if (!AcceptResult(res, query) && res)
+ ResetCancelConn();
+
+ if (!AcceptResult(res))
{
PQclear(res);
res = NULL;
@@ -602,6 +604,7 @@ PrintQueryTuples(const PGresult *results)
/* write output to \g argument, if any */
if (pset.gfname)
{
+ /* keep this code in sync with ExecQueryUsingCursor */
FILE *queryFout_copy = pset.queryFout;
bool queryFoutPipe_copy = pset.queryFoutPipe;
@@ -782,11 +785,10 @@ bool
SendQuery(const char *query)
{
PGresult *results;
- TimevalStruct before,
- after;
+ PGTransactionStatusType transaction_status;
+ double elapsed_msec = 0;
bool OK,
on_error_rollback_savepoint = false;
- PGTransactionStatusType transaction_status;
static bool on_error_rollback_warning = false;
if (!pset.db)
@@ -869,20 +871,38 @@ SendQuery(const char *query)
}
}
- if (pset.timing)
- GETTIMEOFDAY(&before);
+ if (pset.fetch_count <= 0 || !is_select_command(query))
+ {
+ /* Default fetch-it-all-and-print mode */
+ TimevalStruct before,
+ after;
- results = PQexec(pset.db, query);
+ if (pset.timing)
+ GETTIMEOFDAY(&before);
- /* these operations are included in the timing result: */
- OK = (AcceptResult(results, query) && ProcessCopyResult(results));
+ results = PQexec(pset.db, query);
- if (pset.timing)
- GETTIMEOFDAY(&after);
+ /* these operations are included in the timing result: */
+ ResetCancelConn();
+ OK = (AcceptResult(results) && ProcessCopyResult(results));
- /* but printing results isn't: */
- if (OK)
- OK = PrintQueryResults(results);
+ if (pset.timing)
+ {
+ GETTIMEOFDAY(&after);
+ elapsed_msec = DIFF_MSEC(&after, &before);
+ }
+
+ /* 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)
@@ -904,9 +924,10 @@ SendQuery(const char *query)
* the user did RELEASE or ROLLBACK, our savepoint is gone. If
* they issued a SAVEPOINT, releasing ours would remove theirs.
*/
- if (strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 ||
- strcmp(PQcmdStatus(results), "RELEASE") == 0 ||
- strcmp(PQcmdStatus(results), "ROLLBACK") == 0)
+ if (results &&
+ (strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 ||
+ strcmp(PQcmdStatus(results), "RELEASE") == 0 ||
+ strcmp(PQcmdStatus(results), "ROLLBACK") == 0))
svptres = NULL;
else
svptres = PQexec(pset.db, "RELEASE pg_psql_temporary_savepoint");
@@ -927,7 +948,7 @@ SendQuery(const char *query)
/* Possible microtiming output */
if (OK && pset.timing)
- printf(_("Time: %.3f ms\n"), DIFF_MSEC(&after, &before));
+ printf(_("Time: %.3f ms\n"), elapsed_msec);
/* check for events that may occur during query execution */
@@ -948,6 +969,198 @@ SendQuery(const char *query)
/*
+ * ExecQueryUsingCursor: run a SELECT-like query using a cursor
+ *
+ * This feature allows result sets larger than RAM to be dealt with.
+ *
+ * Returns true if the query executed successfully, false otherwise.
+ *
+ * If pset.timing is on, total query time (exclusive of result-printing) is
+ * stored into *elapsed_msec.
+ */
+static bool
+ExecQueryUsingCursor(const char *query, double *elapsed_msec)
+{
+ bool OK = true;
+ PGresult *results;
+ PQExpBufferData buf;
+ printQueryOpt my_popt = pset.popt;
+ FILE *queryFout_copy = pset.queryFout;
+ bool queryFoutPipe_copy = pset.queryFoutPipe;
+ bool started_txn = false;
+ bool did_pager = false;
+ int ntuples;
+ char fetch_cmd[64];
+ TimevalStruct before,
+ after;
+
+ *elapsed_msec = 0;
+
+ /* initialize print options for partial table output */
+ my_popt.topt.start_table = true;
+ my_popt.topt.stop_table = false;
+ my_popt.topt.prior_records = 0;
+
+ if (pset.timing)
+ GETTIMEOFDAY(&before);
+
+ /* if we're not in a transaction, start one */
+ if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
+ {
+ results = PQexec(pset.db, "BEGIN");
+ OK = AcceptResult(results) &&
+ (PQresultStatus(results) == PGRES_COMMAND_OK);
+ PQclear(results);
+ if (!OK)
+ return false;
+ started_txn = true;
+ }
+
+ /* Send DECLARE CURSOR */
+ initPQExpBuffer(&buf);
+ appendPQExpBuffer(&buf, "DECLARE _psql_cursor NO SCROLL CURSOR FOR\n%s",
+ query);
+
+ results = PQexec(pset.db, buf.data);
+ OK = AcceptResult(results) &&
+ (PQresultStatus(results) == PGRES_COMMAND_OK);
+ PQclear(results);
+ termPQExpBuffer(&buf);
+ if (!OK)
+ goto cleanup;
+
+ if (pset.timing)
+ {
+ GETTIMEOFDAY(&after);
+ *elapsed_msec += DIFF_MSEC(&after, &before);
+ }
+
+ snprintf(fetch_cmd, sizeof(fetch_cmd),
+ "FETCH FORWARD %d FROM _psql_cursor",
+ pset.fetch_count);
+
+ /* prepare to write output to \g argument, if any */
+ if (pset.gfname)
+ {
+ /* keep this code in sync with PrintQueryTuples */
+ pset.queryFout = stdout; /* so it doesn't get closed */
+
+ /* open file/pipe */
+ if (!setQFout(pset.gfname))
+ {
+ pset.queryFout = queryFout_copy;
+ pset.queryFoutPipe = queryFoutPipe_copy;
+ OK = false;
+ goto cleanup;
+ }
+ }
+
+ for (;;)
+ {
+ if (pset.timing)
+ GETTIMEOFDAY(&before);
+
+ /* get FETCH_COUNT tuples at a time */
+ results = PQexec(pset.db, fetch_cmd);
+ OK = AcceptResult(results) &&
+ (PQresultStatus(results) == PGRES_TUPLES_OK);
+
+ if (pset.timing)
+ {
+ GETTIMEOFDAY(&after);
+ *elapsed_msec += DIFF_MSEC(&after, &before);
+ }
+
+ if (!OK)
+ {
+ PQclear(results);
+ break;
+ }
+
+ ntuples = PQntuples(results);
+
+ if (ntuples < pset.fetch_count)
+ {
+ /* this is the last result set, so allow footer decoration */
+ my_popt.topt.stop_table = true;
+ }
+ else if (pset.queryFout == stdout && !did_pager)
+ {
+ /*
+ * If query requires multiple result sets, hack to ensure that
+ * only one pager instance is used for the whole mess
+ */
+ pset.queryFout = PageOutput(100000, my_popt.topt.pager);
+ did_pager = true;
+ }
+
+ printQuery(results, &my_popt, pset.queryFout, pset.logfile);
+
+ /* after the first result set, disallow header decoration */
+ my_popt.topt.start_table = false;
+ my_popt.topt.prior_records += ntuples;
+
+ PQclear(results);
+
+ if (ntuples < pset.fetch_count || cancel_pressed)
+ break;
+ }
+
+ /* close \g argument file/pipe, restore old setting */
+ if (pset.gfname)
+ {
+ /* keep this code in sync with PrintQueryTuples */
+ setQFout(NULL);
+
+ pset.queryFout = queryFout_copy;
+ pset.queryFoutPipe = queryFoutPipe_copy;
+
+ free(pset.gfname);
+ pset.gfname = NULL;
+ }
+ else if (did_pager)
+ {
+ ClosePager(pset.queryFout);
+ pset.queryFout = queryFout_copy;
+ pset.queryFoutPipe = queryFoutPipe_copy;
+ }
+
+cleanup:
+ if (pset.timing)
+ GETTIMEOFDAY(&before);
+
+ /*
+ * We try to close the cursor on either success or failure, but on
+ * failure ignore the result (it's probably just a bleat about
+ * being in an aborted transaction)
+ */
+ results = PQexec(pset.db, "CLOSE _psql_cursor");
+ if (OK)
+ {
+ OK = AcceptResult(results) &&
+ (PQresultStatus(results) == PGRES_COMMAND_OK);
+ }
+ PQclear(results);
+
+ if (started_txn)
+ {
+ results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
+ OK &= AcceptResult(results) &&
+ (PQresultStatus(results) == PGRES_COMMAND_OK);
+ PQclear(results);
+ }
+
+ if (pset.timing)
+ {
+ GETTIMEOFDAY(&after);
+ *elapsed_msec += DIFF_MSEC(&after, &before);
+ }
+
+ return OK;
+}
+
+
+/*
* Advance the given char pointer over white space and SQL comments.
*/
static const char *
@@ -1159,6 +1372,43 @@ command_no_begin(const char *query)
/*
+ * Check whether the specified command is a SELECT (or VALUES).
+ */
+static bool
+is_select_command(const char *query)
+{
+ int wordlen;
+
+ /*
+ * First advance over any whitespace, comments and left parentheses.
+ */
+ for (;;)
+ {
+ query = skip_white_space(query);
+ if (query[0] == '(')
+ query++;
+ else
+ break;
+ }
+
+ /*
+ * Check word length (since "selectx" is not "select").
+ */
+ wordlen = 0;
+ while (isalpha((unsigned char) query[wordlen]))
+ wordlen += PQmblen(&query[wordlen], pset.encoding);
+
+ if (wordlen == 6 && pg_strncasecmp(query, "select", 6) == 0)
+ return true;
+
+ if (wordlen == 6 && pg_strncasecmp(query, "values", 6) == 0)
+ return true;
+
+ return false;
+}
+
+
+/*
* Test if the current user is a database superuser.
*
* Note: this will correctly detect superuserness only with a protocol-3.0