Skip to content

Commit c0486e9

Browse files
committed
Fix plpgsql's handling of "simple" expression evaluation.
In general, expression execution state trees aren't re-entrantly usable, since functions can store private state information in them. For efficiency reasons, plpgsql tries to cache and reuse state trees for "simple" expressions. It can get away with that most of the time, but it can fail if the state tree is dirty from a previous failed execution (as in an example from Alvaro) or is being used recursively (as noted by me). Fix by tracking whether a state tree is in use, and falling back to the "non-simple" code path if so. This results in a pretty considerable speed hit when the non-simple path is taken, but the available alternatives seem even more unpleasant because they add overhead in the simple path. Per idea from Heikki. Back-patch to all supported branches.
1 parent 35b66df commit c0486e9

File tree

4 files changed

+113
-4
lines changed

4 files changed

+113
-4
lines changed

src/pl/plpgsql/src/pl_exec.c

+19-1
Original file line numberDiff line numberDiff line change
@@ -3731,7 +3731,14 @@ exec_eval_expr(PLpgSQL_execstate *estate,
37313731
* directly
37323732
*/
37333733
if (expr->expr_simple_expr != NULL)
3734-
return exec_eval_simple_expr(estate, expr, isNull, rettype);
3734+
{
3735+
/*
3736+
* If expression is in use in current xact, don't touch it.
3737+
*/
3738+
if (!(expr->expr_simple_in_use &&
3739+
expr->expr_simple_xid == GetTopTransactionId()))
3740+
return exec_eval_simple_expr(estate, expr, isNull, rettype);
3741+
}
37353742

37363743
rc = exec_run_select(estate, expr, 2, NULL);
37373744
if (rc != SPI_OK_SELECT)
@@ -3883,6 +3890,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
38833890
{
38843891
expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr,
38853892
simple_eval_estate);
3893+
expr->expr_simple_in_use = false;
38863894
expr->expr_simple_xid = curxid;
38873895
}
38883896

@@ -3938,18 +3946,27 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
39383946
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
39393947
}
39403948

3949+
/*
3950+
* Mark expression as busy for the duration of the ExecEvalExpr call.
3951+
*/
3952+
expr->expr_simple_in_use = true;
3953+
39413954
/*
39423955
* Finally we can call the executor to evaluate the expression
39433956
*/
39443957
retval = ExecEvalExpr(expr->expr_simple_state,
39453958
econtext,
39463959
isNull,
39473960
NULL);
3961+
3962+
/* Assorted cleanup */
3963+
expr->expr_simple_in_use = false;
39483964
MemoryContextSwitchTo(oldcontext);
39493965
}
39503966
PG_CATCH();
39513967
{
39523968
/* Restore global vars and propagate error */
3969+
/* note we intentionally don't reset expr_simple_in_use here */
39533970
ActiveSnapshot = saveActiveSnapshot;
39543971
PG_RE_THROW();
39553972
}
@@ -4545,6 +4562,7 @@ exec_simple_check_plan(PLpgSQL_expr *expr)
45454562
*/
45464563
expr->expr_simple_expr = tle->expr;
45474564
expr->expr_simple_state = NULL;
4565+
expr->expr_simple_in_use = false;
45484566
expr->expr_simple_xid = InvalidTransactionId;
45494567
/* Also stash away the expression result type */
45504568
expr->expr_simple_type = exprType((Node *) tle->expr);

src/pl/plpgsql/src/plpgsql.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,12 @@ typedef struct PLpgSQL_expr
201201
Expr *expr_simple_expr; /* NULL means not a simple expr */
202202
Oid expr_simple_type;
203203
/*
204-
* if expr is simple AND in use in current xact, expr_simple_state is
205-
* valid. Test validity by seeing if expr_simple_xid matches current XID.
204+
* if expr is simple AND in use in current xact, expr_simple_state and
205+
* expr_simple_in_use are valid. Test validity by seeing if
206+
* expr_simple_xid matches current XID.
206207
*/
207-
ExprState *expr_simple_state;
208+
ExprState *expr_simple_state; /* eval tree for expr_simple_expr */
209+
bool expr_simple_in_use; /* true if eval tree is active */
208210
TransactionId expr_simple_xid;
209211
/* params to pass to expr */
210212
int nparams;

