diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 29de73c060..1428529b04 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -7,6 +7,7 @@ include $(top_builddir)/src/Makefile.global SUBDIRS = \ brin \ commit_ts \ + delay_execution \ dummy_index_am \ dummy_seclabel \ snapshot_too_old \ diff --git a/src/test/modules/delay_execution/.gitignore b/src/test/modules/delay_execution/.gitignore new file mode 100644 index 0000000000..ba2160b66c --- /dev/null +++ b/src/test/modules/delay_execution/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/output_iso/ +/tmp_check_iso/ diff --git a/src/test/modules/delay_execution/Makefile b/src/test/modules/delay_execution/Makefile new file mode 100644 index 0000000000..f270aebf3a --- /dev/null +++ b/src/test/modules/delay_execution/Makefile @@ -0,0 +1,21 @@ +# src/test/modules/delay_execution/Makefile + +PGFILEDESC = "delay_execution - allow delay between parsing and execution" + +MODULE_big = delay_execution +OBJS = \ + $(WIN32RES) \ + delay_execution.o + +ISOLATION = partition-addition + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/delay_execution +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/delay_execution/delay_execution.c b/src/test/modules/delay_execution/delay_execution.c new file mode 100644 index 0000000000..03ea23d0f2 --- /dev/null +++ b/src/test/modules/delay_execution/delay_execution.c @@ -0,0 +1,104 @@ +/*------------------------------------------------------------------------- + * + * delay_execution.c + * 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 + * between parsing and execution of any desired query. + * + * Copyright (c) 2020, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/delay_execution/delay_execution.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include + +#include "optimizer/planner.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/inval.h" + + +PG_MODULE_MAGIC; + +/* GUC: advisory lock ID to use. Zero disables the feature. */ +static int post_planning_lock_id = 0; + +/* Save previous planner hook user to be a good citizen */ +static planner_hook_type prev_planner_hook = NULL; + +/* Module load/unload functions */ +void _PG_init(void); +void _PG_fini(void); + + +/* planner_hook function to provide the desired delay */ +static PlannedStmt * +delay_execution_planner(Query *parse, const char *query_string, + int cursorOptions, ParamListInfo boundParams) +{ + PlannedStmt *result; + + /* Invoke the planner, possibly via a previous hook user */ + if (prev_planner_hook) + result = prev_planner_hook(parse, query_string, cursorOptions, + boundParams); + else + result = standard_planner(parse, query_string, cursorOptions, + boundParams); + + /* If enabled, delay by taking and releasing the specified lock */ + if (post_planning_lock_id != 0) + { + DirectFunctionCall1(pg_advisory_lock_int8, + Int64GetDatum((int64) post_planning_lock_id)); + DirectFunctionCall1(pg_advisory_unlock_int8, + Int64GetDatum((int64) post_planning_lock_id)); + + /* + * Ensure that we notice any pending invalidations, since the advisory + * lock functions don't do this. + */ + AcceptInvalidationMessages(); + } + + return result; +} + +/* Module load function */ +void +_PG_init(void) +{ + /* 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.", + &post_planning_lock_id, + 0, + 0, INT_MAX, + PGC_USERSET, + 0, + NULL, + NULL, + NULL); + + /* Install our hook */ + prev_planner_hook = planner_hook; + planner_hook = delay_execution_planner; +} + +/* Module unload function (pro forma, not used currently) */ +void +_PG_fini(void) +{ + planner_hook = prev_planner_hook; +} diff --git a/src/test/modules/delay_execution/expected/partition-addition.out b/src/test/modules/delay_execution/expected/partition-addition.out new file mode 100644 index 0000000000..7c91090eef --- /dev/null +++ b/src/test/modules/delay_execution/expected/partition-addition.out @@ -0,0 +1,21 @@ +Parsed test spec with 2 sessions + +starting permutation: s2lock s1exec s2addp s2unlock +step s2lock: SELECT pg_advisory_lock(12345); +pg_advisory_lock + + +step s1exec: LOAD 'delay_execution'; + SET delay_execution.post_planning_lock_id = 12345; + SELECT * FROM foo WHERE a <> 1 AND a <> (SELECT 3); +step s2addp: CREATE TABLE foo2 (LIKE foo); + ALTER TABLE foo ATTACH PARTITION foo2 FOR VALUES IN (2); + INSERT INTO foo VALUES (2, 'ADD2'); +step s2unlock: SELECT pg_advisory_unlock(12345); +pg_advisory_unlock + +t +step s1exec: <... completed> +a b + +4 GHI diff --git a/src/test/modules/delay_execution/specs/partition-addition.spec b/src/test/modules/delay_execution/specs/partition-addition.spec new file mode 100644 index 0000000000..2a0948247e --- /dev/null +++ b/src/test/modules/delay_execution/specs/partition-addition.spec @@ -0,0 +1,38 @@ +# Test addition of a partition with less-than-exclusive locking. + +setup +{ + CREATE TABLE foo (a int, b text) PARTITION BY LIST(a); + CREATE TABLE foo1 PARTITION OF foo FOR VALUES IN (1); + CREATE TABLE foo3 PARTITION OF foo FOR VALUES IN (3); + CREATE TABLE foo4 PARTITION OF foo FOR VALUES IN (4); + INSERT INTO foo VALUES (1, 'ABC'); + INSERT INTO foo VALUES (3, 'DEF'); + INSERT INTO foo VALUES (4, 'GHI'); +} + +teardown +{ + DROP TABLE foo; +} + +# The SELECT will be planned with just the three partitions shown above, +# of which we expect foo1 to be pruned at planning and foo3 at execution. +# Then we'll block, and by the time the query is actually executed, +# partition foo2 will also exist. We expect that not to be scanned. +# This test is specifically designed to check ExecCreatePartitionPruneState's +# code for matching up the partition lists in such cases. + +session "s1" +step "s1exec" { LOAD 'delay_execution'; + SET delay_execution.post_planning_lock_id = 12345; + SELECT * FROM foo WHERE a <> 1 AND a <> (SELECT 3); } + +session "s2" +step "s2lock" { SELECT pg_advisory_lock(12345); } +step "s2unlock" { SELECT pg_advisory_unlock(12345); } +step "s2addp" { CREATE TABLE foo2 (LIKE foo); + ALTER TABLE foo ATTACH PARTITION foo2 FOR VALUES IN (2); + INSERT INTO foo VALUES (2, 'ADD2'); } + +permutation "s2lock" "s1exec" "s2addp" "s2unlock"