Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 36fb9ef

Browse files
committed
Detect whether plpgsql assignment targets are "local" variables.
Mark whether the target of a potentially optimizable assignment is "local", in the sense of being declared inside any exception block that could trap an error thrown from the assignment. (This implies that we needn't preserve the variable's value in case of an error. This patch doesn't do anything with the knowledge, but the next one will.) Normally, this requires a post-parsing scan of the function's parse tree, since we don't know while parsing a BEGIN ... construct whether we will find EXCEPTION at its end. However, if there are no BEGIN ... EXCEPTION blocks in the function at all, then all assignments are local, even those to variables representing function arguments. We optimize that common case by initializing the target_is_local flags to "true", and fixing them up with a post-scan only if we found EXCEPTION. Note that variables' default-value expressions are never interesting for expanded-variable optimization, since they couldn't contain a reference to the target variable anyway. But the code is set up to compute their target_param and target_is_local correctly anyway, for consistency and in case someone thinks of a use for that data. I added a bit of plpgsql_dumptree support to help verify that this code sets the flags as expected. I also added a plpgsql_dumptree call in plpgsql_compile_inline. It was at best an oversight that "#option dump" didn't work in a DO block; now it does. Author: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru> Reviewed-by: Pavel Borisov <pashkin.elfe@gmail.com> Discussion: https://postgr.es/m/CACxu=vJaKFNsYxooSnW1wEgsAO5u_v1XYBacfVJ14wgJV_PYeg@mail.gmail.com
1 parent a654af2 commit 36fb9ef

File tree

4 files changed

+121
-1
lines changed

4 files changed

+121
-1
lines changed

src/pl/plpgsql/src/pl_comp.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ do_compile(FunctionCallInfo fcinfo,
371371

372372
function->nstatements = 0;
373373
function->requires_procedure_resowner = false;
374+
function->has_exception_block = false;
374375

375376
/*
376377
* Initialize the compiler, particularly the namespace stack. The
@@ -811,6 +812,9 @@ do_compile(FunctionCallInfo fcinfo,
811812

812813
plpgsql_finish_datums(function);
813814

815+
if (function->has_exception_block)
816+
plpgsql_mark_local_assignment_targets(function);
817+
814818
/* Debug dump for completed functions */
815819
if (plpgsql_DumpExecTree)
816820
plpgsql_dumptree(function);
@@ -906,6 +910,7 @@ plpgsql_compile_inline(char *proc_source)
906910

907911
function->nstatements = 0;
908912
function->requires_procedure_resowner = false;
913+
function->has_exception_block = false;
909914

910915
plpgsql_ns_init();
911916
plpgsql_ns_push(func_name, PLPGSQL_LABEL_BLOCK);
@@ -962,6 +967,13 @@ plpgsql_compile_inline(char *proc_source)
962967

963968
plpgsql_finish_datums(function);
964969

970+
if (function->has_exception_block)
971+
plpgsql_mark_local_assignment_targets(function);
972+
973+
/* Debug dump for completed functions */
974+
if (plpgsql_DumpExecTree)
975+
plpgsql_dumptree(function);
976+
965977
/*
966978
* Pop the error context stack
967979
*/

src/pl/plpgsql/src/pl_funcs.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,91 @@ plpgsql_statement_tree_walker_impl(PLpgSQL_stmt *stmt,
598598
}
599599

600600

601+
/**********************************************************************
602+
* Mark assignment source expressions that have local target variables,
603+
* that is, the target variable is declared within the exception block
604+
* most closely containing the assignment itself. (Such target variables
605+
* need not be preserved if the assignment's source expression raises an
606+
* error, since the variable will no longer be accessible afterwards.
607+
* Detecting this allows better optimization.)
608+
*
609+
* This code need not be called if the plpgsql function contains no exception
610+
* blocks, because mark_expr_as_assignment_source will have set all the flags
611+
* to true already. Also, we need not reconsider default-value expressions
612+
* for variables, because variable declarations are necessarily within the
613+
* nearest exception block. (In DECLARE ... BEGIN ... EXCEPTION ... END, the
614+
* variable initializations are done before entering the exception scope.)
615+
*
616+
* Within the recursion, local_dnos is a Bitmapset of dnos of variables
617+
* known to be declared within the current exception level.
618+
**********************************************************************/
619+
static void mark_stmt(PLpgSQL_stmt *stmt, Bitmapset *local_dnos);
620+
static void mark_expr(PLpgSQL_expr *expr, Bitmapset *local_dnos);
621+
622+
static void
623+
mark_stmt(PLpgSQL_stmt *stmt, Bitmapset *local_dnos)
624+
{
625+
if (stmt == NULL)
626+
return;
627+
if (stmt->cmd_type == PLPGSQL_STMT_BLOCK)
628+
{
629+
PLpgSQL_stmt_block *block = (PLpgSQL_stmt_block *) stmt;
630+
631+
if (block->exceptions)
632+
{
633+
/*
634+
* The block creates a new exception scope, so variables declared
635+
* at outer levels are nonlocal. For that matter, so are any
636+
* variables declared in the block's DECLARE section. Hence, we
637+
* must pass down empty local_dnos.
638+
*/
639+
plpgsql_statement_tree_walker(stmt, mark_stmt, mark_expr, NULL);
640+
}
641+
else
642+
{
643+
/*
644+
* Otherwise, the block does not create a new exception scope, and
645+
* any variables it declares can also be considered local within
646+
* it. Note that only initializable datum types (VAR, REC) are
647+
* included in initvarnos; but that's sufficient for our purposes.
648+
*/
649+
local_dnos = bms_copy(local_dnos);
650+
for (int i = 0; i < block->n_initvars; i++)
651+
local_dnos = bms_add_member(local_dnos, block->initvarnos[i]);
652+
plpgsql_statement_tree_walker(stmt, mark_stmt, mark_expr,
653+
local_dnos);
654+
bms_free(local_dnos);
655+
}
656+
}
657+
else
658+
plpgsql_statement_tree_walker(stmt, mark_stmt, mark_expr, local_dnos);
659+
}
660+
661+
static void
662+
mark_expr(PLpgSQL_expr *expr, Bitmapset *local_dnos)
663+
{
664+
/*
665+
* If this expression has an assignment target, check whether the target
666+
* is local, and mark the expression accordingly.
667+
*/
668+
if (expr && expr->target_param >= 0)
669+
expr->target_is_local = bms_is_member(expr->target_param, local_dnos);
670+
}
671+
672+
void
673+
plpgsql_mark_local_assignment_targets(PLpgSQL_function *func)
674+
{
675+
Bitmapset *local_dnos;
676+
677+
/* Function parameters can be treated as local targets at outer level */
678+
local_dnos = NULL;
679+
for (int i = 0; i < func->fn_nargs; i++)
680+
local_dnos = bms_add_member(local_dnos, func->fn_argvarnos[i]);
681+
mark_stmt((PLpgSQL_stmt *) func->action, local_dnos);
682+
bms_free(local_dnos);
683+
}
684+
685+
601686
/**********************************************************************
602687
* Release memory when a PL/pgSQL function is no longer needed
603688
*
@@ -1500,6 +1585,9 @@ static void
15001585
dump_expr(PLpgSQL_expr *expr)
15011586
{
15021587
printf("'%s'", expr->query);
1588+
if (expr->target_param >= 0)
1589+
printf(" target %d%s", expr->target_param,
1590+
expr->target_is_local ? " (local)" : "");
15031591
}
15041592

15051593
void

src/pl/plpgsql/src/pl_gram.y

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2328,6 +2328,8 @@ exception_sect :
23282328
PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
23292329
PLpgSQL_variable *var;
23302330

2331+
plpgsql_curr_compile->has_exception_block = true;
2332+
23312333
var = plpgsql_build_variable("sqlstate", lineno,
23322334
plpgsql_build_datatype(TEXTOID,
23332335
-1,
@@ -2673,6 +2675,7 @@ make_plpgsql_expr(const char *query,
26732675
expr->ns = plpgsql_ns_top();
26742676
/* might get changed later during parsing: */
26752677
expr->target_param = -1;
2678+
expr->target_is_local = false;
26762679
/* other fields are left as zeroes until first execution */
26772680
return expr;
26782681
}
@@ -2687,9 +2690,21 @@ mark_expr_as_assignment_source(PLpgSQL_expr *expr, PLpgSQL_datum *target)
26872690
* other DTYPEs, so no need to mark in other cases.
26882691
*/
26892692
if (target->dtype == PLPGSQL_DTYPE_VAR)
2693+
{
26902694
expr->target_param = target->dno;
2695+
2696+
/*
2697+
* For now, assume the target is local to the nearest enclosing
2698+
* exception block. That's correct if the function contains no
2699+
* exception blocks; otherwise we'll update this later.
2700+
*/
2701+
expr->target_is_local = true;
2702+
}
26912703
else
2704+
{
26922705
expr->target_param = -1; /* should be that already */
2706+
expr->target_is_local = false; /* ditto */
2707+
}
26932708
}
26942709

