Commit 35f49941 authored by Tom Lane's avatar Tom Lane

Fix plperl and pltcl error handling per my previous proposal. SPI

operations are now run as subtransactions, so that errors in them
can be reported as ordinary Perl or Tcl errors and caught by the
normal error handling convention of those languages.  Also do some
minor code cleanup in pltcl.c: extract a large chunk of duplicated
code in pltcl_SPI_execute and pltcl_SPI_execute_plan into a shared
subroutine.
parent a3b663df
<!-- <!--
$PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.31 2004/11/19 23:22:54 tgl Exp $ $PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.32 2004/11/21 21:17:01 tgl Exp $
--> -->
<chapter id="plperl"> <chapter id="plperl">
...@@ -219,9 +219,13 @@ $nrows = $rv-&gt;{processed}; ...@@ -219,9 +219,13 @@ $nrows = $rv-&gt;{processed};
Emit a log or error message. Possible levels are Emit a log or error message. Possible levels are
<literal>DEBUG</>, <literal>LOG</>, <literal>INFO</>, <literal>DEBUG</>, <literal>LOG</>, <literal>INFO</>,
<literal>NOTICE</>, <literal>WARNING</>, and <literal>ERROR</>. <literal>NOTICE</>, <literal>WARNING</>, and <literal>ERROR</>.
<literal>ERROR</> raises an error condition: further execution <literal>ERROR</>
of the function is abandoned, and the current transaction is raises an error condition; if this is not trapped by the surrounding
aborted. Perl code, the error propagates out to the calling query, causing
the current transaction or subtransaction to be aborted. This
is effectively the same as the Perl <literal>die</> command.
The other levels simply report the message to the system log
and/or client.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
<!-- <!--
$PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.31 2004/09/20 22:48:25 tgl Exp $ $PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.32 2004/11/21 21:17:02 tgl Exp $
--> -->
<chapter id="pltcl"> <chapter id="pltcl">
...@@ -449,17 +449,19 @@ SELECT 'doesn''t' AS ret ...@@ -449,17 +449,19 @@ SELECT 'doesn''t' AS ret
<term><function>elog</> <replaceable>level</replaceable> <replaceable>msg</replaceable></term> <term><function>elog</> <replaceable>level</replaceable> <replaceable>msg</replaceable></term>
<listitem> <listitem>
<para> <para>
Emits a log or error message. Possible levels are Emits a log or error message. Possible levels are
<literal>DEBUG</>, <literal>LOG</>, <literal>INFO</>, <literal>DEBUG</>, <literal>LOG</>, <literal>INFO</>,
<literal>NOTICE</>, <literal>WARNING</>, <literal>ERROR</>, and <literal>NOTICE</>, <literal>WARNING</>, <literal>ERROR</>, and
<literal>FATAL</>. Most simply emit the given message just like <literal>FATAL</>. Most simply emit the given message just like
the <literal>elog</> C function. <literal>ERROR</> the <literal>elog</> C function. <literal>ERROR</>
raises an error condition: further execution of the function is raises an error condition; if this is not trapped by the surrounding
abandoned, and the current transaction is aborted. Tcl code, the error propagates out to the calling query, causing
<literal>FATAL</> aborts the transaction and causes the current the current transaction or subtransaction to be aborted. This
session to shut down. (There is probably no good reason to use is effectively the same as the Tcl <literal>error</> command.
this error level in PL/Tcl functions, but it's provided for <literal>FATAL</> aborts the transaction and causes the current
completeness.) session to shut down. (There is probably no good reason to use
this error level in PL/Tcl functions, but it's provided for
completeness.)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
<!-- <!--
$PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.309 2004/11/20 21:44:24 tgl Exp $ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.310 2004/11/21 21:17:02 tgl Exp $
--> -->
<appendix id="release"> <appendix id="release">
...@@ -1686,6 +1686,15 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.309 2004/11/20 21:44:24 tgl Exp ...@@ -1686,6 +1686,15 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.309 2004/11/20 21:44:24 tgl Exp
</para> </para>
</listitem> </listitem>
<listitem>
<para>
In PL/Tcl, SPI commands are now run in subtransactions. If an error
occurs, the subtransaction is cleaned up and the error is reported
as an ordinary Tcl error, which can be trapped with <literal>catch</>.
Formerly, it was not possible to catch such errors.
</para>
</listitem>
</itemizedlist> </itemizedlist>
</sect3> </sect3>
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
* ENHANCEMENTS, OR MODIFICATIONS. * ENHANCEMENTS, OR MODIFICATIONS.
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.59 2004/11/20 19:07:40 tgl Exp $ * $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.60 2004/11/21 21:17:03 tgl Exp $
* *
**********************************************************************/ **********************************************************************/
...@@ -1593,20 +1593,79 @@ plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc) ...@@ -1593,20 +1593,79 @@ plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
} }
/*
* Implementation of spi_exec_query() Perl function
*/
HV * HV *
plperl_spi_exec(char *query, int limit) plperl_spi_exec(char *query, int limit)
{ {
HV *ret_hv; HV *ret_hv;
int spi_rv;
spi_rv = SPI_execute(query, plperl_current_prodesc->fn_readonly, limit); /*
ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed, spi_rv); * Execute the query inside a sub-transaction, so we can cope with
* errors sanely
*/
MemoryContext oldcontext = CurrentMemoryContext;
ResourceOwner oldowner = CurrentResourceOwner;
BeginInternalSubTransaction(NULL);
/* Want to run inside function's memory context */
MemoryContextSwitchTo(oldcontext);
PG_TRY();
{
int spi_rv;
spi_rv = SPI_execute(query, plperl_current_prodesc->fn_readonly,
limit);
ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed,
spi_rv);
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
/*
* AtEOSubXact_SPI() should not have popped any SPI context,
* but just in case it did, make sure we remain connected.
*/
SPI_restore_connection();
}
PG_CATCH();
{
ErrorData *edata;
/* Save error info */
MemoryContextSwitchTo(oldcontext);
edata = CopyErrorData();
FlushErrorState();
/* Abort the inner transaction */
RollbackAndReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
/*
* If AtEOSubXact_SPI() popped any SPI context of the subxact,
* it will have left us in a disconnected state. We need this
* hack to return to connected state.
*/
SPI_restore_connection();
/* Punt the error to Perl */
croak("%s", edata->message);
/* Can't get here, but keep compiler quiet */
return NULL;
}
PG_END_TRY();
return ret_hv; return ret_hv;
} }
static HV * static HV *
plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int status) plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed,
int status)
{ {
HV *result; HV *result;
...@@ -1619,21 +1678,18 @@ plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int stat ...@@ -1619,21 +1678,18 @@ plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int stat
if (status == SPI_OK_SELECT) if (status == SPI_OK_SELECT)
{ {
if (processed) AV *rows;
{ HV *row;
AV *rows; int i;
HV *row;
int i;
rows = newAV(); rows = newAV();
for (i = 0; i < processed; i++) for (i = 0; i < processed; i++)
{ {
row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc); row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc);
av_push(rows, newRV_noinc((SV *)row)); av_push(rows, newRV_noinc((SV *)row));
}
hv_store(result, "rows", strlen("rows"),
newRV_noinc((SV *) rows), 0);
} }
hv_store(result, "rows", strlen("rows"),
newRV_noinc((SV *) rows), 0);
} }
SPI_freetuptable(tuptable); SPI_freetuptable(tuptable);
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
* ENHANCEMENTS, OR MODIFICATIONS. * ENHANCEMENTS, OR MODIFICATIONS.
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.93 2004/09/14 03:21:27 tgl Exp $ * $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.94 2004/11/21 21:17:05 tgl Exp $
* *
**********************************************************************/ **********************************************************************/
...@@ -147,19 +147,6 @@ static Tcl_HashTable *pltcl_safe_query_hash = NULL; ...@@ -147,19 +147,6 @@ static Tcl_HashTable *pltcl_safe_query_hash = NULL;
static FunctionCallInfo pltcl_current_fcinfo = NULL; static FunctionCallInfo pltcl_current_fcinfo = NULL;
static pltcl_proc_desc *pltcl_current_prodesc = NULL; static pltcl_proc_desc *pltcl_current_prodesc = NULL;
/*
* When a callback from Tcl into PG incurs an error, we temporarily store
* the error information here, and return TCL_ERROR to the Tcl interpreter.
* Any further callback attempts immediately fail, and when the Tcl interpreter
* returns to the calling function, we re-throw the error (even if Tcl
* thinks it trapped the error and doesn't return TCL_ERROR). Eventually
* this ought to be improved to let Tcl code really truly trap the error,
* but that's more of a change from the pre-8.0 semantics than I have time
* for now --- it will only be possible if the callback query is executed
* inside a subtransaction.
*/
static ErrorData *pltcl_error_in_progress = NULL;
/********************************************************************** /**********************************************************************
* Forward declarations * Forward declarations
**********************************************************************/ **********************************************************************/
...@@ -189,6 +176,12 @@ static int pltcl_returnnull(ClientData cdata, Tcl_Interp *interp, ...@@ -189,6 +176,12 @@ static int pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
static int pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp, static int pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]); int argc, CONST84 char *argv[]);
static int pltcl_process_SPI_result(Tcl_Interp *interp,
CONST84 char *arrayname,
CONST84 char *loop_body,
int spi_rc,
SPITupleTable *tuptable,
int ntuples);
static int pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp, static int pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]); int argc, CONST84 char *argv[]);
static int pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp, static int pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
...@@ -592,28 +585,16 @@ pltcl_func_handler(PG_FUNCTION_ARGS) ...@@ -592,28 +585,16 @@ pltcl_func_handler(PG_FUNCTION_ARGS)
Tcl_DStringFree(&tcl_cmd); Tcl_DStringFree(&tcl_cmd);
/************************************************************ /************************************************************
* If there was an error in a PG callback, propagate that * Check for errors reported by Tcl.
* no matter what Tcl claims about its success.
************************************************************/
if (pltcl_error_in_progress)
{
ErrorData *edata = pltcl_error_in_progress;
pltcl_error_in_progress = NULL;
ReThrowError(edata);
}
/************************************************************
* Check for errors reported by Tcl itself.
************************************************************/ ************************************************************/
if (tcl_rc != TCL_OK) if (tcl_rc != TCL_OK)
{ {
UTF_BEGIN; UTF_BEGIN;
ereport(ERROR, ereport(ERROR,
(errmsg("pltcl: %s", interp->result), (errmsg("%s", interp->result),
errdetail("%s", errcontext("%s",
UTF_U2E(Tcl_GetVar(interp, "errorInfo", UTF_U2E(Tcl_GetVar(interp, "errorInfo",
TCL_GLOBAL_ONLY))))); TCL_GLOBAL_ONLY)))));
UTF_END; UTF_END;
} }
...@@ -820,28 +801,16 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS) ...@@ -820,28 +801,16 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS)
Tcl_DStringFree(&tcl_cmd); Tcl_DStringFree(&tcl_cmd);
/************************************************************ /************************************************************
* If there was an error in a PG callback, propagate that * Check for errors reported by Tcl.
* no matter what Tcl claims about its success.
************************************************************/
if (pltcl_error_in_progress)
{
ErrorData *edata = pltcl_error_in_progress;
pltcl_error_in_progress = NULL;
ReThrowError(edata);
}
/************************************************************
* Check for errors reported by Tcl itself.
************************************************************/ ************************************************************/
if (tcl_rc != TCL_OK) if (tcl_rc != TCL_OK)
{ {
UTF_BEGIN; UTF_BEGIN;
ereport(ERROR, ereport(ERROR,
(errmsg("pltcl: %s", interp->result), (errmsg("%s", interp->result),
errdetail("%s", errcontext("%s",
UTF_U2E(Tcl_GetVar(interp, "errorInfo", UTF_U2E(Tcl_GetVar(interp, "errorInfo",
TCL_GLOBAL_ONLY))))); TCL_GLOBAL_ONLY)))));
UTF_END; UTF_END;
} }
...@@ -1312,15 +1281,6 @@ pltcl_elog(ClientData cdata, Tcl_Interp *interp, ...@@ -1312,15 +1281,6 @@ pltcl_elog(ClientData cdata, Tcl_Interp *interp,
volatile int level; volatile int level;
MemoryContext oldcontext; MemoryContext oldcontext;
/************************************************************
* Suppress messages if an error is already declared
************************************************************/
if (pltcl_error_in_progress)
{
Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
return TCL_ERROR;
}
if (argc != 3) if (argc != 3)
{ {
Tcl_SetResult(interp, "syntax error - 'elog level msg'", Tcl_SetResult(interp, "syntax error - 'elog level msg'",
...@@ -1350,8 +1310,9 @@ pltcl_elog(ClientData cdata, Tcl_Interp *interp, ...@@ -1350,8 +1310,9 @@ pltcl_elog(ClientData cdata, Tcl_Interp *interp,
} }
/************************************************************ /************************************************************
* If elog() throws an error, catch and save it, then return * If elog() throws an error, catch it and return the error to the
* error indication to Tcl interpreter. * Tcl interpreter. Note we are assuming that elog() can't have any
* internal failures that are so bad as to require a transaction abort.
************************************************************/ ************************************************************/
oldcontext = CurrentMemoryContext; oldcontext = CurrentMemoryContext;
PG_TRY(); PG_TRY();
...@@ -1362,9 +1323,17 @@ pltcl_elog(ClientData cdata, Tcl_Interp *interp, ...@@ -1362,9 +1323,17 @@ pltcl_elog(ClientData cdata, Tcl_Interp *interp,
} }
PG_CATCH(); PG_CATCH();
{ {
ErrorData *edata;
/* Must reset elog.c's state */
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
pltcl_error_in_progress = CopyErrorData(); edata = CopyErrorData();
FlushErrorState(); FlushErrorState();
/* Pass the error message to Tcl */
Tcl_SetResult(interp, edata->message, TCL_VOLATILE);
FreeErrorData(edata);
return TCL_ERROR; return TCL_ERROR;
} }
PG_END_TRY(); PG_END_TRY();
...@@ -1522,6 +1491,83 @@ pltcl_returnnull(ClientData cdata, Tcl_Interp *interp, ...@@ -1522,6 +1491,83 @@ pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
} }
/*----------
* Support for running SPI operations inside subtransactions
*
* Intended usage pattern is:
*
* MemoryContext oldcontext = CurrentMemoryContext;
* ResourceOwner oldowner = CurrentResourceOwner;
*
* ...
* pltcl_subtrans_begin(oldcontext, oldowner);
* PG_TRY();
* {
* do something risky;
* pltcl_subtrans_commit(oldcontext, oldowner);
* }
* PG_CATCH();
* {
* pltcl_subtrans_abort(interp, oldcontext, oldowner);
* return TCL_ERROR;
* }
* PG_END_TRY();
* return TCL_OK;
*----------
*/
static void
pltcl_subtrans_begin(MemoryContext oldcontext, ResourceOwner oldowner)
{
BeginInternalSubTransaction(NULL);
/* Want to run inside function's memory context */
MemoryContextSwitchTo(oldcontext);
}
static void
pltcl_subtrans_commit(MemoryContext oldcontext, ResourceOwner oldowner)
{
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
/*
* AtEOSubXact_SPI() should not have popped any SPI context,
* but just in case it did, make sure we remain connected.
*/
SPI_restore_connection();
}
static void
pltcl_subtrans_abort(Tcl_Interp *interp,
MemoryContext oldcontext, ResourceOwner oldowner)
{
ErrorData *edata;
/* Save error info */
MemoryContextSwitchTo(oldcontext);
edata = CopyErrorData();
FlushErrorState();
/* Abort the inner transaction */
RollbackAndReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
/*
* If AtEOSubXact_SPI() popped any SPI context of the subxact,
* it will have left us in a disconnected state. We need this
* hack to return to connected state.
*/
SPI_restore_connection();
/* Pass the error message to Tcl */
Tcl_SetResult(interp, edata->message, TCL_VOLATILE);
FreeErrorData(edata);
}
/********************************************************************** /**********************************************************************
* pltcl_SPI_execute() - The builtin SPI_execute command * pltcl_SPI_execute() - The builtin SPI_execute command
* for the Tcl interpreter * for the Tcl interpreter
...@@ -1530,35 +1576,22 @@ static int ...@@ -1530,35 +1576,22 @@ static int
pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp, pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]) int argc, CONST84 char *argv[])
{ {
volatile int my_rc; int my_rc;
int spi_rc; int spi_rc;
char buf[64]; int query_idx;
int i;
int count = 0; int count = 0;
CONST84 char *volatile arrayname = NULL; CONST84 char *volatile arrayname = NULL;
volatile int query_idx; CONST84 char *volatile loop_body = NULL;
int i; MemoryContext oldcontext = CurrentMemoryContext;
int loop_rc; ResourceOwner oldowner = CurrentResourceOwner;
int ntuples;
HeapTuple *volatile tuples;
volatile TupleDesc tupdesc = NULL;
SPITupleTable *tuptable;
MemoryContext oldcontext;
char *usage = "syntax error - 'SPI_exec " char *usage = "syntax error - 'SPI_exec "
"?-count n? " "?-count n? "
"?-array name? query ?loop body?"; "?-array name? query ?loop body?";
/************************************************************ /************************************************************
* Don't do anything if we are already in error mode * Check the call syntax and get the options
************************************************************/
if (pltcl_error_in_progress)
{
Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
return TCL_ERROR;
}
/************************************************************
* Check the call syntax and get the count option
************************************************************/ ************************************************************/
if (argc < 2) if (argc < 2)
{ {
...@@ -1596,133 +1629,143 @@ pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp, ...@@ -1596,133 +1629,143 @@ pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
} }
query_idx = i; query_idx = i;
if (query_idx >= argc) if (query_idx >= argc || query_idx + 2 < argc)
{ {
Tcl_SetResult(interp, usage, TCL_VOLATILE); Tcl_SetResult(interp, usage, TCL_VOLATILE);
return TCL_ERROR; return TCL_ERROR;
} }
if (query_idx + 1 < argc)
loop_body = argv[query_idx + 1];
/************************************************************ /************************************************************
* Execute the query and handle return codes * Execute the query inside a sub-transaction, so we can cope with
* errors sanely
************************************************************/ ************************************************************/
oldcontext = CurrentMemoryContext;
pltcl_subtrans_begin(oldcontext, oldowner);
PG_TRY(); PG_TRY();
{ {
UTF_BEGIN; UTF_BEGIN;
spi_rc = SPI_execute(UTF_U2E(argv[query_idx]), spi_rc = SPI_execute(UTF_U2E(argv[query_idx]),
pltcl_current_prodesc->fn_readonly, count); pltcl_current_prodesc->fn_readonly, count);
UTF_END; UTF_END;
my_rc = pltcl_process_SPI_result(interp,
arrayname,
loop_body,
spi_rc,
SPI_tuptable,
SPI_processed);
pltcl_subtrans_commit(oldcontext, oldowner);
} }
PG_CATCH(); PG_CATCH();
{ {
MemoryContextSwitchTo(oldcontext); pltcl_subtrans_abort(interp, oldcontext, oldowner);
pltcl_error_in_progress = CopyErrorData();
FlushErrorState();
Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
return TCL_ERROR; return TCL_ERROR;
} }
PG_END_TRY(); PG_END_TRY();
return my_rc;
}
/*
* Process the result from SPI_execute or SPI_execute_plan
*
* Shared code between pltcl_SPI_execute and pltcl_SPI_execute_plan
*/
static int
pltcl_process_SPI_result(Tcl_Interp *interp,
CONST84 char *arrayname,
CONST84 char *loop_body,
int spi_rc,
SPITupleTable *tuptable,
int ntuples)
{
int my_rc = TCL_OK;
char buf[64];
int i;
int loop_rc;
HeapTuple *tuples;
TupleDesc tupdesc;
switch (spi_rc) switch (spi_rc)
{ {
case SPI_OK_UTILITY: case SPI_OK_UTILITY:
Tcl_SetResult(interp, "0", TCL_VOLATILE); Tcl_SetResult(interp, "0", TCL_VOLATILE);
SPI_freetuptable(SPI_tuptable); break;
return TCL_OK;
case SPI_OK_SELINTO: case SPI_OK_SELINTO:
case SPI_OK_INSERT: case SPI_OK_INSERT:
case SPI_OK_DELETE: case SPI_OK_DELETE:
case SPI_OK_UPDATE: case SPI_OK_UPDATE:
snprintf(buf, sizeof(buf), "%d", SPI_processed); snprintf(buf, sizeof(buf), "%d", ntuples);
Tcl_SetResult(interp, buf, TCL_VOLATILE); Tcl_SetResult(interp, buf, TCL_VOLATILE);
SPI_freetuptable(SPI_tuptable);
return TCL_OK;
case SPI_OK_SELECT:
break; break;
default: case SPI_OK_SELECT:
Tcl_AppendResult(interp, "pltcl: SPI_execute failed: ", /*
SPI_result_code_string(spi_rc), NULL); * Process the tuples we got
SPI_freetuptable(SPI_tuptable); */
return TCL_ERROR; tuples = tuptable->vals;
} tupdesc = tuptable->tupdesc;
/************************************************************
* Only SELECT queries fall through to here - process the tuples we got
************************************************************/
ntuples = SPI_processed;
tuptable = SPI_tuptable;
if (ntuples > 0)
{
tuples = tuptable->vals;
tupdesc = tuptable->tupdesc;
}
my_rc = TCL_OK; if (loop_body == NULL)
PG_TRY();
{
if (argc == query_idx + 1)
{
/************************************************************
* If there is no loop body given, just set the variables
* from the first tuple (if any)
************************************************************/
if (ntuples > 0)
pltcl_set_tuple_values(interp, arrayname, 0,
tuples[0], tupdesc);
}
else
{
/************************************************************
* There is a loop body - process all tuples and evaluate
* the body on each
************************************************************/
query_idx++;
for (i = 0; i < ntuples; i++)
{ {
pltcl_set_tuple_values(interp, arrayname, i, /*
tuples[i], tupdesc); * If there is no loop body given, just set the variables
* from the first tuple (if any)
loop_rc = Tcl_Eval(interp, argv[query_idx]); */
if (ntuples > 0)
if (loop_rc == TCL_OK) pltcl_set_tuple_values(interp, arrayname, 0,
continue; tuples[0], tupdesc);
if (loop_rc == TCL_CONTINUE) }
continue; else
if (loop_rc == TCL_RETURN) {
/*
* There is a loop body - process all tuples and evaluate
* the body on each
*/
for (i = 0; i < ntuples; i++)
{ {
my_rc = TCL_RETURN; pltcl_set_tuple_values(interp, arrayname, i,
tuples[i], tupdesc);
loop_rc = Tcl_Eval(interp, loop_body);
if (loop_rc == TCL_OK)
continue;
if (loop_rc == TCL_CONTINUE)
continue;
if (loop_rc == TCL_RETURN)
{
my_rc = TCL_RETURN;
break;
}
if (loop_rc == TCL_BREAK)
break;
my_rc = TCL_ERROR;
break; break;
} }
if (loop_rc == TCL_BREAK)
break;
my_rc = TCL_ERROR;
break;
} }
}
SPI_freetuptable(tuptable); if (my_rc == TCL_OK)
} {
PG_CATCH(); snprintf(buf, sizeof(buf), "%d", ntuples);
{ Tcl_SetResult(interp, buf, TCL_VOLATILE);
MemoryContextSwitchTo(oldcontext); }
pltcl_error_in_progress = CopyErrorData(); break;
FlushErrorState();
Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
return TCL_ERROR;
}
PG_END_TRY();
/************************************************************ default:
* Finally return the number of tuples Tcl_AppendResult(interp, "pltcl: SPI_execute failed: ",
************************************************************/ SPI_result_code_string(spi_rc), NULL);
if (my_rc == TCL_OK) my_rc = TCL_ERROR;
{ break;
snprintf(buf, sizeof(buf), "%d", ntuples);
Tcl_SetResult(interp, buf, TCL_VOLATILE);
} }
SPI_freetuptable(tuptable);
return my_rc; return my_rc;
} }
...@@ -1748,16 +1791,8 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp, ...@@ -1748,16 +1791,8 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
Tcl_HashEntry *hashent; Tcl_HashEntry *hashent;
int hashnew; int hashnew;
Tcl_HashTable *query_hash; Tcl_HashTable *query_hash;
MemoryContext oldcontext; MemoryContext oldcontext = CurrentMemoryContext;
ResourceOwner oldowner = CurrentResourceOwner;
/************************************************************
* Don't do anything if we are already in error mode
************************************************************/
if (pltcl_error_in_progress)
{
Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
return TCL_ERROR;
}
/************************************************************ /************************************************************
* Check the call syntax * Check the call syntax
...@@ -1785,7 +1820,13 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp, ...@@ -1785,7 +1820,13 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
qdesc->arginfuncs = (FmgrInfo *) malloc(nargs * sizeof(FmgrInfo)); qdesc->arginfuncs = (FmgrInfo *) malloc(nargs * sizeof(FmgrInfo));
qdesc->argtypioparams = (Oid *) malloc(nargs * sizeof(Oid)); qdesc->argtypioparams = (Oid *) malloc(nargs * sizeof(Oid));
oldcontext = CurrentMemoryContext; /************************************************************
* Execute the prepare inside a sub-transaction, so we can cope with
* errors sanely
************************************************************/
pltcl_subtrans_begin(oldcontext, oldowner);
PG_TRY(); PG_TRY();
{ {
/************************************************************ /************************************************************
...@@ -1844,31 +1885,31 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp, ...@@ -1844,31 +1885,31 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
/* Release the procCxt copy to avoid within-function memory leak */ /* Release the procCxt copy to avoid within-function memory leak */
SPI_freeplan(plan); SPI_freeplan(plan);
/************************************************************ pltcl_subtrans_commit(oldcontext, oldowner);
* Insert a hashtable entry for the plan and return
* the key to the caller
************************************************************/
if (interp == pltcl_norm_interp)
query_hash = pltcl_norm_query_hash;
else
query_hash = pltcl_safe_query_hash;
} }
PG_CATCH(); PG_CATCH();
{ {
MemoryContextSwitchTo(oldcontext); pltcl_subtrans_abort(interp, oldcontext, oldowner);
pltcl_error_in_progress = CopyErrorData();
FlushErrorState();
free(qdesc->argtypes); free(qdesc->argtypes);
free(qdesc->arginfuncs); free(qdesc->arginfuncs);
free(qdesc->argtypioparams); free(qdesc->argtypioparams);
free(qdesc); free(qdesc);
ckfree((char *) args); ckfree((char *) args);
Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
return TCL_ERROR; return TCL_ERROR;
} }
PG_END_TRY(); PG_END_TRY();
/************************************************************
* Insert a hashtable entry for the plan and return
* the key to the caller
************************************************************/
if (interp == pltcl_norm_interp)
query_hash = pltcl_norm_query_hash;
else
query_hash = pltcl_safe_query_hash;
hashent = Tcl_CreateHashEntry(query_hash, qdesc->qname, &hashnew); hashent = Tcl_CreateHashEntry(query_hash, qdesc->qname, &hashnew);
Tcl_SetHashValue(hashent, (ClientData) qdesc); Tcl_SetHashValue(hashent, (ClientData) qdesc);
...@@ -1886,41 +1927,27 @@ static int ...@@ -1886,41 +1927,27 @@ static int
pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp, pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]) int argc, CONST84 char *argv[])
{ {
volatile int my_rc; int my_rc;
int spi_rc; int spi_rc;
char buf[64]; int i;
volatile int i;
int j; int j;
int loop_body;
Tcl_HashEntry *hashent; Tcl_HashEntry *hashent;
pltcl_query_desc *qdesc; pltcl_query_desc *qdesc;
Datum *volatile argvalues = NULL;
const char *volatile nulls = NULL; const char *volatile nulls = NULL;
CONST84 char *volatile arrayname = NULL; CONST84 char *volatile arrayname = NULL;
CONST84 char *volatile loop_body = NULL;
int count = 0; int count = 0;
int callnargs; int callnargs;
CONST84 char **callargs; CONST84 char **callargs = NULL;
int loop_rc; Datum *argvalues;
int ntuples; MemoryContext oldcontext = CurrentMemoryContext;
HeapTuple *volatile tuples = NULL; ResourceOwner oldowner = CurrentResourceOwner;
volatile TupleDesc tupdesc = NULL;
SPITupleTable *tuptable;
volatile MemoryContext oldcontext;
Tcl_HashTable *query_hash; Tcl_HashTable *query_hash;
char *usage = "syntax error - 'SPI_execp " char *usage = "syntax error - 'SPI_execp "
"?-nulls string? ?-count n? " "?-nulls string? ?-count n? "
"?-array name? query ?args? ?loop body?"; "?-array name? query ?args? ?loop body?";
/************************************************************
* Don't do anything if we are already in error mode
************************************************************/
if (pltcl_error_in_progress)
{
Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
return TCL_ERROR;
}
/************************************************************ /************************************************************
* Get the options and check syntax * Get the options and check syntax
************************************************************/ ************************************************************/
...@@ -1963,7 +1990,7 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp, ...@@ -1963,7 +1990,7 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
} }
/************************************************************ /************************************************************
* Check minimum call arguments * Get the prepared plan descriptor by its key
************************************************************/ ************************************************************/
if (i >= argc) if (i >= argc)
{ {
...@@ -1971,21 +1998,19 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp, ...@@ -1971,21 +1998,19 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
return TCL_ERROR; return TCL_ERROR;
} }
/************************************************************
* Get the prepared plan descriptor by its key
************************************************************/
if (interp == pltcl_norm_interp) if (interp == pltcl_norm_interp)
query_hash = pltcl_norm_query_hash; query_hash = pltcl_norm_query_hash;
else else
query_hash = pltcl_safe_query_hash; query_hash = pltcl_safe_query_hash;
hashent = Tcl_FindHashEntry(query_hash, argv[i++]); hashent = Tcl_FindHashEntry(query_hash, argv[i]);
if (hashent == NULL) if (hashent == NULL)
{ {
Tcl_AppendResult(interp, "invalid queryid '", argv[--i], "'", NULL); Tcl_AppendResult(interp, "invalid queryid '", argv[i], "'", NULL);
return TCL_ERROR; return TCL_ERROR;
} }
qdesc = (pltcl_query_desc *) Tcl_GetHashValue(hashent); qdesc = (pltcl_query_desc *) Tcl_GetHashValue(hashent);
i++;
/************************************************************ /************************************************************
* If a nulls string is given, check for correct length * If a nulls string is given, check for correct length
...@@ -2030,178 +2055,86 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp, ...@@ -2030,178 +2055,86 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
ckfree((char *) callargs); ckfree((char *) callargs);
return TCL_ERROR; return TCL_ERROR;
} }
/************************************************************
* Setup the value array for SPI_execute_plan() using
* the type specific input functions
************************************************************/
oldcontext = CurrentMemoryContext;
PG_TRY();
{
argvalues = (Datum *) palloc(callnargs * sizeof(Datum));
for (j = 0; j < callnargs; j++)
{
if (nulls && nulls[j] == 'n')
{
/* don't try to convert the input for a null */
argvalues[j] = (Datum) 0;
}
else
{
UTF_BEGIN;
argvalues[j] =
FunctionCall3(&qdesc->arginfuncs[j],
CStringGetDatum(UTF_U2E(callargs[j])),
ObjectIdGetDatum(qdesc->argtypioparams[j]),
Int32GetDatum(-1));
UTF_END;
}
}
}
PG_CATCH();
{
ckfree((char *) callargs);
MemoryContextSwitchTo(oldcontext);
pltcl_error_in_progress = CopyErrorData();
FlushErrorState();
Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
return TCL_ERROR;
}
PG_END_TRY();
ckfree((char *) callargs);
} }
else else
callnargs = 0; callnargs = 0;
/************************************************************ /************************************************************
* Remember the index of the last processed call * Get loop body if present
* argument - a loop body for SELECT might follow
************************************************************/ ************************************************************/
loop_body = i; if (i < argc)
loop_body = argv[i++];
/************************************************************ if (i != argc)
* Execute the plan
************************************************************/
oldcontext = CurrentMemoryContext;
PG_TRY();
{ {
spi_rc = SPI_execute_plan(qdesc->plan, argvalues, nulls, Tcl_SetResult(interp, usage, TCL_VOLATILE);
pltcl_current_prodesc->fn_readonly, count);
}
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
pltcl_error_in_progress = CopyErrorData();
FlushErrorState();
Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE);
return TCL_ERROR; return TCL_ERROR;
} }
PG_END_TRY();
/************************************************************ /************************************************************
* Check the return code from SPI_execute_plan() * Execute the plan inside a sub-transaction, so we can cope with
* errors sanely
************************************************************/ ************************************************************/
switch (spi_rc)
{
case SPI_OK_UTILITY:
Tcl_SetResult(interp, "0", TCL_VOLATILE);
SPI_freetuptable(SPI_tuptable);
return TCL_OK;
case SPI_OK_SELINTO:
case SPI_OK_INSERT:
case SPI_OK_DELETE:
case SPI_OK_UPDATE:
snprintf(buf, sizeof(buf), "%d", SPI_processed);
Tcl_SetResult(interp, buf, TCL_VOLATILE);
SPI_freetuptable(SPI_tuptable);
return TCL_OK;
case SPI_OK_SELECT: pltcl_subtrans_begin(oldcontext, oldowner);
break;
default:
Tcl_AppendResult(interp, "pltcl: SPI_execute_plan failed: ",
SPI_result_code_string(spi_rc), NULL);
SPI_freetuptable(SPI_tuptable);
return TCL_ERROR;
}
/************************************************************
* Only SELECT queries fall through to here - process the tuples we got
************************************************************/
ntuples = SPI_processed;
tuptable = SPI_tuptable;
if (ntuples > 0)
{
tuples = tuptable->vals;
tupdesc = tuptable->tupdesc;
}
my_rc = TCL_OK;
PG_TRY(); PG_TRY();
{ {
if (loop_body >= argc) /************************************************************
{ * Setup the value array for SPI_execute_plan() using
/************************************************************ * the type specific input functions
* If there is no loop body given, just set the variables ************************************************************/
* from the first tuple (if any) argvalues = (Datum *) palloc(callnargs * sizeof(Datum));
************************************************************/
if (ntuples > 0) for (j = 0; j < callnargs; j++)
pltcl_set_tuple_values(interp, arrayname, 0,
tuples[0], tupdesc);
}
else
{ {
/************************************************************ if (nulls && nulls[j] == 'n')
* There is a loop body - process all tuples and evaluate
* the body on each
************************************************************/
for (i = 0; i < ntuples; i++)
{ {
pltcl_set_tuple_values(interp, arrayname, i, /* don't try to convert the input for a null */
tuples[i], tupdesc); argvalues[j] = (Datum) 0;
}
loop_rc = Tcl_Eval(interp, argv[loop_body]); else
{
if (loop_rc == TCL_OK) UTF_BEGIN;
continue; argvalues[j] =
if (loop_rc == TCL_CONTINUE) FunctionCall3(&qdesc->arginfuncs[j],
continue; CStringGetDatum(UTF_U2E(callargs[j])),
if (loop_rc == TCL_RETURN) ObjectIdGetDatum(qdesc->argtypioparams[j]),
{ Int32GetDatum(-1));
my_rc = TCL_RETURN; UTF_END;
break;
}
if (loop_rc == TCL_BREAK)
break;
my_rc = TCL_ERROR;
break;
} }
} }
SPI_freetuptable(tuptable); if (callargs)
ckfree((char *) callargs);
callargs = NULL;
/************************************************************
* Execute the plan
************************************************************/
spi_rc = SPI_execute_plan(qdesc->plan, argvalues, nulls,
pltcl_current_prodesc->fn_readonly, count);
my_rc = pltcl_process_SPI_result(interp,
arrayname,
loop_body,
spi_rc,
SPI_tuptable,
SPI_processed);
pltcl_subtrans_commit(oldcontext, oldowner);
} }
PG_CATCH(); PG_CATCH();
{ {
MemoryContextSwitchTo(oldcontext); pltcl_subtrans_abort(interp, oldcontext, oldowner);
pltcl_error_in_progress = CopyErrorData();
FlushErrorState(); if (callargs)
Tcl_SetResult(interp, "Transaction aborted", TCL_VOLATILE); ckfree((char *) callargs);
return TCL_ERROR; return TCL_ERROR;
} }
PG_END_TRY(); PG_END_TRY();
/************************************************************
* Finally return the number of tuples
************************************************************/
if (my_rc == TCL_OK)
{
snprintf(buf, sizeof(buf), "%d", ntuples);
Tcl_SetResult(interp, buf, TCL_VOLATILE);
}
return my_rc; return my_rc;
} }
......
...@@ -6,6 +6,8 @@ export DBNAME ...@@ -6,6 +6,8 @@ export DBNAME
echo "**** Destroy old database $DBNAME ****" echo "**** Destroy old database $DBNAME ****"
dropdb $DBNAME dropdb $DBNAME
sleep 1
echo "**** Create test database $DBNAME ****" echo "**** Create test database $DBNAME ****"
createdb $DBNAME createdb $DBNAME
......
-- suppress CONTEXT so that function OIDs aren't in output
\set VERBOSITY terse
insert into T_pkey1 values (1, 'key1-1', 'test key'); insert into T_pkey1 values (1, 'key1-1', 'test key');
insert into T_pkey1 values (1, 'key1-2', 'test key'); insert into T_pkey1 values (1, 'key1-2', 'test key');
......
--
-- checkpoint so that if we have a crash in the tests, replay of the
-- just-completed CREATE DATABASE won't discard the core dump file
--
checkpoint;
-- --
-- Create the tables used in the test queries -- Create the tables used in the test queries
-- --
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment