mirror of https://github.com/citusdata/citus.git
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
parent
c843cb2060
commit
662b7248db
|
|
@ -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
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 (?))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?"
|
||||
|
|
|
|||
|
|
@ -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?"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
$$;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue