diff --git a/src/test/regress/bin/normalize.sed b/src/test/regress/bin/normalize.sed index eb9dbab23..1ec8fd923 100644 --- a/src/test/regress/bin/normalize.sed +++ b/src/test/regress/bin/normalize.sed @@ -395,3 +395,14 @@ s/\/is still referenced from table/g s/^[[:space:]]*ERROR:[[:space:]]+subscription "[^"]+" could not connect to the publisher:[[:space:]]*/ERROR: could not connect to the publisher: /I # PG18: drop verbose 'connection to server … failed:' preamble s/^[[:space:]]*ERROR:[[:space:]]+could not connect to the publisher:[[:space:]]*connection to server .* failed:[[:space:]]*/ERROR: could not connect to the publisher: /I + +# PG18: replace named window refs like "OVER w1" with neutral "OVER (?)" +# this rule can be removed when PG18 is the minimum supported version +# only on Sort Key / Group Key / Output lines +# Sort Key +/^[[:space:]]*Sort Key:/ s/(OVER[[:space:]]+)w[0-9]+/\1(?)/g +# Group Key +/^[[:space:]]*Group Key:/ s/(OVER[[:space:]]+)w[0-9]+/\1(?)/g +# Output +/^[[:space:]]*Output:/ s/(OVER[[:space:]]+)w[0-9]+/\1(?)/g +# end PG18 window ref normalization diff --git a/src/test/regress/citus_tests/run_test.py b/src/test/regress/citus_tests/run_test.py index 03384ac28..294088f91 100755 --- a/src/test/regress/citus_tests/run_test.py +++ b/src/test/regress/citus_tests/run_test.py @@ -268,6 +268,12 @@ DEPS = { "subquery_in_targetlist": TestDeps( "minimal_schedule", ["multi_behavioral_analytics_create_table"] ), + "window_functions": TestDeps( + "minimal_schedule", ["multi_behavioral_analytics_create_table"] + ), + "multi_subquery_window_functions": TestDeps( + "minimal_schedule", ["multi_behavioral_analytics_create_table"] + ), } diff --git a/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py b/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py index b093b76c7..ba2259751 100755 --- a/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py +++ b/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py @@ -92,7 +92,6 @@ def run_citus_upgrade_tests(config, before_upgrade_schedule, after_upgrade_sched def get_citus_catalog_info(config): - results = {} # Store GUCs guc_results = utils.psql_capture( @@ -258,7 +257,6 @@ def compare_citus_catalog_info(config, pre_upgrade): def find_compatible_udf_signature(full_args, return_type, post_signatures): - pre_args_list = [arg.strip() for arg in full_args.split(",") if arg.strip()] for post_full_args, post_return_type in post_signatures: diff --git a/src/test/regress/expected/mixed_relkind_tests.out b/src/test/regress/expected/mixed_relkind_tests.out index b2c30d1e4..a2d0ea5fc 100644 --- a/src/test/regress/expected/mixed_relkind_tests.out +++ b/src/test/regress/expected/mixed_relkind_tests.out @@ -633,11 +633,13 @@ $Q$); (4 rows) -- pull to coordinator WINDOW +select public.explain_filter(' SELECT public.coordinator_plan($Q$ EXPLAIN (COSTS OFF) SELECT a, COUNT(*) OVER (PARTITION BY a+1) FROM partitioned_distributed_table ORDER BY 1,2; -$Q$); - coordinator_plan +$Q$) +', true); + explain_filter --------------------------------------------------------------------- Sort Sort Key: remote_scan.a, (count(*) OVER (?)) diff --git a/src/test/regress/expected/multi_explain.out b/src/test/regress/expected/multi_explain.out index 5d80f4ce4..e7f1ecac5 100644 --- a/src/test/regress/expected/multi_explain.out +++ b/src/test/regress/expected/multi_explain.out @@ -3118,7 +3118,9 @@ CREATE TABLE distributed_table_1(a int, b int); SELECT create_distributed_table('distributed_table_1','a'); INSERT INTO distributed_table_1 values (1,1); -EXPLAIN :default_analyze_flags SELECT row_number() OVER() AS r FROM distributed_table_1; +select public.explain_filter(' +EXPLAIN (ANALYZE on, COSTS off, TIMING off, SUMMARY off, BUFFERS off) SELECT row_number() OVER() AS r FROM distributed_table_1 +', true); WindowAgg (actual rows=1 loops=1) -> Custom Scan (Citus Adaptive) (actual rows=1 loops=1) Task Count: 2 @@ -3130,11 +3132,13 @@ CREATE TABLE distributed_table_2(a int, b int); SELECT create_distributed_table('distributed_table_2','a'); INSERT INTO distributed_table_2 VALUES (1,1); -EXPLAIN :default_analyze_flags +select public.explain_filter(' +EXPLAIN (ANALYZE on, COSTS off, TIMING off, SUMMARY off, BUFFERS off) WITH r AS (SELECT row_number() OVER () AS r FROM distributed_table_1) SELECT * FROM distributed_table_2 JOIN r ON (r = distributed_table_2.b) -LIMIT 3; +LIMIT 3 +', true); Limit (actual rows=1 loops=1) -> Custom Scan (Citus Adaptive) (actual rows=1 loops=1) -> Distributed Subplan XXX_1 diff --git a/src/test/regress/expected/multi_explain_0.out b/src/test/regress/expected/multi_explain_0.out index b7cdc0e11..60cd87316 100644 --- a/src/test/regress/expected/multi_explain_0.out +++ b/src/test/regress/expected/multi_explain_0.out @@ -3107,7 +3107,9 @@ CREATE TABLE distributed_table_1(a int, b int); SELECT create_distributed_table('distributed_table_1','a'); INSERT INTO distributed_table_1 values (1,1); -EXPLAIN :default_analyze_flags SELECT row_number() OVER() AS r FROM distributed_table_1; +select public.explain_filter(' +EXPLAIN (ANALYZE on, COSTS off, TIMING off, SUMMARY off, BUFFERS off) SELECT row_number() OVER() AS r FROM distributed_table_1 +', true); WindowAgg (actual rows=1 loops=1) -> Custom Scan (Citus Adaptive) (actual rows=1 loops=1) Task Count: 2 @@ -3119,11 +3121,13 @@ CREATE TABLE distributed_table_2(a int, b int); SELECT create_distributed_table('distributed_table_2','a'); INSERT INTO distributed_table_2 VALUES (1,1); -EXPLAIN :default_analyze_flags +select public.explain_filter(' +EXPLAIN (ANALYZE on, COSTS off, TIMING off, SUMMARY off, BUFFERS off) WITH r AS (SELECT row_number() OVER () AS r FROM distributed_table_1) SELECT * FROM distributed_table_2 JOIN r ON (r = distributed_table_2.b) -LIMIT 3; +LIMIT 3 +', true); Limit (actual rows=1 loops=1) -> Custom Scan (Citus Adaptive) (actual rows=1 loops=1) -> Distributed Subplan XXX_1 diff --git a/src/test/regress/expected/multi_outer_join_columns.out b/src/test/regress/expected/multi_outer_join_columns.out index 79527f7c6..3d32a8ef5 100644 --- a/src/test/regress/expected/multi_outer_join_columns.out +++ b/src/test/regress/expected/multi_outer_join_columns.out @@ -55,10 +55,12 @@ FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; 1 | (3 rows) +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) -FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; - QUERY PLAN +FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id +', true); + explain_filter --------------------------------------------------------------------- WindowAgg Output: remote_scan.id, max(remote_scan.max) OVER (?), remote_scan.worker_column_3 @@ -93,10 +95,12 @@ FROM t2 RIGHT OUTER JOIN t1 ON t1.id = t2.account_id; 1 | (3 rows) +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) -FROM t2 RIGHT OUTER JOIN t1 ON t1.id = t2.account_id; - QUERY PLAN +FROM t2 RIGHT OUTER JOIN t1 ON t1.id = t2.account_id +', true); + explain_filter --------------------------------------------------------------------- WindowAgg Output: remote_scan.id, max(remote_scan.max) OVER (?), remote_scan.worker_column_3 @@ -131,10 +135,12 @@ FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; 2 | 20 (3 rows) +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT DISTINCT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) -FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; - QUERY PLAN +FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id +', true); + explain_filter --------------------------------------------------------------------- HashAggregate Output: remote_scan.id, (max(remote_scan.max) OVER (?)), remote_scan.worker_column_3 @@ -302,11 +308,13 @@ HAVING COUNT(DISTINCT a2) > 1; 1 (1 row) +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT 1 FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id HAVING COUNT(DISTINCT a2) > 1; - QUERY PLAN +', true); + explain_filter --------------------------------------------------------------------- Aggregate Output: remote_scan."?column?" diff --git a/src/test/regress/expected/multi_outer_join_columns_1.out b/src/test/regress/expected/multi_outer_join_columns_1.out index 10dc6773d..4df79cc92 100644 --- a/src/test/regress/expected/multi_outer_join_columns_1.out +++ b/src/test/regress/expected/multi_outer_join_columns_1.out @@ -55,10 +55,12 @@ FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; 1 | (3 rows) +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) -FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; - QUERY PLAN +FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id +', true); + explain_filter --------------------------------------------------------------------- WindowAgg Output: remote_scan.id, max(remote_scan.max) OVER (?), remote_scan.worker_column_3 @@ -93,10 +95,12 @@ FROM t2 RIGHT OUTER JOIN t1 ON t1.id = t2.account_id; 1 | (3 rows) +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) -FROM t2 RIGHT OUTER JOIN t1 ON t1.id = t2.account_id; - QUERY PLAN +FROM t2 RIGHT OUTER JOIN t1 ON t1.id = t2.account_id +', true); + explain_filter --------------------------------------------------------------------- WindowAgg Output: remote_scan.id, max(remote_scan.max) OVER (?), remote_scan.worker_column_3 @@ -131,10 +135,12 @@ FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; 2 | 20 (3 rows) +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT DISTINCT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) -FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; - QUERY PLAN +FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id +', true); + explain_filter --------------------------------------------------------------------- HashAggregate Output: remote_scan.id, (max(remote_scan.max) OVER (?)), remote_scan.worker_column_3 @@ -299,11 +305,13 @@ HAVING COUNT(DISTINCT a2) > 1; 1 (1 row) +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT 1 FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id HAVING COUNT(DISTINCT a2) > 1; - QUERY PLAN +', true); + explain_filter --------------------------------------------------------------------- Aggregate Output: remote_scan."?column?" diff --git a/src/test/regress/expected/multi_subquery_window_functions.out b/src/test/regress/expected/multi_subquery_window_functions.out index aa4249efc..854752b63 100644 --- a/src/test/regress/expected/multi_subquery_window_functions.out +++ b/src/test/regress/expected/multi_subquery_window_functions.out @@ -675,6 +675,7 @@ LIMIT 2 | 1 (5 rows) +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT * FROM ( @@ -708,8 +709,9 @@ EXPLAIN (COSTS FALSE) GROUP BY user_id)) AS ftop ORDER BY 2 DESC, 1 DESC - LIMIT 5; - QUERY PLAN + LIMIT 5 +', true); + explain_filter --------------------------------------------------------------------- Limit -> Sort diff --git a/src/test/regress/expected/multi_test_helpers.out b/src/test/regress/expected/multi_test_helpers.out index 00c4a61d7..be46238b1 100644 --- a/src/test/regress/expected/multi_test_helpers.out +++ b/src/test/regress/expected/multi_test_helpers.out @@ -724,30 +724,42 @@ END; $$ language plpgsql; -- ignore details such as exact costs or row counts. These filter -- functions replace changeable output details with fixed strings. -- Copied from PG explain.sql -create function explain_filter(text) returns setof text -language plpgsql as +CREATE OR REPLACE FUNCTION explain_filter(cmd text, keep_numbers boolean DEFAULT false) +RETURNS SETOF text +LANGUAGE plpgsql AS $$ -declare +DECLARE ln text; -begin - for ln in execute $1 - loop +BEGIN + FOR ln IN EXECUTE cmd LOOP -- PG18 extra line "Index Searches: N" — remove entirely IF ln ~ '^[[:space:]]*Index[[:space:]]+Searches:[[:space:]]*[0-9]+[[:space:]]*$' THEN - CONTINUE; + CONTINUE; + END IF; + + -- PG18 extra Window line — remove entirely + IF ln ~ '^[[:space:]]*Window:[[:space:]].*$' THEN -- e.g., "Window: w1 AS (...)" + CONTINUE; + END IF; + + -- Optional numeric normalization + IF NOT keep_numbers THEN + -- Replace any numeric word with just 'N' + ln := regexp_replace(ln, '-?\m\d+\M', 'N', 'g'); + + -- In sort output, the above won't match units-suffixed numbers + ln := regexp_replace(ln, '\m\d+kB', 'NkB', 'g'); END IF; - -- Replace any numeric word with just 'N' - ln := regexp_replace(ln, '-?\m\d+\M', 'N', 'g'); - -- In sort output, the above won't match units-suffixed numbers - ln := regexp_replace(ln, '\m\d+kB', 'NkB', 'g'); -- Ignore text-mode buffers output because it varies depending -- on the system state CONTINUE WHEN (ln ~ ' +Buffers: .*'); + -- Ignore text-mode "Planning:" line because whether it's output -- varies depending on the system state CONTINUE WHEN (ln = 'Planning:'); - return next ln; - end loop; -end; + + RETURN NEXT ln; + END LOOP; +END; $$; diff --git a/src/test/regress/expected/pg17.out b/src/test/regress/expected/pg17.out index 2b3042ca1..b93e790b0 100644 --- a/src/test/regress/expected/pg17.out +++ b/src/test/regress/expected/pg17.out @@ -2069,7 +2069,7 @@ SET citus.grep_remote_commands TO '%12242024%'; select public.explain_filter('explain (memory) select * from int8_tbl i8'); NOTICE: issuing EXPLAIN (ANALYZE FALSE, VERBOSE FALSE, COSTS TRUE, BUFFERS FALSE, WAL FALSE, TIMING FALSE, SUMMARY FALSE, MEMORY TRUE, SERIALIZE none, FORMAT TEXT) SELECT q1, q2 FROM pg17.int8_tbl_12242024 i8 WHERE true DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXECUTE statement +CONTEXT: PL/pgSQL function public.explain_filter(text,boolean) line XX at FOR over EXECUTE statement explain_filter --------------------------------------------------------------------- Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) @@ -2086,8 +2086,8 @@ CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXEC select public.explain_filter('explain (memory, analyze, buffers false) select * from int8_tbl i8'); NOTICE: issuing SELECT * FROM worker_save_query_explain_analyze('SELECT q1, q2 FROM pg17.int8_tbl_12242024 i8 WHERE true', '{"verbose": false, "costs": true, "buffers": false, "wal": false, "memory": true, "serialize": "none", "timing": true, "summary": true, "format": "TEXT"}') AS (field_0 bigint, field_1 bigint) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXECUTE statement - explain_filter +CONTEXT: PL/pgSQL function public.explain_filter(text,boolean) line XX at FOR over EXECUTE statement + explain_filter --------------------------------------------------------------------- Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N) Task Count: N @@ -2109,7 +2109,7 @@ CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXEC select public.explain_filter('explain (memory, summary, format yaml) select * from int8_tbl i8'); NOTICE: issuing EXPLAIN (ANALYZE FALSE, VERBOSE FALSE, COSTS TRUE, BUFFERS FALSE, WAL FALSE, TIMING FALSE, SUMMARY TRUE, MEMORY TRUE, SERIALIZE none, FORMAT YAML) SELECT q1, q2 FROM pg17.int8_tbl_12242024 i8 WHERE true DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXECUTE statement +CONTEXT: PL/pgSQL function public.explain_filter(text,boolean) line XX at FOR over EXECUTE statement explain_filter --------------------------------------------------------------------- - Plan: + @@ -2152,7 +2152,7 @@ CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXEC select public.explain_filter('explain (memory, analyze, buffers false, format json) select * from int8_tbl i8'); NOTICE: issuing SELECT * FROM worker_save_query_explain_analyze('SELECT q1, q2 FROM pg17.int8_tbl_12242024 i8 WHERE true', '{"verbose": false, "costs": true, "buffers": false, "wal": false, "memory": true, "serialize": "none", "timing": true, "summary": true, "format": "JSON"}') AS (field_0 bigint, field_1 bigint) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXECUTE statement +CONTEXT: PL/pgSQL function public.explain_filter(text,boolean) line XX at FOR over EXECUTE statement explain_filter --------------------------------------------------------------------- [ + @@ -2230,7 +2230,7 @@ prepare int8_query as select * from int8_tbl i8; select public.explain_filter('explain (memory) execute int8_query'); NOTICE: issuing EXPLAIN (ANALYZE FALSE, VERBOSE FALSE, COSTS TRUE, BUFFERS FALSE, WAL FALSE, TIMING FALSE, SUMMARY FALSE, MEMORY TRUE, SERIALIZE none, FORMAT TEXT) SELECT q1, q2 FROM pg17.int8_tbl_12242024 i8 WHERE true DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXECUTE statement +CONTEXT: PL/pgSQL function public.explain_filter(text,boolean) line XX at FOR over EXECUTE statement explain_filter --------------------------------------------------------------------- Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) @@ -2248,7 +2248,7 @@ CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXEC select public.explain_filter('explain (analyze, serialize, buffers, format yaml) select * from int8_tbl i8'); NOTICE: issuing SELECT * FROM worker_save_query_explain_analyze('SELECT q1, q2 FROM pg17.int8_tbl_12242024 i8 WHERE true', '{"verbose": false, "costs": true, "buffers": true, "wal": false, "memory": false, "serialize": "text", "timing": true, "summary": true, "format": "YAML"}') AS (field_0 bigint, field_1 bigint) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXECUTE statement +CONTEXT: PL/pgSQL function public.explain_filter(text,boolean) line XX at FOR over EXECUTE statement explain_filter --------------------------------------------------------------------- - Plan: + @@ -2369,8 +2369,8 @@ CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXEC select public.explain_filter('explain (analyze, buffers false, serialize) select * from int8_tbl i8'); NOTICE: issuing SELECT * FROM worker_save_query_explain_analyze('SELECT q1, q2 FROM pg17.int8_tbl_12242024 i8 WHERE true', '{"verbose": false, "costs": true, "buffers": false, "wal": false, "memory": false, "serialize": "text", "timing": true, "summary": true, "format": "TEXT"}') AS (field_0 bigint, field_1 bigint) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXECUTE statement - explain_filter +CONTEXT: PL/pgSQL function public.explain_filter(text,boolean) line XX at FOR over EXECUTE statement + explain_filter --------------------------------------------------------------------- Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N) Task Count: N @@ -2391,8 +2391,8 @@ CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXEC select public.explain_filter('explain (analyze,serialize text,buffers,timing off) select * from int8_tbl i8'); NOTICE: issuing SELECT * FROM worker_save_query_explain_analyze('SELECT q1, q2 FROM pg17.int8_tbl_12242024 i8 WHERE true', '{"verbose": false, "costs": true, "buffers": true, "wal": false, "memory": false, "serialize": "text", "timing": false, "summary": true, "format": "TEXT"}') AS (field_0 bigint, field_1 bigint) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXECUTE statement - explain_filter +CONTEXT: PL/pgSQL function public.explain_filter(text,boolean) line XX at FOR over EXECUTE statement + explain_filter --------------------------------------------------------------------- Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) (actual rows=N loops=N) Task Count: N @@ -2413,8 +2413,8 @@ CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXEC select public.explain_filter('explain (analyze,serialize binary,buffers,timing) select * from int8_tbl i8'); NOTICE: issuing SELECT * FROM worker_save_query_explain_analyze('SELECT q1, q2 FROM pg17.int8_tbl_12242024 i8 WHERE true', '{"verbose": false, "costs": true, "buffers": true, "wal": false, "memory": false, "serialize": "binary", "timing": true, "summary": true, "format": "TEXT"}') AS (field_0 bigint, field_1 bigint) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXECUTE statement - explain_filter +CONTEXT: PL/pgSQL function public.explain_filter(text,boolean) line XX at FOR over EXECUTE statement + explain_filter --------------------------------------------------------------------- Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N) Task Count: N @@ -2436,8 +2436,8 @@ CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXEC select public.explain_filter('explain (analyze, buffers false, serialize) create temp table explain_temp as select * from int8_tbl i8'); NOTICE: issuing SELECT * FROM worker_save_query_explain_analyze('SELECT q1, q2 FROM pg17.int8_tbl_12242024 i8 WHERE true', '{"verbose": false, "costs": true, "buffers": false, "wal": false, "memory": false, "serialize": "text", "timing": true, "summary": true, "format": "TEXT"}') AS (field_0 bigint, field_1 bigint) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -CONTEXT: PL/pgSQL function public.explain_filter(text) line XX at FOR over EXECUTE statement - explain_filter +CONTEXT: PL/pgSQL function public.explain_filter(text,boolean) line XX at FOR over EXECUTE statement + explain_filter --------------------------------------------------------------------- Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N) Task Count: N diff --git a/src/test/regress/expected/window_functions.out b/src/test/regress/expected/window_functions.out index d4718c4dd..ba5fa6b85 100644 --- a/src/test/regress/expected/window_functions.out +++ b/src/test/regress/expected/window_functions.out @@ -1292,6 +1292,7 @@ ORDER BY user_id, avg(value_1) DESC; 6 | 1.00000000000000000000 | 5 (32 rows) +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, @@ -1300,8 +1301,9 @@ SELECT FROM users_table GROUP BY user_id, value_2 -ORDER BY user_id, avg(value_1) DESC; - QUERY PLAN +ORDER BY user_id, avg(value_1) DESC +', true); + explain_filter --------------------------------------------------------------------- Sort Sort Key: remote_scan.user_id, remote_scan.avg DESC @@ -1364,6 +1366,7 @@ ORDER BY user_id, avg(value_1) DESC; (32 rows) -- limit is not pushed down to worker !! +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, @@ -1373,8 +1376,9 @@ FROM users_table GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC -LIMIT 5; - QUERY PLAN +LIMIT 5 +', true); + explain_filter --------------------------------------------------------------------- Limit -> Sort @@ -1395,6 +1399,7 @@ LIMIT 5; -> Seq Scan on users_table_1400256 users_table (17 rows) +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, @@ -1404,8 +1409,9 @@ FROM users_table GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC -LIMIT 5; - QUERY PLAN +LIMIT 5 +', true); + explain_filter --------------------------------------------------------------------- Limit -> Sort @@ -1426,6 +1432,7 @@ LIMIT 5; -> Seq Scan on users_table_1400256 users_table (17 rows) +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, @@ -1435,8 +1442,9 @@ FROM users_table GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC -LIMIT 5; - QUERY PLAN +LIMIT 5 +', true); + explain_filter --------------------------------------------------------------------- Limit -> Sort @@ -1457,6 +1465,7 @@ LIMIT 5; -> Seq Scan on users_table_1400256 users_table (17 rows) +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, @@ -1466,8 +1475,9 @@ FROM users_table GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC -LIMIT 5; - QUERY PLAN +LIMIT 5 +', true); + explain_filter --------------------------------------------------------------------- Limit -> Sort @@ -1489,10 +1499,12 @@ LIMIT 5; (17 rows) -- Grouping can be pushed down with aggregates even when window function can't +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, count(value_1), stddev(value_1), count(user_id) OVER (PARTITION BY random()) -FROM users_table GROUP BY user_id HAVING avg(value_1) > 2 LIMIT 1; - QUERY PLAN +FROM users_table GROUP BY user_id HAVING avg(value_1) > 2 LIMIT 1 +', true); + explain_filter --------------------------------------------------------------------- Limit -> WindowAgg @@ -1534,6 +1546,7 @@ SELECT create_distributed_table('daily_uniques', 'user_id'); (1 row) +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, sum(value_2) AS commits, @@ -1547,8 +1560,9 @@ GROUP BY user_id HAVING sum(value_2) > 0 ORDER BY commits DESC -LIMIT 10; - QUERY PLAN +LIMIT 10 +', true); + explain_filter --------------------------------------------------------------------- Limit -> Sort diff --git a/src/test/regress/sql/mixed_relkind_tests.sql b/src/test/regress/sql/mixed_relkind_tests.sql index 6b7463cfd..7c0a7e4b2 100644 --- a/src/test/regress/sql/mixed_relkind_tests.sql +++ b/src/test/regress/sql/mixed_relkind_tests.sql @@ -232,10 +232,12 @@ SELECT a, COUNT(*) OVER (PARTITION BY a) FROM partitioned_distributed_table ORDE $Q$); -- pull to coordinator WINDOW +select public.explain_filter(' SELECT public.coordinator_plan($Q$ EXPLAIN (COSTS OFF) SELECT a, COUNT(*) OVER (PARTITION BY a+1) FROM partitioned_distributed_table ORDER BY 1,2; -$Q$); +$Q$) +', true); -- FOR UPDATE SELECT * FROM partitioned_distributed_table WHERE a = 1 ORDER BY 1,2 FOR UPDATE; diff --git a/src/test/regress/sql/multi_explain.sql b/src/test/regress/sql/multi_explain.sql index 437c54218..408130fde 100644 --- a/src/test/regress/sql/multi_explain.sql +++ b/src/test/regress/sql/multi_explain.sql @@ -1137,17 +1137,21 @@ CREATE TABLE distributed_table_1(a int, b int); SELECT create_distributed_table('distributed_table_1','a'); INSERT INTO distributed_table_1 values (1,1); -EXPLAIN :default_analyze_flags SELECT row_number() OVER() AS r FROM distributed_table_1; +select public.explain_filter(' +EXPLAIN (ANALYZE on, COSTS off, TIMING off, SUMMARY off, BUFFERS off) SELECT row_number() OVER() AS r FROM distributed_table_1 +', true); CREATE TABLE distributed_table_2(a int, b int); SELECT create_distributed_table('distributed_table_2','a'); INSERT INTO distributed_table_2 VALUES (1,1); -EXPLAIN :default_analyze_flags +select public.explain_filter(' +EXPLAIN (ANALYZE on, COSTS off, TIMING off, SUMMARY off, BUFFERS off) WITH r AS (SELECT row_number() OVER () AS r FROM distributed_table_1) SELECT * FROM distributed_table_2 JOIN r ON (r = distributed_table_2.b) -LIMIT 3; +LIMIT 3 +', true); EXPLAIN :default_analyze_flags SELECT FROM (SELECT * FROM reference_table) subquery; diff --git a/src/test/regress/sql/multi_outer_join_columns.sql b/src/test/regress/sql/multi_outer_join_columns.sql index eec111cb5..8e49c4bcf 100644 --- a/src/test/regress/sql/multi_outer_join_columns.sql +++ b/src/test/regress/sql/multi_outer_join_columns.sql @@ -32,21 +32,27 @@ SELECT create_distributed_table('t2', 'account_id'); -- produces a non-empty varnullingrels set in PG 16 (and higher) SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) -FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; +FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id +', true); SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) FROM t2 RIGHT OUTER JOIN t1 ON t1.id = t2.account_id; +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) -FROM t2 RIGHT OUTER JOIN t1 ON t1.id = t2.account_id; +FROM t2 RIGHT OUTER JOIN t1 ON t1.id = t2.account_id +', true); SELECT DISTINCT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT DISTINCT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) -FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; +FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id +', true); CREATE SEQUENCE test_seq START 101; CREATE OR REPLACE FUNCTION TEST_F(int) returns INT language sql stable as $$ select $1 + 42; $$ ; @@ -86,10 +92,12 @@ FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id; SELECT 1 FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id HAVING COUNT(DISTINCT a2) > 1; +select public.explain_filter(' EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) SELECT 1 FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id HAVING COUNT(DISTINCT a2) > 1; +', true); -- Check right outer join SELECT COUNT(DISTINCT a2) diff --git a/src/test/regress/sql/multi_subquery_window_functions.sql b/src/test/regress/sql/multi_subquery_window_functions.sql index 706aa64fa..913d69a92 100644 --- a/src/test/regress/sql/multi_subquery_window_functions.sql +++ b/src/test/regress/sql/multi_subquery_window_functions.sql @@ -441,6 +441,7 @@ ORDER BY LIMIT 5; +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT * FROM ( @@ -474,7 +475,8 @@ EXPLAIN (COSTS FALSE) GROUP BY user_id)) AS ftop ORDER BY 2 DESC, 1 DESC - LIMIT 5; + LIMIT 5 +', true); -- test with window functions which aren't pushed down SELECT diff --git a/src/test/regress/sql/multi_test_helpers.sql b/src/test/regress/sql/multi_test_helpers.sql index e605e7e90..f6829ead0 100644 --- a/src/test/regress/sql/multi_test_helpers.sql +++ b/src/test/regress/sql/multi_test_helpers.sql @@ -755,30 +755,43 @@ END; $$ language plpgsql; -- functions replace changeable output details with fixed strings. -- Copied from PG explain.sql -create function explain_filter(text) returns setof text -language plpgsql as +CREATE OR REPLACE FUNCTION explain_filter(cmd text, keep_numbers boolean DEFAULT false) +RETURNS SETOF text +LANGUAGE plpgsql AS $$ -declare +DECLARE ln text; -begin - for ln in execute $1 - loop +BEGIN + FOR ln IN EXECUTE cmd LOOP -- PG18 extra line "Index Searches: N" — remove entirely IF ln ~ '^[[:space:]]*Index[[:space:]]+Searches:[[:space:]]*[0-9]+[[:space:]]*$' THEN - CONTINUE; + CONTINUE; + END IF; + + -- PG18 extra Window line — remove entirely + IF ln ~ '^[[:space:]]*Window:[[:space:]].*$' THEN -- e.g., "Window: w1 AS (...)" + CONTINUE; + END IF; + + -- Optional numeric normalization + IF NOT keep_numbers THEN + -- Replace any numeric word with just 'N' + ln := regexp_replace(ln, '-?\m\d+\M', 'N', 'g'); + + -- In sort output, the above won't match units-suffixed numbers + ln := regexp_replace(ln, '\m\d+kB', 'NkB', 'g'); END IF; - -- Replace any numeric word with just 'N' - ln := regexp_replace(ln, '-?\m\d+\M', 'N', 'g'); - -- In sort output, the above won't match units-suffixed numbers - ln := regexp_replace(ln, '\m\d+kB', 'NkB', 'g'); -- Ignore text-mode buffers output because it varies depending -- on the system state CONTINUE WHEN (ln ~ ' +Buffers: .*'); + -- Ignore text-mode "Planning:" line because whether it's output -- varies depending on the system state CONTINUE WHEN (ln = 'Planning:'); - return next ln; - end loop; -end; + + RETURN NEXT ln; + END LOOP; +END; $$; + diff --git a/src/test/regress/sql/window_functions.sql b/src/test/regress/sql/window_functions.sql index 2f7ea18d2..4a59ee9b9 100644 --- a/src/test/regress/sql/window_functions.sql +++ b/src/test/regress/sql/window_functions.sql @@ -508,6 +508,7 @@ FROM GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC; +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, @@ -516,7 +517,8 @@ SELECT FROM users_table GROUP BY user_id, value_2 -ORDER BY user_id, avg(value_1) DESC; +ORDER BY user_id, avg(value_1) DESC +', true); -- order by in the window function is same as avg(value_1) DESC SELECT @@ -529,6 +531,7 @@ GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC; -- limit is not pushed down to worker !! +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, @@ -538,8 +541,10 @@ FROM users_table GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC -LIMIT 5; +LIMIT 5 +', true); +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, @@ -549,8 +554,10 @@ FROM users_table GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC -LIMIT 5; +LIMIT 5 +', true); +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, @@ -560,8 +567,10 @@ FROM users_table GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC -LIMIT 5; +LIMIT 5 +', true); +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, @@ -571,12 +580,15 @@ FROM users_table GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC -LIMIT 5; +LIMIT 5 +', true); -- Grouping can be pushed down with aggregates even when window function can't +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, count(value_1), stddev(value_1), count(user_id) OVER (PARTITION BY random()) -FROM users_table GROUP BY user_id HAVING avg(value_1) > 2 LIMIT 1; +FROM users_table GROUP BY user_id HAVING avg(value_1) > 2 LIMIT 1 +', true); -- Window function with inlined CTE WITH cte as ( @@ -594,6 +606,7 @@ ORDER BY 1; CREATE TABLE daily_uniques (value_2 float, user_id bigint); SELECT create_distributed_table('daily_uniques', 'user_id'); +select public.explain_filter(' EXPLAIN (COSTS FALSE) SELECT user_id, sum(value_2) AS commits, @@ -607,7 +620,8 @@ GROUP BY user_id HAVING sum(value_2) > 0 ORDER BY commits DESC -LIMIT 10; +LIMIT 10 +', true); DROP TABLE daily_uniques;