26952710
/* Convenience routine to read an expression with one possible terminator */

src/pl/plpgsql/src/plpgsql.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,12 @@ typedef struct PLpgSQL_expr
225225
/*
226226
* These fields are used to help optimize assignments to expanded-datum
227227
* variables. If this expression is the source of an assignment to a
228-
* simple variable, target_param holds that variable's dno (else it's -1).
228+
* simple variable, target_param holds that variable's dno (else it's -1),
229+
* and target_is_local indicates whether the target is declared inside the
230+
* closest exception block containing the assignment.
229231
*/
230232
int target_param; /* dno of assign target, or -1 if none */
233+
bool target_is_local; /* is it within nearest exception block? */
231234

232235
/*
233236
* Fields above are set during plpgsql parsing. Remaining fields are left
@@ -1014,6 +1017,7 @@ typedef struct PLpgSQL_function
10141017
/* data derived while parsing body */
10151018
unsigned int nstatements; /* counter for assigning stmtids */
10161019
bool requires_procedure_resowner; /* contains CALL or DO? */
1020+
bool has_exception_block; /* contains BEGIN...EXCEPTION? */
10171021

10181022
/* these fields change when the function is used */
10191023
struct PLpgSQL_execstate *cur_estate;
@@ -1312,6 +1316,7 @@ extern PLpgSQL_nsitem *plpgsql_ns_find_nearest_loop(PLpgSQL_nsitem *ns_cur);
13121316
*/
13131317
extern PGDLLEXPORT const char *plpgsql_stmt_typename(PLpgSQL_stmt *stmt);
13141318
extern const char *plpgsql_getdiag_kindname(PLpgSQL_getdiag_kind kind);
1319+
extern void plpgsql_mark_local_assignment_targets(PLpgSQL_function *func);
13151320
extern void plpgsql_free_function_memory(PLpgSQL_function *func);
13161321
extern void plpgsql_dumptree(PLpgSQL_function *func);
13171322

0 commit comments

Comments
 (0)