src/test/regress/expected/plpgsql.out

+47
Original file line numberDiff line numberDiff line change
@@ -2782,3 +2782,50 @@ SELECT nonsimple_expr_test();
27822782
(1 row)
27832783

27842784
DROP FUNCTION nonsimple_expr_test();
2785+
--
2786+
-- Test cases involving recursion and error recovery in simple expressions
2787+
-- (bugs in all versions before October 2010). The problems are most
2788+
-- easily exposed by mutual recursion between plpgsql and sql functions.
2789+
--
2790+
create function recurse(float8) returns float8 as
2791+
$$
2792+
begin
2793+
if ($1 < 10) then
2794+
return sql_recurse($1 + 1);
2795+
else
2796+
return $1;
2797+
end if;
2798+
end;
2799+
$$ language plpgsql;
2800+
-- "limit" is to prevent this from being inlined
2801+
create function sql_recurse(float8) returns float8 as
2802+
$$ select recurse($1) limit 1; $$ language sql;
2803+
select recurse(0);
2804+
recurse
2805+
---------
2806+
10
2807+
(1 row)
2808+
2809+
create function error1(text) returns text language sql as
2810+
$$ SELECT relname::text FROM pg_class c WHERE c.oid = $1::regclass $$;
2811+
create function error2(p_name_table text) returns text language plpgsql as $$
2812+
begin
2813+
return error1(p_name_table);
2814+
end$$;
2815+
BEGIN;
2816+
create table public.stuffs (stuff text);
2817+
SAVEPOINT a;
2818+
select error2('nonexistent.stuffs');
2819+
ERROR: schema "nonexistent" does not exist
2820+
CONTEXT: SQL function "error1" statement 1
2821+
PL/pgSQL function "error2" line 2 at return
2822+
ROLLBACK TO a;
2823+
select error2('public.stuffs');
2824+
error2
2825+
--------
2826+
stuffs
2827+
(1 row)
2828+
2829+
rollback;
2830+
drop function error2(p_name_table text);
2831+
drop function error1(text);

src/test/regress/sql/plpgsql.sql

+42
Original file line numberDiff line numberDiff line change
@@ -2335,3 +2335,45 @@ $$ LANGUAGE plpgsql;
23352335
SELECT nonsimple_expr_test();
23362336

23372337
DROP FUNCTION nonsimple_expr_test();
2338+
2339+
--
2340+
-- Test cases involving recursion and error recovery in simple expressions
2341+
-- (bugs in all versions before October 2010). The problems are most
2342+
-- easily exposed by mutual recursion between plpgsql and sql functions.
2343+
--
2344+
2345+
create function recurse(float8) returns float8 as
2346+
$$
2347+
begin
2348+
if ($1 < 10) then
2349+
return sql_recurse($1 + 1);
2350+
else
2351+
return $1;
2352+
end if;
2353+
end;
2354+
$$ language plpgsql;
2355+
2356+
-- "limit" is to prevent this from being inlined
2357+
create function sql_recurse(float8) returns float8 as
2358+
$$ select recurse($1) limit 1; $$ language sql;
2359+
2360+
select recurse(0);
2361+
2362+
create function error1(text) returns text language sql as
2363+
$$ SELECT relname::text FROM pg_class c WHERE c.oid = $1::regclass $$;
2364+
2365+
create function error2(p_name_table text) returns text language plpgsql as $$
2366+
begin
2367+
return error1(p_name_table);
2368+
end$$;
2369+
2370+
BEGIN;
2371+
create table public.stuffs (stuff text);
2372+
SAVEPOINT a;
2373+
select error2('nonexistent.stuffs');
2374+
ROLLBACK TO a;
2375+
select error2('public.stuffs');
2376+
rollback;
2377+
2378+
drop function error2(p_name_table text);
2379+
drop function error1(text);

0 commit comments

Comments
 (0)