diff options
5 files changed, 7 insertions, 399 deletions
diff --git a/src/test/modules/delay_execution/Makefile b/src/test/modules/delay_execution/Makefile index 3eeb097fde4..70f24e846da 100644 --- a/src/test/modules/delay_execution/Makefile +++ b/src/test/modules/delay_execution/Makefile @@ -8,8 +8,7 @@ OBJS = \ delay_execution.o ISOLATION = partition-addition \ - partition-removal-1 \ - cached-plan-inval + partition-removal-1 ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/src/test/modules/delay_execution/delay_execution.c b/src/test/modules/delay_execution/delay_execution.c index ad22bc9f2a8..7bc97f84a1c 100644 --- a/src/test/modules/delay_execution/delay_execution.c +++ b/src/test/modules/delay_execution/delay_execution.c @@ -1,18 +1,14 @@ /*------------------------------------------------------------------------- * * delay_execution.c - * Test module to introduce delay at various points during execution of a - * query to test that execution proceeds safely in light of concurrent - * changes. + * Test module to allow delay between parsing and execution of a query. * * The delay is implemented by taking and immediately releasing a specified * advisory lock. If another process has previously taken that lock, the * current process will be blocked until the lock is released; otherwise, * there's no effect. This allows an isolationtester script to reliably - * test behaviors where some specified action happens in another backend in - * a couple of cases: 1) between parsing and execution of any desired query - * when using the planner_hook, 2) between RevalidateCachedQuery() and - * ExecutorStart() when using the ExecutorStart_hook. + * test behaviors where some specified action happens in another backend + * between parsing and execution of any desired query. * * Copyright (c) 2020-2025, PostgreSQL Global Development Group * @@ -26,7 +22,6 @@ #include <limits.h> -#include "executor/executor.h" #include "optimizer/planner.h" #include "utils/fmgrprotos.h" #include "utils/guc.h" @@ -37,11 +32,9 @@ PG_MODULE_MAGIC; /* GUC: advisory lock ID to use. Zero disables the feature. */ static int post_planning_lock_id = 0; -static int executor_start_lock_id = 0; -/* Save previous hook users to be a good citizen */ +/* Save previous planner hook user to be a good citizen */ static planner_hook_type prev_planner_hook = NULL; -static ExecutorStart_hook_type prev_ExecutorStart_hook = NULL; /* planner_hook function to provide the desired delay */ @@ -77,45 +70,11 @@ delay_execution_planner(Query *parse, const char *query_string, return result; } -/* ExecutorStart_hook function to provide the desired delay */ -static bool -delay_execution_ExecutorStart(QueryDesc *queryDesc, int eflags) -{ - bool plan_valid; - - /* If enabled, delay by taking and releasing the specified lock */ - if (executor_start_lock_id != 0) - { - DirectFunctionCall1(pg_advisory_lock_int8, - Int64GetDatum((int64) executor_start_lock_id)); - DirectFunctionCall1(pg_advisory_unlock_int8, - Int64GetDatum((int64) executor_start_lock_id)); - - /* - * Ensure that we notice any pending invalidations, since the advisory - * lock functions don't do this. - */ - AcceptInvalidationMessages(); - } - - /* Now start the executor, possibly via a previous hook user */ - if (prev_ExecutorStart_hook) - plan_valid = prev_ExecutorStart_hook(queryDesc, eflags); - else - plan_valid = standard_ExecutorStart(queryDesc, eflags); - - if (executor_start_lock_id != 0) - elog(NOTICE, "Finished ExecutorStart(): CachedPlan is %s", - plan_valid ? "valid" : "not valid"); - - return plan_valid; -} - /* Module load function */ void _PG_init(void) { - /* Set up GUCs to control which lock is used */ + /* Set up the GUC to control which lock is used */ DefineCustomIntVariable("delay_execution.post_planning_lock_id", "Sets the advisory lock ID to be locked/unlocked after planning.", "Zero disables the delay.", @@ -128,22 +87,9 @@ _PG_init(void) NULL, NULL); - DefineCustomIntVariable("delay_execution.executor_start_lock_id", - "Sets the advisory lock ID to be locked/unlocked before starting execution.", - "Zero disables the delay.", - &executor_start_lock_id, - 0, - 0, INT_MAX, - PGC_USERSET, - 0, - NULL, - NULL, - NULL); MarkGUCPrefixReserved("delay_execution"); - /* Install our hooks. */ + /* Install our hook */ prev_planner_hook = planner_hook; planner_hook = delay_execution_planner; - prev_ExecutorStart_hook = ExecutorStart_hook; - ExecutorStart_hook = delay_execution_ExecutorStart; } diff --git a/src/test/modules/delay_execution/expected/cached-plan-inval.out b/src/test/modules/delay_execution/expected/cached-plan-inval.out deleted file mode 100644 index 444e06b43e4..00000000000 --- a/src/test/modules/delay_execution/expected/cached-plan-inval.out +++ /dev/null @@ -1,250 +0,0 @@ -Parsed test spec with 2 sessions - -starting permutation: s1prep s2lock s1exec s2dropi s2unlock -step s1prep: SET plan_cache_mode = force_generic_plan; - PREPARE q AS SELECT * FROM foov WHERE a = $1 FOR UPDATE; - EXPLAIN (COSTS OFF) EXECUTE q (1); -QUERY PLAN ------------------------------------------------------ -LockRows - -> Append - Subplans Removed: 2 - -> Index Scan using foo1_1_a on foo1_1 foo_1 - Index Cond: (a = $1) -(5 rows) - -step s2lock: SELECT pg_advisory_lock(12345); -pg_advisory_lock ----------------- - -(1 row) - -step s1exec: LOAD 'delay_execution'; - SET delay_execution.executor_start_lock_id = 12345; - EXPLAIN (COSTS OFF) EXECUTE q (1); <waiting ...> -step s2dropi: DROP INDEX foo1_1_a; -step s2unlock: SELECT pg_advisory_unlock(12345); -pg_advisory_unlock ------------------- -t -(1 row) - -step s1exec: <... completed> -s1: NOTICE: Finished ExecutorStart(): CachedPlan is not valid -s1: NOTICE: Finished ExecutorStart(): CachedPlan is valid -QUERY PLAN ------------------------------------- -LockRows - -> Append - Subplans Removed: 2 - -> Seq Scan on foo1_1 foo_1 - Filter: (a = $1) -(5 rows) - - -starting permutation: s1prep2 s2lock s1exec2 s2dropi s2unlock -step s1prep2: SET plan_cache_mode = force_generic_plan; - PREPARE q2 AS SELECT * FROM foov WHERE a = one() or a = two(); - EXPLAIN (COSTS OFF) EXECUTE q2; -s1: NOTICE: Finished ExecutorStart(): CachedPlan is valid -QUERY PLAN ---------------------------------------------------- -Append - Subplans Removed: 1 - -> Index Scan using foo1_1_a on foo1_1 foo_1 - Index Cond: (a = ANY (ARRAY[one(), two()])) - -> Seq Scan on foo1_2 foo_2 - Filter: ((a = one()) OR (a = two())) -(6 rows) - -step s2lock: SELECT pg_advisory_lock(12345); -pg_advisory_lock ----------------- - -(1 row) - -step s1exec2: LOAD 'delay_execution'; - SET delay_execution.executor_start_lock_id = 12345; - EXPLAIN (COSTS OFF) EXECUTE q2; <waiting ...> -step s2dropi: DROP INDEX foo1_1_a; -step s2unlock: SELECT pg_advisory_unlock(12345); -pg_advisory_unlock ------------------- -t -(1 row) - -step s1exec2: <... completed> -s1: NOTICE: Finished ExecutorStart(): CachedPlan is not valid -s1: NOTICE: Finished ExecutorStart(): CachedPlan is valid -QUERY PLAN --------------------------------------------- -Append - Subplans Removed: 1 - -> Seq Scan on foo1_1 foo_1 - Filter: ((a = one()) OR (a = two())) - -> Seq Scan on foo1_2 foo_2 - Filter: ((a = one()) OR (a = two())) -(6 rows) - - -starting permutation: s1prep3 s2lock s1exec3 s2dropi s2unlock -step s1prep3: SET plan_cache_mode = force_generic_plan; - PREPARE q3 AS UPDATE foov SET a = a WHERE a = one() or a = two(); - EXPLAIN (COSTS OFF) EXECUTE q3; -s1: NOTICE: Finished ExecutorStart(): CachedPlan is valid -s1: NOTICE: Finished ExecutorStart(): CachedPlan is valid -s1: NOTICE: Finished ExecutorStart(): CachedPlan is valid -QUERY PLAN ---------------------------------------------------------------- -Nested Loop - -> Append - Subplans Removed: 1 - -> Index Only Scan using foo1_1_a on foo1_1 foo_1 - Index Cond: (a = ANY (ARRAY[one(), two()])) - -> Seq Scan on foo1_2 foo_2 - Filter: ((a = one()) OR (a = two())) - -> Materialize - -> Append - Subplans Removed: 1 - -> Seq Scan on bar1 bar_1 - Filter: (a = one()) - -Update on bar - Update on bar1 bar_1 - -> Nested Loop - -> Append - Subplans Removed: 1 - -> Index Scan using foo1_1_a on foo1_1 foo_1 - Index Cond: (a = ANY (ARRAY[one(), two()])) - -> Seq Scan on foo1_2 foo_2 - Filter: ((a = one()) OR (a = two())) - -> Materialize - -> Append - Subplans Removed: 1 - -> Seq Scan on bar1 bar_1 - Filter: (a = one()) - -Update on foo - Update on foo1_1 foo_1 - Update on foo1_2 foo_2 - -> Append - Subplans Removed: 1 - -> Index Scan using foo1_1_a on foo1_1 foo_1 - Index Cond: (a = ANY (ARRAY[one(), two()])) - -> Seq Scan on foo1_2 foo_2 - Filter: ((a = one()) OR (a = two())) -(37 rows) - -step s2lock: SELECT pg_advisory_lock(12345); -pg_advisory_lock ----------------- - -(1 row) - -step s1exec3: LOAD 'delay_execution'; - SET delay_execution.executor_start_lock_id = 12345; - EXPLAIN (COSTS OFF) EXECUTE q3; <waiting ...> -step s2dropi: DROP INDEX foo1_1_a; -step s2unlock: SELECT pg_advisory_unlock(12345); -pg_advisory_unlock ------------------- -t -(1 row) - -step s1exec3: <... completed> -s1: NOTICE: Finished ExecutorStart(): CachedPlan is not valid -s1: NOTICE: Finished ExecutorStart(): CachedPlan is valid -s1: NOTICE: Finished ExecutorStart(): CachedPlan is valid -s1: NOTICE: Finished ExecutorStart(): CachedPlan is valid -QUERY PLAN --------------------------------------------------------- -Nested Loop - -> Append - Subplans Removed: 1 - -> Seq Scan on foo1_1 foo_1 - Filter: ((a = one()) OR (a = two())) - -> Seq Scan on foo1_2 foo_2 - Filter: ((a = one()) OR (a = two())) - -> Materialize - -> Append - Subplans Removed: 1 - -> Seq Scan on bar1 bar_1 - Filter: (a = one()) - -Update on bar - Update on bar1 bar_1 - -> Nested Loop - -> Append - Subplans Removed: 1 - -> Seq Scan on foo1_1 foo_1 - Filter: ((a = one()) OR (a = two())) - -> Seq Scan on foo1_2 foo_2 - Filter: ((a = one()) OR (a = two())) - -> Materialize - -> Append - Subplans Removed: 1 - -> Seq Scan on bar1 bar_1 - Filter: (a = one()) - -Update on foo - Update on foo1_1 foo_1 - Update on foo1_2 foo_2 - -> Append - Subplans Removed: 1 - -> Seq Scan on foo1_1 foo_1 - Filter: ((a = one()) OR (a = two())) - -> Seq Scan on foo1_2 foo_2 - Filter: ((a = one()) OR (a = two())) -(37 rows) - - -starting permutation: s1prep4 s2lock s1exec4 s2dropi s2unlock -step s1prep4: SET plan_cache_mode = force_generic_plan; - PREPARE q4 AS SELECT * FROM generate_series(1, 1) WHERE EXISTS (SELECT * FROM foov WHERE a = $1 FOR UPDATE); - EXPLAIN (COSTS OFF) EXECUTE q4 (1); -s1: NOTICE: Finished ExecutorStart(): CachedPlan is valid -QUERY PLAN -------------------------------------------------------------- -Result - One-Time Filter: (InitPlan 1).col1 - InitPlan 1 - -> LockRows - -> Append - Subplans Removed: 2 - -> Index Scan using foo1_1_a on foo1_1 foo_1 - Index Cond: (a = $1) - -> Function Scan on generate_series -(9 rows) - -step s2lock: SELECT pg_advisory_lock(12345); -pg_advisory_lock ----------------- - -(1 row) - -step s1exec4: LOAD 'delay_execution'; - SET delay_execution.executor_start_lock_id = 12345; - EXPLAIN (COSTS OFF) EXECUTE q4 (1); <waiting ...> -step s2dropi: DROP INDEX foo1_1_a; -step s2unlock: SELECT pg_advisory_unlock(12345); -pg_advisory_unlock ------------------- -t -(1 row) - -step s1exec4: <... completed> -s1: NOTICE: Finished ExecutorStart(): CachedPlan is not valid -s1: NOTICE: Finished ExecutorStart(): CachedPlan is valid -QUERY PLAN --------------------------------------------- -Result - One-Time Filter: (InitPlan 1).col1 - InitPlan 1 - -> LockRows - -> Append - Subplans Removed: 2 - -> Seq Scan on foo1_1 foo_1 - Filter: (a = $1) - -> Function Scan on generate_series -(9 rows) - diff --git a/src/test/modules/delay_execution/meson.build b/src/test/modules/delay_execution/meson.build index 58159bfc574..b53488f76d2 100644 --- a/src/test/modules/delay_execution/meson.build +++ b/src/test/modules/delay_execution/meson.build @@ -24,7 +24,6 @@ tests += { 'specs': [ 'partition-addition', 'partition-removal-1', - 'cached-plan-inval', ], }, } diff --git a/src/test/modules/delay_execution/specs/cached-plan-inval.spec b/src/test/modules/delay_execution/specs/cached-plan-inval.spec deleted file mode 100644 index f0cf06f9060..00000000000 --- a/src/test/modules/delay_execution/specs/cached-plan-inval.spec +++ /dev/null @@ -1,86 +0,0 @@ -# Test to check that invalidation of cached generic plans during ExecutorStart -# is correctly detected causing an updated plan to be re-executed. - -setup -{ - CREATE TABLE foo (a int, b text) PARTITION BY RANGE (a); - CREATE TABLE foo1 PARTITION OF foo FOR VALUES FROM (MINVALUE) TO (3) PARTITION BY RANGE (a); - CREATE TABLE foo1_1 PARTITION OF foo1 FOR VALUES FROM (MINVALUE) TO (2); - CREATE TABLE foo1_2 PARTITION OF foo1 FOR VALUES FROM (2) TO (3); - CREATE INDEX foo1_1_a ON foo1_1 (a); - CREATE TABLE foo2 PARTITION OF foo FOR VALUES FROM (3) TO (MAXVALUE); - INSERT INTO foo SELECT generate_series(-1000, 1000); - CREATE VIEW foov AS SELECT * FROM foo; - CREATE FUNCTION one () RETURNS int AS $$ BEGIN RETURN 1; END; $$ LANGUAGE PLPGSQL STABLE; - CREATE FUNCTION two () RETURNS int AS $$ BEGIN RETURN 2; END; $$ LANGUAGE PLPGSQL STABLE; - CREATE TABLE bar (a int, b text) PARTITION BY LIST(a); - CREATE TABLE bar1 PARTITION OF bar FOR VALUES IN (1); - CREATE INDEX ON bar1(a); - CREATE TABLE bar2 PARTITION OF bar FOR VALUES IN (2); - CREATE RULE update_foo AS ON UPDATE TO foo DO ALSO UPDATE bar SET a = a WHERE a = one(); - CREATE RULE update_bar AS ON UPDATE TO bar DO ALSO SELECT 1; - ANALYZE; -} - -teardown -{ - DROP VIEW foov; - DROP RULE update_foo ON foo; - DROP TABLE foo, bar; - DROP FUNCTION one(), two(); -} - -session "s1" -step "s1prep" { SET plan_cache_mode = force_generic_plan; - PREPARE q AS SELECT * FROM foov WHERE a = $1 FOR UPDATE; - EXPLAIN (COSTS OFF) EXECUTE q (1); } - -step "s1prep2" { SET plan_cache_mode = force_generic_plan; - PREPARE q2 AS SELECT * FROM foov WHERE a = one() or a = two(); - EXPLAIN (COSTS OFF) EXECUTE q2; } - -step "s1prep3" { SET plan_cache_mode = force_generic_plan; - PREPARE q3 AS UPDATE foov SET a = a WHERE a = one() or a = two(); - EXPLAIN (COSTS OFF) EXECUTE q3; } - -step "s1prep4" { SET plan_cache_mode = force_generic_plan; - PREPARE q4 AS SELECT * FROM generate_series(1, 1) WHERE EXISTS (SELECT * FROM foov WHERE a = $1 FOR UPDATE); - EXPLAIN (COSTS OFF) EXECUTE q4 (1); } - -step "s1exec" { LOAD 'delay_execution'; - SET delay_execution.executor_start_lock_id = 12345; - EXPLAIN (COSTS OFF) EXECUTE q (1); } -step "s1exec2" { LOAD 'delay_execution'; - SET delay_execution.executor_start_lock_id = 12345; - EXPLAIN (COSTS OFF) EXECUTE q2; } -step "s1exec3" { LOAD 'delay_execution'; - SET delay_execution.executor_start_lock_id = 12345; - EXPLAIN (COSTS OFF) EXECUTE q3; } -step "s1exec4" { LOAD 'delay_execution'; - SET delay_execution.executor_start_lock_id = 12345; - EXPLAIN (COSTS OFF) EXECUTE q4 (1); } - -session "s2" -step "s2lock" { SELECT pg_advisory_lock(12345); } -step "s2unlock" { SELECT pg_advisory_unlock(12345); } -step "s2dropi" { DROP INDEX foo1_1_a; } - -# In all permutations below, while "s1exec", "s1exec2", etc. wait to -# acquire the advisory lock, "s2drop" drops the index being used in the -# cached plan. When "s1exec" and others are unblocked and begin initializing -# the plan, including acquiring necessary locks on partitions, the concurrent -# index drop is detected. This causes plan initialization to be aborted, -# prompting the caller to retry with a new plan. - -# Case with runtime pruning using EXTERN parameter -permutation "s1prep" "s2lock" "s1exec" "s2dropi" "s2unlock" - -# Case with runtime pruning using stable function -permutation "s1prep2" "s2lock" "s1exec2" "s2dropi" "s2unlock" - -# Case with a rule adding another query causing the CachedPlan to contain -# multiple PlannedStmts -permutation "s1prep3" "s2lock" "s1exec3" "s2dropi" "s2unlock" - -# Case with run-time pruning inside a subquery -permutation "s1prep4" "s2lock" "s1exec4" "s2dropi" "s2unlock" |