</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>ERROR</varname></term>
+ <listitem>
+ <para>
+ <literal>true</> if the last SQL query failed, <literal>false</> if
+ it succeeded. See also <varname>SQLSTATE</>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>FETCH_COUNT</varname></term>
<listitem>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>LAST_ERROR_MESSAGE</varname></term>
+ <term><varname>LAST_ERROR_SQLSTATE</varname></term>
+ <listitem>
+ <para>
+ The primary error message and associated SQLSTATE code for the most
+ recent failed query in the current <application>psql</> session, or
+ an empty string and <literal>00000</> if no error has occurred in
+ the current session.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term>
<varname>ON_ERROR_ROLLBACK</varname>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>ROW_COUNT</varname></term>
+ <listitem>
+ <para>
+ The number of rows returned or affected by the last SQL query, or 0
+ if the query failed or did not report a row count.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>SERVER_VERSION_NAME</varname></term>
<term><varname>SERVER_VERSION_NUM</varname></term>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>SQLSTATE</varname></term>
+ <listitem>
+ <para>
+ The error code (see <xref linkend="errcodes-appendix">) associated
+ with the last SQL query's failure, or <literal>00000</> if it
+ succeeded.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>USER</varname></term>
<listitem>
}
+/*
+ * Set special variables from a query result
+ * - ERROR: true/false, whether an error occurred on this query
+ * - SQLSTATE: code of error, or "00000" if no error, or "" if unknown
+ * - ROW_COUNT: how many rows were returned or affected, or "0"
+ * - LAST_ERROR_SQLSTATE: same for last error
+ * - LAST_ERROR_MESSAGE: message of last error
+ *
+ * Note: current policy is to apply this only to the results of queries
+ * entered by the user, not queries generated by slash commands.
+ */
+static void
+SetResultVariables(PGresult *results, bool success)
+{
+ if (success)
+ {
+ const char *ntuples = PQcmdTuples(results);
+
+ SetVariable(pset.vars, "ERROR", "false");
+ SetVariable(pset.vars, "SQLSTATE", "00000");
+ SetVariable(pset.vars, "ROW_COUNT", *ntuples ? ntuples : "0");
+ }
+ else
+ {
+ const char *code = PQresultErrorField(results, PG_DIAG_SQLSTATE);
+ const char *mesg = PQresultErrorField(results, PG_DIAG_MESSAGE_PRIMARY);
+
+ SetVariable(pset.vars, "ERROR", "true");
+
+ /*
+ * If there is no SQLSTATE code, use an empty string. This can happen
+ * for libpq-detected errors (e.g., lost connection, ENOMEM).
+ */
+ if (code == NULL)
+ code = "";
+ SetVariable(pset.vars, "SQLSTATE", code);
+ SetVariable(pset.vars, "ROW_COUNT", "0");
+ SetVariable(pset.vars, "LAST_ERROR_SQLSTATE", code);
+ SetVariable(pset.vars, "LAST_ERROR_MESSAGE", mesg ? mesg : "");
+ }
+}
+
+
/*
* ClearOrSaveResult
*
* If the result represents an error, remember it for possible display by
* \errverbose. Otherwise, just PQclear() it.
+ *
+ * Note: current policy is to apply this to the results of all queries,
+ * including "back door" queries, for debugging's sake. It's OK to use
+ * PQclear() directly on results known to not be error results, however.
*/
static void
ClearOrSaveResult(PGresult *result)
first_cycle = false;
}
+ SetResultVariables(*results, success);
+
/* may need this to recover from conn loss during COPY */
if (!first_cycle && !CheckConnection())
return false;
if (PQresultStatus(results) != PGRES_COMMAND_OK)
{
psql_error("%s", PQerrorMessage(pset.db));
+ SetResultVariables(results, false);
ClearOrSaveResult(results);
return false;
}
_("The command has no result, or the result has no columns.\n"));
}
+ SetResultVariables(results, OK);
ClearOrSaveResult(results);
return OK;
bool is_pipe;
bool is_pager = false;
bool started_txn = false;
+ int64 total_tuples = 0;
int ntuples;
int fetch_count;
char fetch_cmd[64];
results = PQexec(pset.db, buf.data);
OK = AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK);
+ if (!OK)
+ SetResultVariables(results, OK);
ClearOrSaveResult(results);
termPQExpBuffer(&buf);
if (!OK)
OK = AcceptResult(results);
Assert(!OK);
+ SetResultVariables(results, OK);
ClearOrSaveResult(results);
break;
}
*/
ntuples = PQntuples(results);
+ total_tuples += ntuples;
if (ntuples < fetch_count)
{
ClosePager(fout);
}
+ if (OK)
+ {
+ /*
+ * We don't have a PGresult here, and even if we did it wouldn't have
+ * the right row count, so fake SetResultVariables(). In error cases,
+ * we already set the result variables above.
+ */
+ char buf[32];
+
+ SetVariable(pset.vars, "ERROR", "false");
+ SetVariable(pset.vars, "SQLSTATE", "00000");
+ snprintf(buf, sizeof(buf), INT64_FORMAT, total_tuples);
+ SetVariable(pset.vars, "ROW_COUNT", buf);
+ }
+
cleanup:
if (pset.timing)
INSTR_TIME_SET_CURRENT(before);
* Windows builds currently print one more line than non-Windows builds.
* Using the larger number is fine.
*/
- output = PageOutput(147, pager ? &(pset.popt.topt) : NULL);
+ output = PageOutput(156, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("List of specially treated variables\n\n"));
" if set to \"noexec\", just show them without execution\n"));
fprintf(output, _(" ENCODING\n"
" current client character set encoding\n"));
+ fprintf(output, _(" ERROR\n"
+ " true if last query failed, else false\n"));
fprintf(output, _(" FETCH_COUNT\n"
" the number of result rows to fetch and display at a time (0 = unlimited)\n"));
fprintf(output, _(" HISTCONTROL\n"
" number of EOFs needed to terminate an interactive session\n"));
fprintf(output, _(" LASTOID\n"
" value of the last affected OID\n"));
+ fprintf(output, _(" LAST_ERROR_MESSAGE\n"
+ " LAST_ERROR_SQLSTATE\n"
+ " message and SQLSTATE of last error, or empty string and \"00000\" if none\n"));
fprintf(output, _(" ON_ERROR_ROLLBACK\n"
" if set, an error doesn't stop a transaction (uses implicit savepoints)\n"));
fprintf(output, _(" ON_ERROR_STOP\n"
" specifies the prompt used during COPY ... FROM STDIN\n"));
fprintf(output, _(" QUIET\n"
" run quietly (same as -q option)\n"));
+ fprintf(output, _(" ROW_COUNT\n"
+ " number of rows returned or affected by last query, or 0\n"));
fprintf(output, _(" SERVER_VERSION_NAME\n"
" SERVER_VERSION_NUM\n"
" server's version (in short string or numeric format)\n"));
" if set, end of line terminates SQL commands (same as -S option)\n"));
fprintf(output, _(" SINGLESTEP\n"
" single-step mode (same as -s option)\n"));
+ fprintf(output, _(" SQLSTATE\n"
+ " SQLSTATE of last query, or \"00000\" if no error\n"));
fprintf(output, _(" USER\n"
" the currently connected database user\n"));
fprintf(output, _(" VERBOSITY\n"
SetVariable(pset.vars, "VERSION_NAME", PG_VERSION);
SetVariable(pset.vars, "VERSION_NUM", CppAsString2(PG_VERSION_NUM));
+ /* Initialize variables for last error */
+ SetVariable(pset.vars, "LAST_ERROR_MESSAGE", "");
+ SetVariable(pset.vars, "LAST_ERROR_SQLSTATE", "00000");
+
/* Default values for variables (that don't match the result of \unset) */
SetVariableBool(pset.vars, "AUTOCOMMIT");
SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1);
UNION SELECT 4
UNION SELECT 5
ORDER BY 1;
+-- tests for special result variables
+-- working query, 2 rows selected
+SELECT 1 AS stuff UNION SELECT 2;
+ stuff
+-------
+ 1
+ 2
+(2 rows)
+
+\echo 'error:' :ERROR
+error: false
+\echo 'error code:' :SQLSTATE
+error code: 00000
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 2
+-- syntax error
+SELECT 1 UNION;
+ERROR: syntax error at or near ";"
+LINE 1: SELECT 1 UNION;
+ ^
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 42601
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 0
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: syntax error at or near ";"
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+last error code: 42601
+-- empty query
+;
+\echo 'error:' :ERROR
+error: false
+\echo 'error code:' :SQLSTATE
+error code: 00000
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 0
+-- must have kept previous values
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: syntax error at or near ";"
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+last error code: 42601
+-- other query error
+DROP TABLE this_table_does_not_exist;
+ERROR: table "this_table_does_not_exist" does not exist
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 42P01
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 0
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: table "this_table_does_not_exist" does not exist
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+last error code: 42P01
+-- working \gdesc
+SELECT 3 AS three, 4 AS four \gdesc
+ Column | Type
+--------+---------
+ three | integer
+ four | integer
+(2 rows)
+
+\echo 'error:' :ERROR
+error: false
+\echo 'error code:' :SQLSTATE
+error code: 00000
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 2
+-- \gdesc with an error
+SELECT 4 AS \gdesc
+ERROR: syntax error at end of input
+LINE 1: SELECT 4 AS
+ ^
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 42601
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 0
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: syntax error at end of input
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+last error code: 42601
+-- check row count for a cursor-fetched query
+\set FETCH_COUNT 10
+select unique2 from tenk1 limit 19;
+ unique2
+---------
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+(19 rows)
+
+\echo 'error:' :ERROR
+error: false
+\echo 'error code:' :SQLSTATE
+error code: 00000
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 19
+-- cursor-fetched query with an error
+select 1/unique1 from tenk1;
+ERROR: division by zero
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 22012
+\echo 'number of rows:' :ROW_COUNT
+number of rows: 0
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: division by zero
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+last error code: 22012
+\unset FETCH_COUNT
ORDER BY 1;
\r
\p
+
+-- tests for special result variables
+
+-- working query, 2 rows selected
+SELECT 1 AS stuff UNION SELECT 2;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+
+-- syntax error
+SELECT 1 UNION;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+
+-- empty query
+;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+-- must have kept previous values
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+
+-- other query error
+DROP TABLE this_table_does_not_exist;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+
+-- working \gdesc
+SELECT 3 AS three, 4 AS four \gdesc
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+
+-- \gdesc with an error
+SELECT 4 AS \gdesc
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+
+-- check row count for a cursor-fetched query
+\set FETCH_COUNT 10
+select unique2 from tenk1 limit 19;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+
+-- cursor-fetched query with an error
+select 1/unique1 from tenk1;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'number of rows:' :ROW_COUNT
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+\echo 'last error code:' :LAST_ERROR_SQLSTATE
+
+\unset FETCH_COUNT