PG18 - Normalize window output and add filter (#8344)

fixes #8156


8b1b342544


* Extend `normalize.sed` to:

* Rewrite auto-generated window names like `OVER w1` back to `OVER (?)`
on:

    * `Sort Key: …`
    * `Group Key: …`
    * `Output: …`
* Leave functional window specs like `OVER (PARTITION BY …)` untouched.

* Use `public.explain_filter(...)` around EXPLAINs in window-related
tests to:

* Avoid plan text churn from PG18 planner/EXPLAIN changes while still
checking that we use the Citus executor and expected node types.

* Update expected outputs in:

  * `mixed_relkind_tests.out`
  * `multi_explain*.out`
  * `multi_outer_join_columns*.out`
  * `multi_subquery_window_functions.out`
  * `multi_test_helpers.out`
  * `window_functions.out`
to match the filtered EXPLAIN output on PG18 while remaining compatible
with older PG versions.
pull/4271/merge
Mehmet YILMAZ 2025-11-19 22:48:24 +03:00 committed by GitHub
parent c843cb2060
commit 662b7248db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 210 additions and 98 deletions

View File

@ -395,3 +395,14 @@ s/\<is referenced from table\>/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 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 # 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 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

View File

@ -268,6 +268,12 @@ DEPS = {
"subquery_in_targetlist": TestDeps( "subquery_in_targetlist": TestDeps(
"minimal_schedule", ["multi_behavioral_analytics_create_table"] "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"]
),
} }

View File

@ -92,7 +92,6 @@ def run_citus_upgrade_tests(config, before_upgrade_schedule, after_upgrade_sched
def get_citus_catalog_info(config): def get_citus_catalog_info(config):
results = {} results = {}
# Store GUCs # Store GUCs
guc_results = utils.psql_capture( 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): 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()] pre_args_list = [arg.strip() for arg in full_args.split(",") if arg.strip()]
for post_full_args, post_return_type in post_signatures: for post_full_args, post_return_type in post_signatures:

View File

@ -633,11 +633,13 @@ $Q$);
(4 rows) (4 rows)
-- pull to coordinator WINDOW -- pull to coordinator WINDOW
select public.explain_filter('
SELECT public.coordinator_plan($Q$ SELECT public.coordinator_plan($Q$
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT a, COUNT(*) OVER (PARTITION BY a+1) FROM partitioned_distributed_table ORDER BY 1,2; SELECT a, COUNT(*) OVER (PARTITION BY a+1) FROM partitioned_distributed_table ORDER BY 1,2;
$Q$); $Q$)
coordinator_plan ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Sort Sort
Sort Key: remote_scan.a, (count(*) OVER (?)) Sort Key: remote_scan.a, (count(*) OVER (?))

View File

@ -3118,7 +3118,9 @@ CREATE TABLE distributed_table_1(a int, b int);
SELECT create_distributed_table('distributed_table_1','a'); SELECT create_distributed_table('distributed_table_1','a');
INSERT INTO distributed_table_1 values (1,1); 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) WindowAgg (actual rows=1 loops=1)
-> Custom Scan (Citus Adaptive) (actual rows=1 loops=1) -> Custom Scan (Citus Adaptive) (actual rows=1 loops=1)
Task Count: 2 Task Count: 2
@ -3130,11 +3132,13 @@ CREATE TABLE distributed_table_2(a int, b int);
SELECT create_distributed_table('distributed_table_2','a'); SELECT create_distributed_table('distributed_table_2','a');
INSERT INTO distributed_table_2 VALUES (1,1); 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) WITH r AS (SELECT row_number() OVER () AS r FROM distributed_table_1)
SELECT * FROM distributed_table_2 SELECT * FROM distributed_table_2
JOIN r ON (r = distributed_table_2.b) JOIN r ON (r = distributed_table_2.b)
LIMIT 3; LIMIT 3
', true);
Limit (actual rows=1 loops=1) Limit (actual rows=1 loops=1)
-> Custom Scan (Citus Adaptive) (actual rows=1 loops=1) -> Custom Scan (Citus Adaptive) (actual rows=1 loops=1)
-> Distributed Subplan XXX_1 -> Distributed Subplan XXX_1

View File

@ -3107,7 +3107,9 @@ CREATE TABLE distributed_table_1(a int, b int);
SELECT create_distributed_table('distributed_table_1','a'); SELECT create_distributed_table('distributed_table_1','a');
INSERT INTO distributed_table_1 values (1,1); 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) WindowAgg (actual rows=1 loops=1)
-> Custom Scan (Citus Adaptive) (actual rows=1 loops=1) -> Custom Scan (Citus Adaptive) (actual rows=1 loops=1)
Task Count: 2 Task Count: 2
@ -3119,11 +3121,13 @@ CREATE TABLE distributed_table_2(a int, b int);
SELECT create_distributed_table('distributed_table_2','a'); SELECT create_distributed_table('distributed_table_2','a');
INSERT INTO distributed_table_2 VALUES (1,1); 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) WITH r AS (SELECT row_number() OVER () AS r FROM distributed_table_1)
SELECT * FROM distributed_table_2 SELECT * FROM distributed_table_2
JOIN r ON (r = distributed_table_2.b) JOIN r ON (r = distributed_table_2.b)
LIMIT 3; LIMIT 3
', true);
Limit (actual rows=1 loops=1) Limit (actual rows=1 loops=1)
-> Custom Scan (Citus Adaptive) (actual rows=1 loops=1) -> Custom Scan (Citus Adaptive) (actual rows=1 loops=1)
-> Distributed Subplan XXX_1 -> Distributed Subplan XXX_1

View File

@ -55,10 +55,12 @@ FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id;
1 | 1 |
(3 rows) (3 rows)
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) 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
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
WindowAgg WindowAgg
Output: remote_scan.id, max(remote_scan.max) OVER (?), remote_scan.worker_column_3 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 | 1 |
(3 rows) (3 rows)
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) 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
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
WindowAgg WindowAgg
Output: remote_scan.id, max(remote_scan.max) OVER (?), remote_scan.worker_column_3 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 2 | 20
(3 rows) (3 rows)
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT DISTINCT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) 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
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
HashAggregate HashAggregate
Output: remote_scan.id, (max(remote_scan.max) OVER (?)), remote_scan.worker_column_3 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
(1 row) (1 row)
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT 1 SELECT 1
FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id
HAVING COUNT(DISTINCT a2) > 1; HAVING COUNT(DISTINCT a2) > 1;
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Aggregate Aggregate
Output: remote_scan."?column?" Output: remote_scan."?column?"

View File

@ -55,10 +55,12 @@ FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id;
1 | 1 |
(3 rows) (3 rows)
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) 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
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
WindowAgg WindowAgg
Output: remote_scan.id, max(remote_scan.max) OVER (?), remote_scan.worker_column_3 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 | 1 |
(3 rows) (3 rows)
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) 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
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
WindowAgg WindowAgg
Output: remote_scan.id, max(remote_scan.max) OVER (?), remote_scan.worker_column_3 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 2 | 20
(3 rows) (3 rows)
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT DISTINCT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) 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
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
HashAggregate HashAggregate
Output: remote_scan.id, (max(remote_scan.max) OVER (?)), remote_scan.worker_column_3 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
(1 row) (1 row)
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT 1 SELECT 1
FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id
HAVING COUNT(DISTINCT a2) > 1; HAVING COUNT(DISTINCT a2) > 1;
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Aggregate Aggregate
Output: remote_scan."?column?" Output: remote_scan."?column?"

View File

@ -675,6 +675,7 @@ LIMIT
2 | 1 2 | 1
(5 rows) (5 rows)
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT * SELECT *
FROM ( FROM (
@ -708,8 +709,9 @@ EXPLAIN (COSTS FALSE)
GROUP BY GROUP BY
user_id)) AS ftop user_id)) AS ftop
ORDER BY 2 DESC, 1 DESC ORDER BY 2 DESC, 1 DESC
LIMIT 5; LIMIT 5
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Limit Limit
-> Sort -> Sort

View File

@ -724,30 +724,42 @@ END; $$ language plpgsql;
-- ignore details such as exact costs or row counts. These filter -- ignore details such as exact costs or row counts. These filter
-- functions replace changeable output details with fixed strings. -- functions replace changeable output details with fixed strings.
-- Copied from PG explain.sql -- Copied from PG explain.sql
create function explain_filter(text) returns setof text CREATE OR REPLACE FUNCTION explain_filter(cmd text, keep_numbers boolean DEFAULT false)
language plpgsql as RETURNS SETOF text
LANGUAGE plpgsql AS
$$ $$
declare DECLARE
ln text; ln text;
begin BEGIN
for ln in execute $1 FOR ln IN EXECUTE cmd LOOP
loop
-- PG18 extra line "Index Searches: N" — remove entirely -- PG18 extra line "Index Searches: N" — remove entirely
IF ln ~ '^[[:space:]]*Index[[:space:]]+Searches:[[:space:]]*[0-9]+[[:space:]]*$' THEN IF ln ~ '^[[:space:]]*Index[[:space:]]+Searches:[[:space:]]*[0-9]+[[:space:]]*$' THEN
CONTINUE; CONTINUE;
END IF; 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' -- Replace any numeric word with just 'N'
ln := regexp_replace(ln, '-?\m\d+\M', 'N', 'g'); ln := regexp_replace(ln, '-?\m\d+\M', 'N', 'g');
-- In sort output, the above won't match units-suffixed numbers -- In sort output, the above won't match units-suffixed numbers
ln := regexp_replace(ln, '\m\d+kB', 'NkB', 'g'); ln := regexp_replace(ln, '\m\d+kB', 'NkB', 'g');
END IF;
-- Ignore text-mode buffers output because it varies depending -- Ignore text-mode buffers output because it varies depending
-- on the system state -- on the system state
CONTINUE WHEN (ln ~ ' +Buffers: .*'); CONTINUE WHEN (ln ~ ' +Buffers: .*');
-- Ignore text-mode "Planning:" line because whether it's output -- Ignore text-mode "Planning:" line because whether it's output
-- varies depending on the system state -- varies depending on the system state
CONTINUE WHEN (ln = 'Planning:'); CONTINUE WHEN (ln = 'Planning:');
return next ln;
end loop; RETURN NEXT ln;
end; END LOOP;
END;
$$; $$;

View File

@ -2069,7 +2069,7 @@ SET citus.grep_remote_commands TO '%12242024%';
select public.explain_filter('explain (memory) select * from int8_tbl i8'); 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 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 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 explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N)
@ -2086,7 +2086,7 @@ 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'); 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) 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 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 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) Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=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'); 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 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 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 explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
- Plan: + - 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'); 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) 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 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 explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
[ + [ +
@ -2230,7 +2230,7 @@ prepare int8_query as select * from int8_tbl i8;
select public.explain_filter('explain (memory) execute int8_query'); 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 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 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 explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) 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'); 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) 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 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 explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
- Plan: + - Plan: +
@ -2369,7 +2369,7 @@ 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'); 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) 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 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 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) Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
@ -2391,7 +2391,7 @@ 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'); 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) 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 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 explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) (actual rows=N loops=N) Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) (actual rows=N loops=N)
@ -2413,7 +2413,7 @@ 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'); 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) 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 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 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) Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
@ -2436,7 +2436,7 @@ 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'); 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) 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 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 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) Custom Scan (Citus Adaptive) (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)

View File

@ -1292,6 +1292,7 @@ ORDER BY user_id, avg(value_1) DESC;
6 | 1.00000000000000000000 | 5 6 | 1.00000000000000000000 | 5
(32 rows) (32 rows)
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT SELECT
user_id, user_id,
@ -1300,8 +1301,9 @@ SELECT
FROM FROM
users_table users_table
GROUP BY user_id, value_2 GROUP BY user_id, value_2
ORDER BY user_id, avg(value_1) DESC; ORDER BY user_id, avg(value_1) DESC
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Sort Sort
Sort Key: remote_scan.user_id, remote_scan.avg DESC Sort Key: remote_scan.user_id, remote_scan.avg DESC
@ -1364,6 +1366,7 @@ ORDER BY user_id, avg(value_1) DESC;
(32 rows) (32 rows)
-- limit is not pushed down to worker !! -- limit is not pushed down to worker !!
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT SELECT
user_id, user_id,
@ -1373,8 +1376,9 @@ FROM
users_table users_table
GROUP BY user_id, value_2 GROUP BY user_id, value_2
ORDER BY user_id, avg(value_1) DESC ORDER BY user_id, avg(value_1) DESC
LIMIT 5; LIMIT 5
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Limit Limit
-> Sort -> Sort
@ -1395,6 +1399,7 @@ LIMIT 5;
-> Seq Scan on users_table_1400256 users_table -> Seq Scan on users_table_1400256 users_table
(17 rows) (17 rows)
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT SELECT
user_id, user_id,
@ -1404,8 +1409,9 @@ FROM
users_table users_table
GROUP BY user_id, value_2 GROUP BY user_id, value_2
ORDER BY user_id, avg(value_1) DESC ORDER BY user_id, avg(value_1) DESC
LIMIT 5; LIMIT 5
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Limit Limit
-> Sort -> Sort
@ -1426,6 +1432,7 @@ LIMIT 5;
-> Seq Scan on users_table_1400256 users_table -> Seq Scan on users_table_1400256 users_table
(17 rows) (17 rows)
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT SELECT
user_id, user_id,
@ -1435,8 +1442,9 @@ FROM
users_table users_table
GROUP BY user_id, value_2 GROUP BY user_id, value_2
ORDER BY user_id, avg(value_1) DESC ORDER BY user_id, avg(value_1) DESC
LIMIT 5; LIMIT 5
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Limit Limit
-> Sort -> Sort
@ -1457,6 +1465,7 @@ LIMIT 5;
-> Seq Scan on users_table_1400256 users_table -> Seq Scan on users_table_1400256 users_table
(17 rows) (17 rows)
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT SELECT
user_id, user_id,
@ -1466,8 +1475,9 @@ FROM
users_table users_table
GROUP BY user_id, value_2 GROUP BY user_id, value_2
ORDER BY user_id, avg(value_1) DESC ORDER BY user_id, avg(value_1) DESC
LIMIT 5; LIMIT 5
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Limit Limit
-> Sort -> Sort
@ -1489,10 +1499,12 @@ LIMIT 5;
(17 rows) (17 rows)
-- Grouping can be pushed down with aggregates even when window function can't -- Grouping can be pushed down with aggregates even when window function can't
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT user_id, count(value_1), stddev(value_1), count(user_id) OVER (PARTITION BY random()) 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
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Limit Limit
-> WindowAgg -> WindowAgg
@ -1534,6 +1546,7 @@ SELECT create_distributed_table('daily_uniques', 'user_id');
(1 row) (1 row)
select public.explain_filter('
EXPLAIN (COSTS FALSE) SELECT EXPLAIN (COSTS FALSE) SELECT
user_id, user_id,
sum(value_2) AS commits, sum(value_2) AS commits,
@ -1547,8 +1560,9 @@ GROUP BY user_id
HAVING HAVING
sum(value_2) > 0 sum(value_2) > 0
ORDER BY commits DESC ORDER BY commits DESC
LIMIT 10; LIMIT 10
QUERY PLAN ', true);
explain_filter
--------------------------------------------------------------------- ---------------------------------------------------------------------
Limit Limit
-> Sort -> Sort

View File

@ -232,10 +232,12 @@ SELECT a, COUNT(*) OVER (PARTITION BY a) FROM partitioned_distributed_table ORDE
$Q$); $Q$);
-- pull to coordinator WINDOW -- pull to coordinator WINDOW
select public.explain_filter('
SELECT public.coordinator_plan($Q$ SELECT public.coordinator_plan($Q$
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT a, COUNT(*) OVER (PARTITION BY a+1) FROM partitioned_distributed_table ORDER BY 1,2; SELECT a, COUNT(*) OVER (PARTITION BY a+1) FROM partitioned_distributed_table ORDER BY 1,2;
$Q$); $Q$)
', true);
-- FOR UPDATE -- FOR UPDATE
SELECT * FROM partitioned_distributed_table WHERE a = 1 ORDER BY 1,2 FOR UPDATE; SELECT * FROM partitioned_distributed_table WHERE a = 1 ORDER BY 1,2 FOR UPDATE;

View File

@ -1137,17 +1137,21 @@ CREATE TABLE distributed_table_1(a int, b int);
SELECT create_distributed_table('distributed_table_1','a'); SELECT create_distributed_table('distributed_table_1','a');
INSERT INTO distributed_table_1 values (1,1); 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); CREATE TABLE distributed_table_2(a int, b int);
SELECT create_distributed_table('distributed_table_2','a'); SELECT create_distributed_table('distributed_table_2','a');
INSERT INTO distributed_table_2 VALUES (1,1); 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) WITH r AS (SELECT row_number() OVER () AS r FROM distributed_table_1)
SELECT * FROM distributed_table_2 SELECT * FROM distributed_table_2
JOIN r ON (r = distributed_table_2.b) JOIN r ON (r = distributed_table_2.b)
LIMIT 3; LIMIT 3
', true);
EXPLAIN :default_analyze_flags SELECT FROM (SELECT * FROM reference_table) subquery; EXPLAIN :default_analyze_flags SELECT FROM (SELECT * FROM reference_table) subquery;

