@ -26,7 +26,6 @@
# include "commands/defrem.h"
# include "executor/execExpr.h"
# include "executor/spi.h"
# include "executor/spi_priv.h"
# include "executor/tstoreReceiver.h"
# include "funcapi.h"
# include "mb/stringinfo_mb.h"
@ -329,8 +328,7 @@ static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
static void exec_eval_cleanup ( PLpgSQL_execstate * estate ) ;
static void exec_prepare_plan ( PLpgSQL_execstate * estate ,
PLpgSQL_expr * expr , int cursorOptions ,
bool keepplan ) ;
PLpgSQL_expr * expr , int cursorOptions ) ;
static void exec_simple_check_plan ( PLpgSQL_execstate * estate , PLpgSQL_expr * expr ) ;
static void exec_save_simple_expr ( PLpgSQL_expr * expr , CachedPlan * cplan ) ;
static void exec_check_rw_parameter ( PLpgSQL_expr * expr ) ;
@ -446,6 +444,8 @@ static char *format_expr_params(PLpgSQL_execstate *estate,
const PLpgSQL_expr * expr ) ;
static char * format_preparedparamsdata ( PLpgSQL_execstate * estate ,
ParamListInfo paramLI ) ;
static PLpgSQL_variable * make_callstmt_target ( PLpgSQL_execstate * estate ,
PLpgSQL_expr * expr ) ;
/* ----------
@ -460,12 +460,18 @@ static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
* shared_simple_eval_resowner . ( When using a private simple_eval_estate ,
* we must also use a private cast hashtable , but that ' s taken care of
* within plpgsql_estate_setup . )
* procedure_resowner is a resowner that will survive for the duration
* of execution of this function / procedure . It is needed only if we
* are doing non - atomic execution and there are CALL or DO statements
* in the function ; otherwise it can be NULL . We use it to hold refcounts
* on the CALL / DO statements ' plans .
* - - - - - - - - - -
*/
Datum
plpgsql_exec_function ( PLpgSQL_function * func , FunctionCallInfo fcinfo ,
EState * simple_eval_estate ,
ResourceOwner simple_eval_resowner ,
ResourceOwner procedure_resowner ,
bool atomic )
{
PLpgSQL_execstate estate ;
@ -478,6 +484,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
*/
plpgsql_estate_setup ( & estate , func , ( ReturnSetInfo * ) fcinfo - > resultinfo ,
simple_eval_estate , simple_eval_resowner ) ;
estate . procedure_resowner = procedure_resowner ;
estate . atomic = atomic ;
/*
@ -2150,76 +2157,122 @@ static int
exec_stmt_call ( PLpgSQL_execstate * estate , PLpgSQL_stmt_call * stmt )
{
PLpgSQL_expr * expr = stmt - > expr ;
SPIPlanPtr orig_plan = expr - > plan ;
bool local_plan ;
PLpgSQL_variable * volatile cur_target = stmt - > target ;
volatile LocalTransactionId before_lxid ;
LocalTransactionId before_lxid ;
LocalTransactionId after_lxid ;
volatile bool pushed_active_snap = false ;
volatile int rc ;
bool pushed_active_snap = false ;
ParamListInfo paramLI ;
SPIExecuteOptions options ;
int rc ;
/*
* If not in atomic context , we make a local plan that we ' ll just use for
* this invocation , and will free at the end . Otherwise , transaction ends
* would cause errors about plancache leaks .
*
* XXX This would be fixable with some plancache / resowner surgery
* elsewhere , but for now we ' ll just work around this here .
* Make a plan if we don ' t have one already .
*/
local_plan = ! estate - > atomic ;
/* PG_TRY to ensure we clear the plan link, if needed, on failure */
PG_TRY ( ) ;
if ( expr - > plan = = NULL )
{
SPIPlanPtr plan = expr - > plan ;
ParamListInfo paramLI ;
exec_prepare_plan ( estate , expr , 0 ) ;
/*
* Make a plan if we don ' t have one , or if we need a local one . Note
* that we ' ll overwrite expr - > plan either way ; the PG_TRY block will
* ensure we undo that on the way out , if the plan is local .
* A CALL or DO can never be a simple expression .
*/
if ( plan = = NULL | | local_plan )
{
/* Don't let SPI save the plan if it's going to be local */
exec_prepare_plan ( estate , expr , 0 , ! local_plan ) ;
plan = expr - > plan ;
Assert ( ! expr - > expr_simple_expr ) ;
/*
* A CALL or DO can never be a simple expression . ( If it could
* be , we ' d have to worry about saving / restoring the previous
* values of the related expr fields , not just expr - > plan . )
* Also construct a DTYPE_ROW datum representing the plpgsql variables
* associated with the procedure ' s output arguments . Then we can use
* exec_move_row ( ) to do the assignments .
*/
Assert ( ! expr - > expr_simple_expr ) ;
if ( stmt - > is_call )
stmt - > target = make_callstmt_target ( estate , expr ) ;
}
paramLI = setup_param_list ( estate , expr ) ;
before_lxid = MyProc - > lxid ;
/*
* The procedure call could end transactions , which would upset
* the snapshot management in SPI_execute * , so don ' t let it do it .
* Instead , we set the snapshots ourselves below .
* The procedure call could end transactions , which would upset the
* snapshot management in SPI_execute * , so handle snapshots here instead .
* Set a snapshot only for non - read - only procedures , similar to SPI
* behavior .
*/
plan - > no_snapshots = true ;
if ( ! estate - > readonly_func )
{
PushActiveSnapshot ( GetTransactionSnapshot ( ) ) ;
pushed_active_snap = true ;
}
/*
* Force target to be recalculated whenever the plan changes , in
* case the procedure ' s argument list has changed .
* If we have a procedure - lifespan resowner , use that to hold the refcount
* for the plan . This avoids refcount leakage complaints if the called
* procedure ends the current transaction .
*/
stmt - > target = NULL ;
cur_target = NULL ;
memset ( & options , 0 , sizeof ( options ) ) ;
options . params = paramLI ;
options . read_only = estate - > readonly_func ;
options . no_snapshots = true ; /* disable SPI's snapshot management */
options . owner = estate - > procedure_resowner ;
rc = SPI_execute_plan_extended ( expr - > plan , & options ) ;
if ( rc < 0 )
elog ( ERROR , " SPI_execute_plan_extended failed executing query \" %s \" : %s " ,
expr - > query , SPI_result_code_string ( rc ) ) ;
after_lxid = MyProc - > lxid ;
if ( before_lxid = = after_lxid )
{
/*
* If we are still in the same transaction after the call , pop the
* snapshot that we might have pushed . ( If it ' s a new transaction ,
* then all the snapshots are gone already . )
*/
if ( pushed_active_snap )
PopActiveSnapshot ( ) ;
}
else
{
/*
* If we are in a new transaction after the call , we need to build new
* simple - expression infrastructure .
*/
estate - > simple_eval_estate = NULL ;
estate - > simple_eval_resowner = NULL ;
plpgsql_create_econtext ( estate ) ;
}
/*
* Check result rowcount ; if there ' s one row , assign procedure ' s output
* values back to the appropriate variables .
*/
if ( SPI_processed = = 1 )
{
SPITupleTable * tuptab = SPI_tuptable ;
if ( ! stmt - > is_call )
elog ( ERROR , " DO statement returned a row " ) ;
exec_move_row ( estate , stmt - > target , tuptab - > vals [ 0 ] , tuptab - > tupdesc ) ;
}
else if ( SPI_processed > 1 )
elog ( ERROR , " procedure call returned more than one row " ) ;
exec_eval_cleanup ( estate ) ;
SPI_freetuptable ( SPI_tuptable ) ;
return PLPGSQL_RC_OK ;
}
/*
* We construct a DTYPE_ROW datum representing the plpgsql variables
* associated with the procedure ' s output arguments . Then we can use
* exec_move_row ( ) to do the assignments .
*
* If we ' re using a local plan , also make a local target ; otherwise ,
* since the above code will force a new plan each time through , we ' d
* repeatedly leak the memory for the target . ( Note : we also leak the
* target when a plan change is forced , but that isn ' t so likely to
* cause excessive memory leaks . )
*/
if ( stmt - > is_call & & cur_target = = NULL )
{
static PLpgSQL_variable *
make_callstmt_target ( PLpgSQL_execstate * estate , PLpgSQL_expr * expr )
{
List * plansources ;
CachedPlanSource * plansource ;
Node * node ;
FuncExpr * funcexpr ;
HeapTuple func_tuple ;
@ -2233,14 +2286,19 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
int i ;
ListCell * lc ;
/* Use stmt _mcontext for any cruft accumulated here */
oldcontext = MemoryContextSwitchTo ( get_stmt _mcontext ( estate ) ) ;
/* Use eval _mcontext for any cruft accumulated here */
oldcontext = MemoryContextSwitchTo ( get_eval _mcontext ( estate ) ) ;
/*
* Get the parsed CallStmt , and look up the called procedure
*/
node = linitial_node ( Query ,
( ( CachedPlanSource * ) linitial ( plan - > plancache_list ) ) - > query_list ) - > utilityStmt ;
plansources = SPI_plan_get_plan_sources ( expr - > plan ) ;
if ( list_length ( plansources ) ! = 1 )
elog ( ERROR , " query for CALL statement is not a CallStmt " ) ;
plansource = ( CachedPlanSource * ) linitial ( plansources ) ;
if ( list_length ( plansource - > query_list ) ! = 1 )
elog ( ERROR , " query for CALL statement is not a CallStmt " ) ;
node = linitial_node ( Query , plansource - > query_list ) - > utilityStmt ;
if ( node = = NULL | | ! IsA ( node , CallStmt ) )
elog ( ERROR , " query for CALL statement is not a CallStmt " ) ;
@ -2267,10 +2325,9 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
ReleaseSysCache ( func_tuple ) ;
/*
* Begin constructing row Datum ; keep it in fn_cxt if it ' s to be
* Begin constructing row Datum ; keep it in fn_cxt so it ' s adequately
* long - lived .
*/
if ( ! local_plan )
MemoryContextSwitchTo ( estate - > func - > fn_cxt ) ;
row = ( PLpgSQL_row * ) palloc0 ( sizeof ( PLpgSQL_row ) ) ;
@ -2279,13 +2336,12 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
row - > lineno = - 1 ;
row - > varnos = ( int * ) palloc ( sizeof ( int ) * list_length ( funcargs ) ) ;
if ( ! local_plan )
MemoryContextSwitchTo ( get_stmt_mcontext ( estate ) ) ;
MemoryContextSwitchTo ( get_eval_mcontext ( estate ) ) ;
/*
* Examine procedure ' s argument list . Each output arg position
* should be an unadorned plpgsql variable ( Datum ) , which we can
* insert into the row Datum .
* Examine procedure ' s argument list . Each output arg position should be
* an unadorned plpgsql variable ( Datum ) , which we can insert into the row
* Datum .
*/
nfields = 0 ;
i = 0 ;
@ -2324,104 +2380,9 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
row - > nfields = nfields ;
cur_target = ( PLpgSQL_variable * ) row ;
/* We can save and re-use the target datum, if it's not local */
if ( ! local_plan )
stmt - > target = cur_target ;
MemoryContextSwitchTo ( oldcontext ) ;
}
paramLI = setup_param_list ( estate , expr ) ;
before_lxid = MyProc - > lxid ;
/*
* Set snapshot only for non - read - only procedures , similar to SPI
* behavior .
*/
if ( ! estate - > readonly_func )
{
PushActiveSnapshot ( GetTransactionSnapshot ( ) ) ;
pushed_active_snap = true ;
}
rc = SPI_execute_plan_with_paramlist ( expr - > plan , paramLI ,
estate - > readonly_func , 0 ) ;
}
PG_CATCH ( ) ;
{
/*
* If we are using a local plan , restore the old plan link .
*/
if ( local_plan )
expr - > plan = orig_plan ;
PG_RE_THROW ( ) ;
}
PG_END_TRY ( ) ;
/*
* If we are using a local plan , restore the old plan link ; then free the
* local plan to avoid memory leaks . ( Note that the error exit path above
* just clears the link without risking calling SPI_freeplan ; we expect
* that xact cleanup will take care of the mess in that case . )
*/
if ( local_plan )
{
SPIPlanPtr plan = expr - > plan ;
expr - > plan = orig_plan ;
SPI_freeplan ( plan ) ;
}
if ( rc < 0 )
elog ( ERROR , " SPI_execute_plan_with_paramlist failed executing query \" %s \" : %s " ,
expr - > query , SPI_result_code_string ( rc ) ) ;
after_lxid = MyProc - > lxid ;
if ( before_lxid = = after_lxid )
{
/*
* If we are still in the same transaction after the call , pop the
* snapshot that we might have pushed . ( If it ' s a new transaction ,
* then all the snapshots are gone already . )
*/
if ( pushed_active_snap )
PopActiveSnapshot ( ) ;
}
else
{
/*
* If we are in a new transaction after the call , we need to build new
* simple - expression infrastructure .
*/
estate - > simple_eval_estate = NULL ;
estate - > simple_eval_resowner = NULL ;
plpgsql_create_econtext ( estate ) ;
}
/*
* Check result rowcount ; if there ' s one row , assign procedure ' s output
* values back to the appropriate variables .
*/
if ( SPI_processed = = 1 )
{
SPITupleTable * tuptab = SPI_tuptable ;
if ( ! cur_target )
elog ( ERROR , " DO statement returned a row " ) ;
exec_move_row ( estate , cur_target , tuptab - > vals [ 0 ] , tuptab - > tupdesc ) ;
}
else if ( SPI_processed > 1 )
elog ( ERROR , " procedure call returned more than one row " ) ;
exec_eval_cleanup ( estate ) ;
SPI_freetuptable ( SPI_tuptable ) ;
return PLPGSQL_RC_OK ;
return ( PLpgSQL_variable * ) row ;
}
/* ----------
@ -2960,7 +2921,7 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
Assert ( query ) ;
if ( query - > plan = = NULL )
exec_prepare_plan ( estate , query , curvar - > cursor_options , true ) ;
exec_prepare_plan ( estate , query , curvar - > cursor_options ) ;
/*
* Set up ParamListInfo for this query
@ -3607,12 +3568,13 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
/* static query */
PLpgSQL_expr * expr = stmt - > query ;
ParamListInfo paramLI ;
SPIExecuteOptions options ;
/*
* On the first call for this expression generate the plan .
*/
if ( expr - > plan = = NULL )
exec_prepare_plan ( estate , expr , CURSOR_OPT_PARALLEL_OK , true ) ;
exec_prepare_plan ( estate , expr , CURSOR_OPT_PARALLEL_OK ) ;
/*
* Set up ParamListInfo to pass to executor
@ -3622,9 +3584,12 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
/*
* Execute the query
*/
rc = SPI_execute_plan_with_receiver ( expr - > plan , paramLI ,
estate - > readonly_func , 0 ,
treceiver ) ;
memset ( & options , 0 , sizeof ( options ) ) ;
options . params = paramLI ;
options . read_only = estate - > readonly_func ;
options . dest = treceiver ;
rc = SPI_execute_plan_extended ( expr - > plan , & options ) ;
if ( rc ! = SPI_OK_SELECT )
ereport ( ERROR ,
( errcode ( ERRCODE_SYNTAX_ERROR ) ,
@ -4091,6 +4056,9 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
else
estate - > simple_eval_resowner = shared_simple_eval_resowner ;
/* if there's a procedure resowner, it'll be filled in later */
estate - > procedure_resowner = NULL ;
/*
* We start with no stmt_mcontext ; one will be created only if needed .
* That context will be a direct child of the function ' s main execution
@ -4159,8 +4127,7 @@ exec_eval_cleanup(PLpgSQL_execstate *estate)
*/
static void
exec_prepare_plan ( PLpgSQL_execstate * estate ,
PLpgSQL_expr * expr , int cursorOptions ,
bool keepplan )
PLpgSQL_expr * expr , int cursorOptions )
{
SPIPlanPtr plan ;
SPIPrepareOptions options ;
@ -4183,7 +4150,7 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
if ( plan = = NULL )
elog ( ERROR , " SPI_prepare_extended failed for \" %s \" : %s " ,
expr - > query , SPI_result_code_string ( SPI_result ) ) ;
if ( keepplan )
SPI_keepplan ( plan ) ;
expr - > plan = plan ;
@ -4222,7 +4189,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
{
ListCell * l ;
exec_prepare_plan ( estate , expr , CURSOR_OPT_PARALLEL_OK , true ) ;
exec_prepare_plan ( estate , expr , CURSOR_OPT_PARALLEL_OK ) ;
stmt - > mod_stmt = false ;
foreach ( l , SPI_plan_get_plan_sources ( expr - > plan ) )
{
@ -4681,7 +4648,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
*/
query = stmt - > query ;
if ( query - > plan = = NULL )
exec_prepare_plan ( estate , query , stmt - > cursor_options , true ) ;
exec_prepare_plan ( estate , query , stmt - > cursor_options ) ;
}
else if ( stmt - > dynquery ! = NULL )
{
@ -4752,7 +4719,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
query = curvar - > cursor_explicit_expr ;
if ( query - > plan = = NULL )
exec_prepare_plan ( estate , query , curvar - > cursor_options , true ) ;
exec_prepare_plan ( estate , query , curvar - > cursor_options ) ;
}
/*
@ -4985,18 +4952,20 @@ static int
exec_stmt_set ( PLpgSQL_execstate * estate , PLpgSQL_stmt_set * stmt )
{
PLpgSQL_expr * expr = stmt - > expr ;
SPIExecuteOptions options ;
int rc ;
if ( expr - > plan = = NULL )
{
exec_prepare_plan ( estate , expr , 0 , true ) ;
expr - > plan - > no_snapshots = true ;
}
exec_prepare_plan ( estate , expr , 0 ) ;
rc = SPI_execute_plan ( expr - > plan , NULL , NULL , estate - > readonly_func , 0 ) ;
memset ( & options , 0 , sizeof ( options ) ) ;
options . read_only = estate - > readonly_func ;
options . no_snapshots = true ; /* disable SPI's snapshot management */
rc = SPI_execute_plan_extended ( expr - > plan , & options ) ;
if ( rc ! = SPI_OK_UTILITY )
elog ( ERROR , " SPI_execute_plan failed executing query \" %s \" : %s " ,
elog ( ERROR , " SPI_execute_plan_extended failed executing query \" %s \" : %s " ,
expr - > query , SPI_result_code_string ( rc ) ) ;
return PLPGSQL_RC_OK ;
@ -5032,7 +5001,7 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
else
expr - > target_param = - 1 ; /* should be that already */
exec_prepare_plan ( estate , expr , 0 , true ) ;
exec_prepare_plan ( estate , expr , 0 ) ;
}
value = exec_eval_expr ( estate , expr , & isnull , & valtype , & valtypmod ) ;
@ -5697,7 +5666,7 @@ exec_eval_expr(PLpgSQL_execstate *estate,
* If first time through , create a plan for this expression .
*/
if ( expr - > plan = = NULL )
exec_prepare_plan ( estate , expr , CURSOR_OPT_PARALLEL_OK , true ) ;
exec_prepare_plan ( estate , expr , CURSOR_OPT_PARALLEL_OK ) ;
/*
* If this is a simple expression , bypass SPI and use the executor
@ -5783,7 +5752,7 @@ exec_run_select(PLpgSQL_execstate *estate,
*/
if ( expr - > plan = = NULL )
exec_prepare_plan ( estate , expr ,
portalP = = NULL ? CURSOR_OPT_PARALLEL_OK : 0 , true ) ;
portalP = = NULL ? CURSOR_OPT_PARALLEL_OK : 0 ) ;
/*
* Set up ParamListInfo to pass to executor
@ -6056,11 +6025,8 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
*/
if ( expr - > expr_simple_plan_lxid = = curlxid )
{
ResourceOwner saveResourceOwner = CurrentResourceOwner ;
CurrentResourceOwner = estate - > simple_eval_resowner ;
ReleaseCachedPlan ( expr - > expr_simple_plan , true ) ;
CurrentResourceOwner = saveResourceOwner ;
ReleaseCachedPlan ( expr - > expr_simple_plan ,
estate - > simple_eval_resowner ) ;
expr - > expr_simple_plan = NULL ;
expr - > expr_simple_plan_lxid = InvalidLocalTransactionId ;
}
@ -6094,7 +6060,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
else
{
/* Release SPI_plan_get_cached_plan's refcount */
ReleaseCachedPlan ( cplan , true ) ;
ReleaseCachedPlan ( cplan , CurrentResourceOwner ) ;
/* Mark expression as non-simple, and fail */
expr - > expr_simple_expr = NULL ;
expr - > expr_rw_param = NULL ;
@ -6105,7 +6071,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
* SPI_plan_get_cached_plan acquired a plan refcount stored in the
* active resowner . We don ' t need that anymore , so release it .
*/
ReleaseCachedPlan ( cplan , true ) ;
ReleaseCachedPlan ( cplan , CurrentResourceOwner ) ;
/* Extract desired scalar expression from cached plan */
exec_save_simple_expr ( expr , cplan ) ;
@ -8023,7 +7989,7 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
* Release the plan refcount obtained by SPI_plan_get_cached_plan . ( This
* refcount is held by the wrong resowner , so we can ' t just repurpose it . )
*/
ReleaseCachedPlan ( cplan , true ) ;
ReleaseCachedPlan ( cplan , CurrentResourceOwner ) ;
}
/*