Commit 800af890 authored by Tom Lane's avatar Tom Lane

Code review for spi_query/spi_fetchrow patch: handle errors sanely,

avoid leaking memory.  I would add a regression test for error handling
except it seems eval{} can't be used in unprivileged plperl :-(
parent 056eb141
...@@ -46,6 +46,33 @@ do_spi_elog(int level, char *message) ...@@ -46,6 +46,33 @@ do_spi_elog(int level, char *message)
PG_END_TRY(); PG_END_TRY();
} }
/*
* Interface routine to catch ereports and punt them to Perl
*/
static void
do_plperl_return_next(SV *sv)
{
MemoryContext oldcontext = CurrentMemoryContext;
PG_TRY();
{
plperl_return_next(sv);
}
PG_CATCH();
{
ErrorData *edata;
/* Must reset elog.c's state */
MemoryContextSwitchTo(oldcontext);
edata = CopyErrorData();
FlushErrorState();
/* Punt the error to Perl */
croak("%s", edata->message);
}
PG_END_TRY();
}
MODULE = SPI PREFIX = spi_ MODULE = SPI PREFIX = spi_
...@@ -101,7 +128,7 @@ void ...@@ -101,7 +128,7 @@ void
spi_return_next(rv) spi_return_next(rv)
SV *rv; SV *rv;
CODE: CODE:
plperl_return_next(rv); do_plperl_return_next(rv);
SV * SV *
spi_spi_query(query) spi_spi_query(query)
......
...@@ -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.93 2005/10/15 02:49:49 momjian Exp $ * $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.94 2005/10/18 17:13:14 tgl Exp $
* *
**********************************************************************/ **********************************************************************/
...@@ -119,9 +119,6 @@ Datum plperl_call_handler(PG_FUNCTION_ARGS); ...@@ -119,9 +119,6 @@ Datum plperl_call_handler(PG_FUNCTION_ARGS);
Datum plperl_validator(PG_FUNCTION_ARGS); Datum plperl_validator(PG_FUNCTION_ARGS);
void plperl_init(void); void plperl_init(void);
HV *plperl_spi_exec(char *query, int limit);
SV *plperl_spi_query(char *);
static Datum plperl_func_handler(PG_FUNCTION_ARGS); static Datum plperl_func_handler(PG_FUNCTION_ARGS);
static Datum plperl_trigger_handler(PG_FUNCTION_ARGS); static Datum plperl_trigger_handler(PG_FUNCTION_ARGS);
...@@ -131,8 +128,6 @@ static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc); ...@@ -131,8 +128,6 @@ static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc);
static void plperl_init_shared_libs(pTHX); static void plperl_init_shared_libs(pTHX);
static HV *plperl_spi_execute_fetch_result(SPITupleTable *, int, int); static HV *plperl_spi_execute_fetch_result(SPITupleTable *, int, int);
void plperl_return_next(SV *);
/* /*
* This routine is a crock, and so is everyplace that calls it. The problem * This routine is a crock, and so is everyplace that calls it. The problem
* is that the cached form of plperl functions/queries is allocated permanently * is that the cached form of plperl functions/queries is allocated permanently
...@@ -1552,8 +1547,16 @@ plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, ...@@ -1552,8 +1547,16 @@ plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed,
} }
/*
* Note: plperl_return_next is called both in Postgres and Perl contexts.
* We report any errors in Postgres fashion (via ereport). If called in
* Perl context, it is SPI.xs's responsibility to catch the error and
* convert to a Perl error. We assume (perhaps without adequate justification)
* that we need not abort the current transaction if the Perl code traps the
* error.
*/
void void
plperl_return_next(SV * sv) plperl_return_next(SV *sv)
{ {
plperl_proc_desc *prodesc = plperl_current_prodesc; plperl_proc_desc *prodesc = plperl_current_prodesc;
FunctionCallInfo fcinfo = plperl_current_caller_info; FunctionCallInfo fcinfo = plperl_current_caller_info;
...@@ -1566,20 +1569,16 @@ plperl_return_next(SV * sv) ...@@ -1566,20 +1569,16 @@ plperl_return_next(SV * sv)
return; return;
if (!prodesc->fn_retisset) if (!prodesc->fn_retisset)
{
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use return_next in a non-SETOF function"))); errmsg("cannot use return_next in a non-SETOF function")));
}
if (prodesc->fn_retistuple && if (prodesc->fn_retistuple &&
!(SvOK(sv) && SvTYPE(sv) == SVt_RV && SvTYPE(SvRV(sv)) == SVt_PVHV)) !(SvOK(sv) && SvTYPE(sv) == SVt_RV && SvTYPE(SvRV(sv)) == SVt_PVHV))
{
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("setof-composite-returning Perl function " errmsg("setof-composite-returning Perl function "
"must call return_next with reference to hash"))); "must call return_next with reference to hash")));
}
cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory); cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
...@@ -1637,10 +1636,15 @@ plperl_spi_query(char *query) ...@@ -1637,10 +1636,15 @@ plperl_spi_query(char *query)
{ {
SV *cursor; SV *cursor;
/*
* Execute the query inside a sub-transaction, so we can cope with errors
* sanely
*/
MemoryContext oldcontext = CurrentMemoryContext; MemoryContext oldcontext = CurrentMemoryContext;
ResourceOwner oldowner = CurrentResourceOwner; ResourceOwner oldowner = CurrentResourceOwner;
BeginInternalSubTransaction(NULL); BeginInternalSubTransaction(NULL);
/* Want to run inside function's memory context */
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
PG_TRY(); PG_TRY();
...@@ -1648,6 +1652,7 @@ plperl_spi_query(char *query) ...@@ -1648,6 +1652,7 @@ plperl_spi_query(char *query)
void *plan; void *plan;
Portal portal = NULL; Portal portal = NULL;
/* Create a cursor for the query */
plan = SPI_prepare(query, 0, NULL); plan = SPI_prepare(query, 0, NULL);
if (plan) if (plan)
portal = SPI_cursor_open(NULL, plan, NULL, NULL, false); portal = SPI_cursor_open(NULL, plan, NULL, NULL, false);
...@@ -1656,25 +1661,42 @@ plperl_spi_query(char *query) ...@@ -1656,25 +1661,42 @@ plperl_spi_query(char *query)
else else
cursor = newSV(0); cursor = newSV(0);
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction(); ReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner; 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(); SPI_restore_connection();
} }
PG_CATCH(); PG_CATCH();
{ {
ErrorData *edata; ErrorData *edata;
/* Save error info */
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
edata = CopyErrorData(); edata = CopyErrorData();
FlushErrorState(); FlushErrorState();
/* Abort the inner transaction */
RollbackAndReleaseCurrentSubTransaction(); RollbackAndReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner; 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(); SPI_restore_connection();
/* Punt the error to Perl */
croak("%s", edata->message); croak("%s", edata->message);
/* Can't get here, but keep compiler quiet */
return NULL; return NULL;
} }
PG_END_TRY(); PG_END_TRY();
...@@ -1686,22 +1708,80 @@ plperl_spi_query(char *query) ...@@ -1686,22 +1708,80 @@ plperl_spi_query(char *query)
SV * SV *
plperl_spi_fetchrow(char *cursor) plperl_spi_fetchrow(char *cursor)
{ {
SV *row = newSV(0); SV *row;
Portal p = SPI_cursor_find(cursor);
/*
* Execute the FETCH inside a sub-transaction, so we can cope with errors
* sanely
*/
MemoryContext oldcontext = CurrentMemoryContext;
ResourceOwner oldowner = CurrentResourceOwner;
if (!p) BeginInternalSubTransaction(NULL);
return row; /* Want to run inside function's memory context */
MemoryContextSwitchTo(oldcontext);
SPI_cursor_fetch(p, true, 1); PG_TRY();
if (SPI_processed == 0)
{ {
SPI_cursor_close(p); Portal p = SPI_cursor_find(cursor);
return row;
if (!p)
row = newSV(0);
else
{
SPI_cursor_fetch(p, true, 1);
if (SPI_processed == 0)
{
SPI_cursor_close(p);
row = newSV(0);
}
else
{
row = plperl_hash_from_tuple(SPI_tuptable->vals[0],
SPI_tuptable->tupdesc);
}
SPI_freetuptable(SPI_tuptable);
}
/* 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;
row = plperl_hash_from_tuple(SPI_tuptable->vals[0], /* Save error info */
SPI_tuptable->tupdesc); MemoryContextSwitchTo(oldcontext);
SPI_freetuptable(SPI_tuptable); 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 row; return row;
} }
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