View File

@ -32,21 +32,27 @@ SELECT create_distributed_table('t2', 'account_id');
-- produces a non-empty varnullingrels set in PG 16 (and higher) -- produces a non-empty varnullingrels set in PG 16 (and higher)
SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) 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;
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) 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) 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;
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) 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) 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;
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT DISTINCT t1.id, MAX(t2.a2) OVER (PARTITION BY t2.id) 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 SEQUENCE test_seq START 101;
CREATE OR REPLACE FUNCTION TEST_F(int) returns INT language sql stable as $$ select $1 + 42; $$ ; 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 SELECT 1
FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id
HAVING COUNT(DISTINCT a2) > 1; HAVING COUNT(DISTINCT a2) > 1;
select public.explain_filter('
EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF) EXPLAIN (VERBOSE, COSTS OFF, TIMING OFF)
SELECT 1 SELECT 1
FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.account_id
HAVING COUNT(DISTINCT a2) > 1; HAVING COUNT(DISTINCT a2) > 1;
', true);
-- Check right outer join -- Check right outer join
SELECT COUNT(DISTINCT a2) SELECT COUNT(DISTINCT a2)

View File

@ -441,6 +441,7 @@ ORDER BY
LIMIT LIMIT
5; 5;
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT * SELECT *
FROM ( FROM (
@ -474,7 +475,8 @@ EXPLAIN (COSTS FALSE)
GROUP BY GROUP BY
user_id)) AS ftop user_id)) AS ftop
ORDER BY 2 DESC, 1 DESC ORDER BY 2 DESC, 1 DESC
LIMIT 5; LIMIT 5
', true);
-- test with window functions which aren't pushed down -- test with window functions which aren't pushed down
SELECT SELECT

View File

@ -755,30 +755,43 @@ END; $$ language plpgsql;
-- functions replace changeable output details with fixed strings. -- functions replace changeable output details with fixed strings.
-- Copied from PG explain.sql -- Copied from PG explain.sql
create function explain_filter(text) returns setof text CREATE OR REPLACE FUNCTION explain_filter(cmd text, keep_numbers boolean DEFAULT false)
language plpgsql as RETURNS SETOF text
LANGUAGE plpgsql AS
$$ $$
declare DECLARE
ln text; ln text;
begin BEGIN
for ln in execute $1 FOR ln IN EXECUTE cmd LOOP
loop
-- PG18 extra line "Index Searches: N" — remove entirely -- PG18 extra line "Index Searches: N" — remove entirely
IF ln ~ '^[[:space:]]*Index[[:space:]]+Searches:[[:space:]]*[0-9]+[[:space:]]*$' THEN IF ln ~ '^[[:space:]]*Index[[:space:]]+Searches:[[:space:]]*[0-9]+[[:space:]]*$' THEN
CONTINUE; CONTINUE;
END IF; 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' -- Replace any numeric word with just 'N'
ln := regexp_replace(ln, '-?\m\d+\M', 'N', 'g'); ln := regexp_replace(ln, '-?\m\d+\M', 'N', 'g');
-- In sort output, the above won't match units-suffixed numbers -- In sort output, the above won't match units-suffixed numbers
ln := regexp_replace(ln, '\m\d+kB', 'NkB', 'g'); ln := regexp_replace(ln, '\m\d+kB', 'NkB', 'g');
END IF;
-- Ignore text-mode buffers output because it varies depending -- Ignore text-mode buffers output because it varies depending
-- on the system state -- on the system state
CONTINUE WHEN (ln ~ ' +Buffers: .*'); CONTINUE WHEN (ln ~ ' +Buffers: .*');
-- Ignore text-mode "Planning:" line because whether it's output -- Ignore text-mode "Planning:" line because whether it's output
-- varies depending on the system state -- varies depending on the system state
CONTINUE WHEN (ln = 'Planning:'); CONTINUE WHEN (ln = 'Planning:');
return next ln;
end loop; RETURN NEXT ln;
end; END LOOP;
END;
$$; $$;

