From 7c6d95096e7aac5b568a545d72edc8ab5cd61dc9 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 26 Apr 2023 12:39:21 +0900 Subject: [PATCH] Simplify tracking of recursive call depth inside PL/pgSQL functions This commit replaces and simplifies the calculation of the counter tracking the recursion level inside PL/pgSQL functions by relying on fmgr_start and end hooks within the function manager for this purpose. The previous implementation had a huge flaw: it failed to properly detect the case of exceptions in PL functions, hence it would be possible to break the static counter so badly that the hint to use would be incorrect, or utterly broken in some cases where it would finish by being negative when stacking exceptions. a9863af has provided a band-aid fix to address this issue, but it has been relying on the erase_callback of PL/pgSQL to force a level of 0 when reaching the top-level, while the correct method would be to decrement the counter and apply sanity checks to make sure that the depth calculation is never messed up when coming back to the top-level. This commit switches the calculation of the depth to not rely any more on a resource owner callback or the plpgsql start/end hooks, as it happens that the function manager is correctly able: - To track if an exception happens within a PL/pgSQL function as a TRY/CATCH block would call the fmgr_hook before re-throwing an error. - To increment and decrement the count if facing a PL/pgSQL function. - To add sanity checks on the depth level to make sure that this is *never* less than zero after decrementing, and always higher than zero after incrementing. The only tweak that needs to be done is to check if a function needs to call the fmgr hook, something that can be done by checking if the function is written in PL/pgSQL. Even-though this requires caching the language OID on the first lookup, the change is quite intuitive. Relying on the fmgr hook has the advantage to not require the registration of the resowner callback for all the backends, saving some cycles in the most common cases. The test cases for PL functions are extended a bit to check for more scenarios: - test_hint_transaction() gains a before/after mode to control the timing of the internal COMMIT and ROLLBACK sub-commands. - test_hint_tab() gains a new query with a HashJoin hint, mixed with all the others to check that the hint is correctly applied, across multiple levels of depth. This is arguably a bug fix, and structurally it could be backpatch, but I am not doing so out of caution. Per pull request #131. --- expected/plpgsql.out | 652 ++++++++++++++++++++++++++++++++++++++----- pg_hint_plan.c | 160 +++++------ sql/plpgsql.sql | 37 ++- 3 files changed, 688 insertions(+), 161 deletions(-) diff --git a/expected/plpgsql.out b/expected/plpgsql.out index 0d44b647..bde17aed 100644 --- a/expected/plpgsql.out +++ b/expected/plpgsql.out @@ -19,8 +19,7 @@ SHOW pg_hint_plan.enable_hint_table; (1 row) -- Internal handling of hints within plpgsql functions. --- This forces an exception, manipulating internally plpgsql_recurse_level --- in the resowner cleanup callback. +-- This forces an exception, manipulating internally plpgsql_recurse_level. create or replace function test_hint_exception(level int) returns void language plpgsql as $$ begin @@ -35,8 +34,7 @@ begin exception when others then end; $$; -- Having a transaction context is essential to mess up with the --- recursion counter and to make sure that the resowner cleanup is called --- when expected. +-- plpgsql_recurse_level. begin; select set_config('compute_query_id','off', true); set_config @@ -185,12 +183,17 @@ begin end if; -- Mix of queries with and without hints. The level is mixed in the -- query string to show it in the output generated. + raise notice 'Execution % at level %, hash-join t2/t1 hint', run, level; + execute 'explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select ' || level || ' val) + select t1.val from test t1, test t2 where t1.val = t2.val;' + into c; raise notice 'Execution % at level %, no hints', run, level; execute 'explain (costs false) with test as (select ' || level || ' val) select t1.val from test t1, test t2 where t1.val = t2.val;' into c; - raise notice 'Execution % at level %, merge-join hint', run, level; + raise notice 'Execution % at level %, merge-join t1/t2 hint', run, level; execute 'explain (costs false) with test /*+ MergeJoin(t1 t2) */ as (select ' || level || ' val) select t1.val from test t1, test t2 where t1.val = t2.val;' @@ -199,29 +202,58 @@ begin end; $$; -- Entry point of this test. This executes the transaction -- commands while calling test_hint_queries in a nested loop. -create procedure test_hint_transaction() +-- "mode" can be set to "before" or "after", to control the timing of +-- the subtransaction commands launched in this procedure. +create procedure test_hint_transaction(mode text) language plpgsql as $$ declare c text; begin for i in 0..3 loop - execute 'select test_hint_queries(' || i || ', 0)'; - insert into test_hint_tab (a) values (i); + + if mode = 'before' then + execute 'select test_hint_queries(' || i || ', 0)'; + insert into test_hint_tab (a) values (i); + end if; + + -- Mix commits and rollbacks. if i % 2 = 0 then commit; else rollback; end if; + + if mode = 'after' then + execute 'select test_hint_queries(' || i || ', 0)'; + insert into test_hint_tab (a) values (i); + end if; end loop; end; $$; -call test_hint_transaction(); -NOTICE: Execution 0 at level 1, no hints +call test_hint_transaction('before'); +NOTICE: Execution 0 at level 1, hash-join t2/t1 hint CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE SQL statement "select test_hint_queries(0, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 0 at level 1, merge-join hint +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 0 at level 1, no hints CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE SQL statement "select test_hint_queries(0, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 0 at level 1, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE LOG: pg_hint_plan: used hint: MergeJoin(t1 t2) @@ -232,21 +264,42 @@ error hint: CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ as (select 1 val) select t1.val from test t1, test t2 where t1.val = t2.val;" -PL/pgSQL function test_hint_queries(integer,integer) line 17 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE SQL statement "select test_hint_queries(0, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 0 at level 2, no hints +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 0 at level 2, hash-join t2/t1 hint CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE SQL statement "select test_hint_queries(0,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(0, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 0 at level 2, merge-join hint +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(0,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 0 at level 2, no hints CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE SQL statement "select test_hint_queries(0,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(0, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 0 at level 2, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(0,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE LOG: pg_hint_plan: used hint: MergeJoin(t1 t2) @@ -257,19 +310,36 @@ error hint: CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ as (select 2 val) select t1.val from test t1, test t2 where t1.val = t2.val;" -PL/pgSQL function test_hint_queries(integer,integer) line 17 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE SQL statement "select test_hint_queries(0,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(0, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 1 at level 1, no hints +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 1 at level 1, hash-join t2/t1 hint CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE SQL statement "select test_hint_queries(1, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 1 at level 1, merge-join hint +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 1 at level 1, no hints CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE SQL statement "select test_hint_queries(1, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 1 at level 1, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE LOG: pg_hint_plan: used hint: MergeJoin(t1 t2) @@ -280,21 +350,42 @@ error hint: CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ as (select 1 val) select t1.val from test t1, test t2 where t1.val = t2.val;" -PL/pgSQL function test_hint_queries(integer,integer) line 17 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE SQL statement "select test_hint_queries(1, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 1 at level 2, no hints +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 1 at level 2, hash-join t2/t1 hint CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE SQL statement "select test_hint_queries(1,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(1, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 1 at level 2, merge-join hint +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(1,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 1 at level 2, no hints CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE SQL statement "select test_hint_queries(1,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 1 at level 2, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(1,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(1, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE LOG: pg_hint_plan: used hint: MergeJoin(t1 t2) @@ -305,19 +396,36 @@ error hint: CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ as (select 2 val) select t1.val from test t1, test t2 where t1.val = t2.val;" -PL/pgSQL function test_hint_queries(integer,integer) line 17 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE SQL statement "select test_hint_queries(1,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(1, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 2 at level 1, no hints +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 2 at level 1, hash-join t2/t1 hint CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE SQL statement "select test_hint_queries(2, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 2 at level 1, merge-join hint +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 2 at level 1, no hints CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE SQL statement "select test_hint_queries(2, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 2 at level 1, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE LOG: pg_hint_plan: used hint: MergeJoin(t1 t2) @@ -328,21 +436,42 @@ error hint: CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ as (select 1 val) select t1.val from test t1, test t2 where t1.val = t2.val;" -PL/pgSQL function test_hint_queries(integer,integer) line 17 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE SQL statement "select test_hint_queries(2, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 2 at level 2, no hints +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 2 at level 2, hash-join t2/t1 hint CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE SQL statement "select test_hint_queries(2,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(2, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 2 at level 2, merge-join hint +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(2,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 2 at level 2, no hints CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE SQL statement "select test_hint_queries(2,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(2, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 2 at level 2, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(2,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE LOG: pg_hint_plan: used hint: MergeJoin(t1 t2) @@ -353,19 +482,36 @@ error hint: CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ as (select 2 val) select t1.val from test t1, test t2 where t1.val = t2.val;" -PL/pgSQL function test_hint_queries(integer,integer) line 17 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE SQL statement "select test_hint_queries(2,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(2, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 3 at level 1, no hints +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 3 at level 1, hash-join t2/t1 hint CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE SQL statement "select test_hint_queries(3, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 3 at level 1, merge-join hint +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 3 at level 1, no hints CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE SQL statement "select test_hint_queries(3, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 3 at level 1, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE LOG: pg_hint_plan: used hint: MergeJoin(t1 t2) @@ -376,21 +522,387 @@ error hint: CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ as (select 1 val) select t1.val from test t1, test t2 where t1.val = t2.val;" -PL/pgSQL function test_hint_queries(integer,integer) line 17 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 3 at level 2, hash-join t2/t1 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE +SQL statement "select test_hint_queries(3,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(3,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(3, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE NOTICE: Execution 3 at level 2, no hints +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE +SQL statement "select test_hint_queries(3,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +NOTICE: Execution 3 at level 2, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(3,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +LOG: pg_hint_plan: +used hint: +MergeJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE +SQL statement "select test_hint_queries(3,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 7 at EXECUTE +call test_hint_transaction('after'); +NOTICE: Execution 0 at level 1, hash-join t2/t1 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 0 at level 1, no hints +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 0 at level 1, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +MergeJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 0 at level 2, hash-join t2/t1 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE +SQL statement "select test_hint_queries(0,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(0,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 0 at level 2, no hints +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE +SQL statement "select test_hint_queries(0,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 0 at level 2, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(0,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +MergeJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE +SQL statement "select test_hint_queries(0,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(0, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 1 at level 1, hash-join t2/t1 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 1 at level 1, no hints +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 1 at level 1, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +MergeJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 1 at level 2, hash-join t2/t1 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE +SQL statement "select test_hint_queries(1,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(1,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 1 at level 2, no hints +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE +SQL statement "select test_hint_queries(1,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 1 at level 2, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(1,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +MergeJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE +SQL statement "select test_hint_queries(1,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(1, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 2 at level 1, hash-join t2/t1 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 2 at level 1, no hints +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 2 at level 1, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +MergeJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 2 at level 2, hash-join t2/t1 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE +SQL statement "select test_hint_queries(2,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(2,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 2 at level 2, no hints +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE +SQL statement "select test_hint_queries(2,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 2 at level 2, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(2,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +MergeJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE +SQL statement "select test_hint_queries(2,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(2, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 3 at level 1, hash-join t2/t1 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 3 at level 1, no hints +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 3 at level 1, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +MergeJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ + as (select 1 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 3 at level 2, hash-join t2/t1 hint CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 11 at RAISE SQL statement "select test_hint_queries(3,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(3, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE -NOTICE: Execution 3 at level 2, merge-join hint +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + +CONTEXT: SQL statement "explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select 2 val) + select t1.val from test t1, test t2 where t1.val = t2.val;" +PL/pgSQL function test_hint_queries(integer,integer) line 12 at EXECUTE +SQL statement "select test_hint_queries(3,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 3 at level 2, no hints CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 16 at RAISE SQL statement "select test_hint_queries(3,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE +SQL statement "select test_hint_queries(3, 0)" +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE +NOTICE: Execution 3 at level 2, merge-join t1/t2 hint +CONTEXT: PL/pgSQL function test_hint_queries(integer,integer) line 21 at RAISE +SQL statement "select test_hint_queries(3,1)" +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(3, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE LOG: pg_hint_plan: used hint: MergeJoin(t1 t2) @@ -401,17 +913,19 @@ error hint: CONTEXT: SQL statement "explain (costs false) with test /*+ MergeJoin(t1 t2) */ as (select 2 val) select t1.val from test t1, test t2 where t1.val = t2.val;" -PL/pgSQL function test_hint_queries(integer,integer) line 17 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 22 at EXECUTE SQL statement "select test_hint_queries(3,1)" -PL/pgSQL function test_hint_queries(integer,integer) line 21 at EXECUTE +PL/pgSQL function test_hint_queries(integer,integer) line 26 at EXECUTE SQL statement "select test_hint_queries(3, 0)" -PL/pgSQL function test_hint_transaction() line 5 at EXECUTE +PL/pgSQL function test_hint_transaction(text) line 19 at EXECUTE table test_hint_tab; a --- 0 2 -(2 rows) + 1 + 3 +(4 rows) drop procedure test_hint_transaction; drop function test_hint_queries; diff --git a/pg_hint_plan.c b/pg_hint_plan.c index ac5a2d4a..9a1ff3cb 100644 --- a/pg_hint_plan.c +++ b/pg_hint_plan.c @@ -15,7 +15,9 @@ #include "access/relation.h" #include "catalog/pg_collation.h" #include "catalog/pg_index.h" +#include "catalog/pg_proc.h" #include "commands/prepare.h" +#include "commands/proclang.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" @@ -44,7 +46,6 @@ #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/syscache.h" -#include "utils/resowner.h" #include "catalog/pg_class.h" @@ -511,14 +512,6 @@ static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *pg_hint_plan_make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2); -static void pg_hint_plan_plpgsql_stmt_beg(PLpgSQL_execstate *estate, - PLpgSQL_stmt *stmt); -static void pg_hint_plan_plpgsql_stmt_end(PLpgSQL_execstate *estate, - PLpgSQL_stmt *stmt); -static void plpgsql_query_erase_callback(ResourceReleasePhase phase, - bool isCommit, - bool isTopLevel, - void *arg); static int set_config_option_noerror(const char *name, const char *value, GucContext context, GucSource source, GucAction action, bool changeVal, int elevel); @@ -586,10 +579,18 @@ static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL; static planner_hook_type prev_planner = NULL; static join_search_hook_type prev_join_search = NULL; static set_rel_pathlist_hook_type prev_set_rel_pathlist = NULL; +static needs_fmgr_hook_type prev_needs_fmgr_hook = NULL; +static fmgr_hook_type prev_fmgr_hook = NULL; /* Hold reference to currently active hint */ static HintState *current_hint_state = NULL; +/* + * Reference to OID of PL/pgsql language, saved on first lookup at a + * PL function. + */ +static Oid pg_hint_plan_pgpg_oid = InvalidOid; + /* * List of hint contexts. We treat the head of the list as the Top of the * context stack, so current_hint_state always points the first element of this @@ -631,15 +632,67 @@ static const HintParser parsers[] = { {NULL, NULL, HINT_KEYWORD_UNRECOGNIZED} }; -PLpgSQL_plugin plugin_funcs = { - NULL, - NULL, - NULL, - pg_hint_plan_plpgsql_stmt_beg, - pg_hint_plan_plpgsql_stmt_end, - NULL, - NULL, -}; +static bool +pg_hint_plan_is_plpgsql_function(Oid funcoid) +{ + HeapTuple procTuple; + Form_pg_proc procStruct; + bool result; + + procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); + if (!HeapTupleIsValid(procTuple)) + return false; + + procStruct = (Form_pg_proc) GETSTRUCT(procTuple); + + if (!OidIsValid(pg_hint_plan_pgpg_oid)) + pg_hint_plan_pgpg_oid = get_language_oid("plpgsql", false); + + result = (procStruct->prolang == pg_hint_plan_pgpg_oid); + + ReleaseSysCache(procTuple); + + return result; +} + +/* + * Used as needs_fmgr_hook. All plpgsql functions needs this hook to properly + * track the nested depth of plpgsql calls. + */ +static bool +pg_hint_plan_needs_fmgr_hook(Oid funcoid) +{ + if (prev_needs_fmgr_hook && + (*prev_needs_fmgr_hook)(funcoid)) + return true; + + return pg_hint_plan_is_plpgsql_function(funcoid); +} + +static void +pg_hint_plan_fmgr_hook(FmgrHookEventType event, + FmgrInfo *flinfo, Datum *private) +{ + if (prev_fmgr_hook) + (*prev_fmgr_hook) (event, flinfo, private); + + switch (event) + { + case FHET_START: + plpgsql_recurse_level++; + Assert(plpgsql_recurse_level > 0); + break; + case FHET_END: + case FHET_ABORT: /* may be an exception */ + plpgsql_recurse_level--; + Assert(plpgsql_recurse_level >= 0); + break; + default: + break; + } + + return; +} /* * Module load callbacks @@ -647,8 +700,6 @@ PLpgSQL_plugin plugin_funcs = { void _PG_init(void) { - PLpgSQL_plugin **var_ptr; - /* Define custom GUC variables. */ DefineCustomBoolVariable("pg_hint_plan.enable_hint", "Force planner to use plans specified in the hint comment preceding to the query.", @@ -730,12 +781,10 @@ _PG_init(void) join_search_hook = pg_hint_plan_join_search; prev_set_rel_pathlist = set_rel_pathlist_hook; set_rel_pathlist_hook = pg_hint_plan_set_rel_pathlist; - - /* setup PL/pgSQL plugin hook */ - var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin"); - *var_ptr = &plugin_funcs; - - RegisterResourceReleaseCallback(plpgsql_query_erase_callback, NULL); + prev_fmgr_hook = fmgr_hook; + fmgr_hook = pg_hint_plan_fmgr_hook; + prev_needs_fmgr_hook = needs_fmgr_hook; + needs_fmgr_hook = pg_hint_plan_needs_fmgr_hook; } /* @@ -751,6 +800,8 @@ _PG_fini(void) planner_hook = prev_planner; join_search_hook = prev_join_search; set_rel_pathlist_hook = prev_set_rel_pathlist; + needs_fmgr_hook = prev_needs_fmgr_hook; + fmgr_hook = prev_fmgr_hook; /* uninstall PL/pgSQL plugin hook */ var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin"); @@ -4873,63 +4924,6 @@ pg_hint_plan_set_rel_pathlist(PlannerInfo * root, RelOptInfo *rel, reset_hint_enforcement(); } -/* - * stmt_beg callback is called when each query in PL/pgSQL function is about - * to be executed. At that timing, we save query string in the global variable - * plpgsql_query_string to use it in planner hook. It's safe to use one global - * variable for the purpose, because its content is only necessary until - * planner hook is called for the query, so recursive PL/pgSQL function calls - * don't harm this mechanism. - */ -static void -pg_hint_plan_plpgsql_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) -{ - plpgsql_recurse_level++; -} - -/* - * stmt_end callback is called then each query in PL/pgSQL function has - * finished. At that timing, we clear plpgsql_query_string to tell planner - * hook that next call is not for a query written in PL/pgSQL block. - */ -static void -pg_hint_plan_plpgsql_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) -{ - - /* - * If we come here, we should have gone through the statement begin - * callback at least once. - */ - if (plpgsql_recurse_level > 0) - plpgsql_recurse_level--; -} - -void plpgsql_query_erase_callback(ResourceReleasePhase phase, - bool isCommit, - bool isTopLevel, - void *arg) -{ - /* Cleanup is just applied once all the locks are released */ - if (phase != RESOURCE_RELEASE_AFTER_LOCKS) - return; - - if (isTopLevel) - { - /* Cancel recurse level */ - plpgsql_recurse_level = 0; - } - else if (plpgsql_recurse_level > 0) - { - /* - * This applies when a transaction is aborted for a PL/pgSQL query, - * like when a transaction triggers an exception, or for an internal - * commit. - */ - plpgsql_recurse_level--; - } -} - - /* include core static functions */ static void populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *joinrel, diff --git a/sql/plpgsql.sql b/sql/plpgsql.sql index cc4e49c2..39a1aed0 100644 --- a/sql/plpgsql.sql +++ b/sql/plpgsql.sql @@ -12,8 +12,7 @@ SELECT setting <> 'off' FROM pg_settings WHERE name = 'compute_query_id'; SHOW pg_hint_plan.enable_hint_table; -- Internal handling of hints within plpgsql functions. --- This forces an exception, manipulating internally plpgsql_recurse_level --- in the resowner cleanup callback. +-- This forces an exception, manipulating internally plpgsql_recurse_level. create or replace function test_hint_exception(level int) returns void language plpgsql as $$ begin @@ -28,8 +27,7 @@ begin exception when others then end; $$; -- Having a transaction context is essential to mess up with the --- recursion counter and to make sure that the resowner cleanup is called --- when expected. +-- plpgsql_recurse_level. begin; select set_config('compute_query_id','off', true); -- Show plan without hints @@ -72,35 +70,56 @@ begin end if; -- Mix of queries with and without hints. The level is mixed in the -- query string to show it in the output generated. + raise notice 'Execution % at level %, hash-join t2/t1 hint', run, level; + execute 'explain (costs false) with test /*+ HashJoin(t2 t1) */ + as (select ' || level || ' val) + select t1.val from test t1, test t2 where t1.val = t2.val;' + into c; raise notice 'Execution % at level %, no hints', run, level; execute 'explain (costs false) with test as (select ' || level || ' val) select t1.val from test t1, test t2 where t1.val = t2.val;' into c; - raise notice 'Execution % at level %, merge-join hint', run, level; + raise notice 'Execution % at level %, merge-join t1/t2 hint', run, level; execute 'explain (costs false) with test /*+ MergeJoin(t1 t2) */ as (select ' || level || ' val) select t1.val from test t1, test t2 where t1.val = t2.val;' into c; execute 'select test_hint_queries(' || run || ',' || level || ')'; end; $$; + -- Entry point of this test. This executes the transaction -- commands while calling test_hint_queries in a nested loop. -create procedure test_hint_transaction() +-- "mode" can be set to "before" or "after", to control the timing of +-- the subtransaction commands launched in this procedure. +create procedure test_hint_transaction(mode text) language plpgsql as $$ declare c text; begin for i in 0..3 loop - execute 'select test_hint_queries(' || i || ', 0)'; - insert into test_hint_tab (a) values (i); + + if mode = 'before' then + execute 'select test_hint_queries(' || i || ', 0)'; + insert into test_hint_tab (a) values (i); + end if; + + -- Mix commits and rollbacks. if i % 2 = 0 then commit; else rollback; end if; + + if mode = 'after' then + execute 'select test_hint_queries(' || i || ', 0)'; + insert into test_hint_tab (a) values (i); + end if; end loop; end; $$; -call test_hint_transaction(); + +call test_hint_transaction('before'); +call test_hint_transaction('after'); + table test_hint_tab; drop procedure test_hint_transaction; drop function test_hint_queries;