LOAD 'pg_disorder'; SHOW pg_disorder.mode; SHOW pg_disorder.seed; CREATE TABLE t (id int, name text); INSERT INTO t SELECT g, 'row' || g FROM generate_series(1, 20) g; SET pg_disorder.mode = 'shuffle'; SET pg_disorder.seed = 12345; SELECT string_agg(id::text, ',' ORDER BY id) AS ordered_ids FROM t; SELECT id FROM t ORDER BY id DESC LIMIT 3; CREATE TEMP TABLE cap AS SELECT id FROM t; SELECT array_agg(id ORDER BY id) AS multiset_preserved FROM cap; SELECT count(*) AS n_rows FROM cap; SET max_parallel_workers_per_gather = 0; CREATE TEMP TABLE shuffle_proof AS SELECT id FROM t; SELECT (SELECT array_agg(id) FROM shuffle_proof) IS DISTINCT FROM (SELECT array_agg(id ORDER BY id) FROM t) AS shuffle_happened; CREATE TEMP TABLE cap5 AS SELECT id FROM t LIMIT 5; SELECT count(*) AS limited FROM cap5; SELECT DISTINCT id FROM t WHERE id <= 3 ORDER BY id; SELECT count(*) AS union_rows FROM (SELECT id FROM t UNION SELECT id FROM t) u; SELECT count(*) AS grouped_rows FROM (SELECT id FROM t GROUP BY id) g; SELECT count(*) AS total FROM t; SELECT id FROM t WHERE false; CREATE TEMP TABLE t_ins (id int); INSERT INTO t_ins SELECT id FROM t; SELECT count(*) AS inserted FROM t_ins; CREATE FUNCTION pg_disorder_simple_expr() RETURNS int AS $$ DECLARE n int := 42; BEGIN RETURN n; END; $$ LANGUAGE plpgsql; SELECT pg_disorder_simple_expr() AS simple_expr; CREATE FUNCTION pg_disorder_nested_scan() RETURNS bigint AS $$ DECLARE c bigint := 0; r record; BEGIN FOR r IN SELECT id FROM t LOOP c := c + 1; END LOOP; RETURN c; END; $$ LANGUAGE plpgsql; SELECT pg_disorder_nested_scan() AS nested_rows; -- reverse mode is deterministic, so unlike shuffle its exact row order can be -- asserted. On a freshly loaded table the physical scan order is the insertion -- order, so a top-level unordered scan comes back reversed: 20..1. (This must -- be a bare top-level SELECT: wrapping it in an aggregate or a subquery would -- make it ineligible and it would not be perturbed at all.) SET pg_disorder.mode = 'reverse'; SELECT id FROM t; -- an explicit ORDER BY is still not perturbed SELECT id FROM t ORDER BY id LIMIT 3; -- the row count is preserved SELECT count(*) AS reverse_total FROM t; -- an excluded shape still returns the full correct result under reverse SELECT array_agg(id ORDER BY id) AS grouped_multiset FROM (SELECT id FROM t WHERE id <= 5 GROUP BY id) g; -- DO and CALL reach the executor through ProcessUtility rather than -- ExecutorRun, so their bodies must still be recognised as nested and left -- unperturbed. The second SELECT is the important one: eligibility is decided -- at plan time and cached, so a body perturbed here would leave a poisoned plan -- behind and keep returning the wrong row from ordinary nested calls too. CREATE FUNCTION pg_disorder_first_id() RETURNS int AS $$ DECLARE v int; BEGIN SELECT id INTO v FROM t LIMIT 1; RETURN v; END; $$ LANGUAGE plpgsql; DO $$ BEGIN RAISE NOTICE 'do block sees %', pg_disorder_first_id(); END $$; SELECT pg_disorder_first_id() AS after_do_block; CREATE PROCEDURE pg_disorder_scan_proc() AS $$ DECLARE c bigint := 0; r record; BEGIN FOR r IN SELECT id FROM t LOOP c := c + 1; END LOOP; RAISE NOTICE 'call body scanned % rows, first id %', c, pg_disorder_first_id(); END; $$ LANGUAGE plpgsql; CALL pg_disorder_scan_proc(); -- ... while a utility statement that merely carries a query is still perturbed. -- The read-back is an aggregate, so it is ineligible and reports the temp -- table's physical order, which is the perturbed order the CTAS wrote: 20..1. CREATE TEMP TABLE reverse_ctas AS SELECT id FROM t; SELECT (array_agg(id))[1:3] AS ctas_wrote_reversed FROM reverse_ctas; SELECT id FROM t LIMIT 5; SELECT id FROM t FOR SHARE LIMIT 5; SELECT * FROM (SELECT id FROM t FOR UPDATE) s LIMIT 5; WITH locked AS (SELECT id FROM t FOR UPDATE) SELECT * FROM locked LIMIT 5; SELECT id FROM (SELECT id FROM t) plain LIMIT 5; WITH RECURSIVE r(n) AS (VALUES (1) UNION ALL SELECT n + 1 FROM r) SELECT * FROM r LIMIT 3; WITH plain_cte AS (SELECT id FROM t) SELECT * FROM plain_cte LIMIT 3; CREATE FUNCTION pg_disorder_sql_exec_arg() RETURNS int LANGUAGE sql AS $$ SELECT id FROM t LIMIT 1; $$; PREPARE pg_disorder_param(int) AS SELECT $1 AS v; EXECUTE pg_disorder_param(pg_disorder_sql_exec_arg()); CREATE TEMP TABLE pg_disorder_ctas_exec AS EXECUTE pg_disorder_param(pg_disorder_sql_exec_arg()); SELECT v AS ctas_execute_param FROM pg_disorder_ctas_exec; SELECT pg_disorder_sql_exec_arg() AS execute_param_left_no_poisoned_plan; DEALLOCATE pg_disorder_param; DROP FUNCTION pg_disorder_sql_exec_arg(); PREPARE pg_disorder_noparam AS SELECT id FROM t; EXECUTE pg_disorder_noparam; DEALLOCATE pg_disorder_noparam; BEGIN; DECLARE pg_disorder_cur CURSOR FOR SELECT id FROM t; FETCH 3 FROM pg_disorder_cur; COMMIT; COPY (SELECT id FROM t LIMIT 3) TO stdout; CREATE TEMP TABLE reverse_insert_select (id int); INSERT INTO reverse_insert_select SELECT id FROM t; SELECT (array_agg(id))[1:3] AS insert_select_kept_order FROM reverse_insert_select; DO $$ BEGIN EXECUTE 'CREATE TEMP TABLE reverse_ctas_in_do AS SELECT id FROM t'; END $$; SELECT (array_agg(id))[1:3] AS ctas_in_do_not_perturbed FROM reverse_ctas_in_do; CREATE TABLE pg_disorder_trigger_log (v int); CREATE FUNCTION pg_disorder_log_first_id() RETURNS trigger AS $$ DECLARE v int; BEGIN SELECT id INTO v FROM t LIMIT 1; INSERT INTO pg_disorder_trigger_log VALUES (v); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TABLE pg_disorder_copy_target (x int); CREATE TRIGGER pg_disorder_copy_trg BEFORE INSERT ON pg_disorder_copy_target FOR EACH ROW EXECUTE FUNCTION pg_disorder_log_first_id(); COPY pg_disorder_copy_target FROM stdin; 1 \. SELECT v AS trigger_under_copy FROM pg_disorder_trigger_log; TRUNCATE pg_disorder_trigger_log; INSERT INTO pg_disorder_copy_target VALUES (2); SELECT v AS trigger_under_insert FROM pg_disorder_trigger_log; CREATE TABLE pg_disorder_deferred (x int); CREATE CONSTRAINT TRIGGER pg_disorder_deferred_trg AFTER INSERT ON pg_disorder_deferred DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION pg_disorder_log_first_id(); TRUNCATE pg_disorder_trigger_log; BEGIN; INSERT INTO pg_disorder_deferred VALUES (1); COMMIT; SELECT v AS trigger_deferred_to_commit FROM pg_disorder_trigger_log; CREATE PROCEDURE pg_disorder_commit_proc() AS $$ BEGIN INSERT INTO pg_disorder_deferred VALUES (2); COMMIT; END; $$ LANGUAGE plpgsql; CALL pg_disorder_commit_proc(); SELECT count(*) AS committed_inside_procedure FROM pg_disorder_deferred; CREATE FUNCTION pg_disorder_secdef_first_id() RETURNS int SECURITY DEFINER SET work_mem = '4MB' AS $$ DECLARE v int; BEGIN SELECT id INTO v FROM t LIMIT 1; RETURN v; END; $$ LANGUAGE plpgsql; SELECT pg_disorder_secdef_first_id() AS security_definer_body; CREATE FUNCTION pg_disorder_sql_immutable() RETURNS int LANGUAGE sql IMMUTABLE AS $$ SELECT id FROM t LIMIT 1; $$; CREATE FUNCTION pg_disorder_sql_values() RETURNS int LANGUAGE sql IMMUTABLE AS $$ SELECT x FROM (VALUES (1), (2), (3)) v(x) LIMIT 1; $$; SELECT pg_disorder_sql_immutable() AS sql_body_const_folded, pg_disorder_sql_values() AS sql_values_body_const_folded; SELECT pg_disorder_sql_immutable() AS sql_body_const_folded_again, pg_disorder_sql_values() AS sql_values_body_const_folded_again; DROP FUNCTION pg_disorder_sql_immutable(); DROP FUNCTION pg_disorder_sql_values(); CREATE FUNCTION pg_disorder_sql_first_id() RETURNS int LANGUAGE sql AS $$ SELECT 1; SELECT id FROM t LIMIT 1; $$; CREATE TABLE pg_disorder_rewrite (a int); INSERT INTO pg_disorder_rewrite VALUES (1); ALTER TABLE pg_disorder_rewrite ADD COLUMN b int DEFAULT pg_disorder_sql_first_id(); SELECT b AS sql_function_under_table_rewrite FROM pg_disorder_rewrite; CREATE TABLE pg_disorder_sql_default (a int, b int DEFAULT pg_disorder_sql_first_id()); COPY pg_disorder_sql_default (a) FROM stdin; 1 \. SELECT b AS sql_function_under_copy_default FROM pg_disorder_sql_default; DROP TABLE pg_disorder_rewrite; DROP TABLE pg_disorder_sql_default; DROP FUNCTION pg_disorder_sql_first_id(); DROP TABLE pg_disorder_copy_target; DROP TABLE pg_disorder_deferred; DROP TABLE pg_disorder_trigger_log; DROP PROCEDURE pg_disorder_commit_proc(); DROP PROCEDURE pg_disorder_scan_proc(); DROP FUNCTION pg_disorder_log_first_id(); DROP FUNCTION pg_disorder_secdef_first_id(); DROP FUNCTION pg_disorder_first_id(); DROP FUNCTION pg_disorder_nested_scan(); DROP FUNCTION pg_disorder_simple_expr(); DROP TABLE t;