View File

@ -508,6 +508,7 @@ FROM
GROUP BY user_id, value_2 GROUP BY user_id, value_2
ORDER BY user_id, avg(value_1) DESC; ORDER BY user_id, avg(value_1) DESC;
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT SELECT
user_id, user_id,
@ -516,7 +517,8 @@ SELECT
FROM FROM
users_table users_table
GROUP BY user_id, value_2 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 -- order by in the window function is same as avg(value_1) DESC
SELECT SELECT
@ -529,6 +531,7 @@ GROUP BY user_id, value_2
ORDER BY user_id, avg(value_1) DESC; ORDER BY user_id, avg(value_1) DESC;
-- limit is not pushed down to worker !! -- limit is not pushed down to worker !!
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT SELECT
user_id, user_id,
@ -538,8 +541,10 @@ FROM
users_table users_table
GROUP BY user_id, value_2 GROUP BY user_id, value_2
ORDER BY user_id, avg(value_1) DESC ORDER BY user_id, avg(value_1) DESC
LIMIT 5; LIMIT 5
', true);
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT SELECT
user_id, user_id,
@ -549,8 +554,10 @@ FROM
users_table users_table
GROUP BY user_id, value_2 GROUP BY user_id, value_2
ORDER BY user_id, avg(value_1) DESC ORDER BY user_id, avg(value_1) DESC
LIMIT 5; LIMIT 5
', true);
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT SELECT
user_id, user_id,
@ -560,8 +567,10 @@ FROM
users_table users_table
GROUP BY user_id, value_2 GROUP BY user_id, value_2
ORDER BY user_id, avg(value_1) DESC ORDER BY user_id, avg(value_1) DESC
LIMIT 5; LIMIT 5
', true);
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT SELECT
user_id, user_id,
@ -571,12 +580,15 @@ FROM
users_table users_table
GROUP BY user_id, value_2 GROUP BY user_id, value_2
ORDER BY user_id, avg(value_1) DESC 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 -- Grouping can be pushed down with aggregates even when window function can't
select public.explain_filter('
EXPLAIN (COSTS FALSE) EXPLAIN (COSTS FALSE)
SELECT user_id, count(value_1), stddev(value_1), count(user_id) OVER (PARTITION BY random()) 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 -- Window function with inlined CTE
WITH cte as ( WITH cte as (
@ -594,6 +606,7 @@ ORDER BY 1;
CREATE TABLE daily_uniques (value_2 float, user_id bigint); CREATE TABLE daily_uniques (value_2 float, user_id bigint);
SELECT create_distributed_table('daily_uniques', 'user_id'); SELECT create_distributed_table('daily_uniques', 'user_id');
select public.explain_filter('
EXPLAIN (COSTS FALSE) SELECT EXPLAIN (COSTS FALSE) SELECT
user_id, user_id,
sum(value_2) AS commits, sum(value_2) AS commits,
@ -607,7 +620,8 @@ GROUP BY user_id
HAVING HAVING
sum(value_2) > 0 sum(value_2) > 0
ORDER BY commits DESC ORDER BY commits DESC
LIMIT 10; LIMIT 10
', true);
DROP TABLE daily_uniques; DROP TABLE daily_uniques;