treat_window_func_calc_parallel_restricted_v2.patch

application/octet-stream

Filename: treat_window_func_calc_parallel_restricted_v2.patch
Type: application/octet-stream
Part: 3
Message: Re: BUG #15324: Non-deterministic behaviour from parallelised sub-query

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v2
File+
src/backend/optimizer/util/clauses.c 13 0
src/include/catalog/pg_proc.dat 49 38
src/test/regress/expected/select_parallel.out 26 0
src/test/regress/sql/select_parallel.sql 5 0
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a04ad6e..1c7136f 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1197,6 +1197,19 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 	}
 
 	/*
+	 * Treat window functions as parallel-restricted as the row ordering
+	 * induced by them is non-deterministic.  We can relax this condition for
+	 * cases where the row ordering can be deterministic like when there is
+	 * an ORDER BY on the primary key, but those cases don't seem to be
+	 * interesting enough to have additional checks.
+	 */
+	if (IsA(node, WindowFunc))
+	{
+		if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+			return true;
+	}
+
+	/*
 	 * As a notational convenience for callers, look through RestrictInfo.
 	 */
 	else if (IsA(node, RestrictInfo))
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a146510..a641b8c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9413,51 +9413,62 @@
 # SQL-spec window functions
 { oid => '3100', descr => 'row number within partition',
   proname => 'row_number', prokind => 'w', proisstrict => 'f',
-  prorettype => 'int8', proargtypes => '', prosrc => 'window_row_number' },
+  proparallel => 'r', prorettype => 'int8', proargtypes => '',
+  prosrc => 'window_row_number' },
 { oid => '3101', descr => 'integer rank with gaps',
-  proname => 'rank', prokind => 'w', proisstrict => 'f', prorettype => 'int8',
-  proargtypes => '', prosrc => 'window_rank' },
+  proname => 'rank', prokind => 'w', proisstrict => 'f', proparallel => 'r',
+  prorettype => 'int8', proargtypes => '', prosrc => 'window_rank' },
 { oid => '3102', descr => 'integer rank without gaps',
   proname => 'dense_rank', prokind => 'w', proisstrict => 'f',
-  prorettype => 'int8', proargtypes => '', prosrc => 'window_dense_rank' },
+  proparallel => 'r', prorettype => 'int8', proargtypes => '',
+  prosrc => 'window_dense_rank' },
 { oid => '3103', descr => 'fractional rank within partition',
   proname => 'percent_rank', prokind => 'w', proisstrict => 'f',
-  prorettype => 'float8', proargtypes => '', prosrc => 'window_percent_rank' },
+  proparallel => 'r', prorettype => 'float8', proargtypes => '',
+  prosrc => 'window_percent_rank' },
 { oid => '3104', descr => 'fractional row number within partition',
   proname => 'cume_dist', prokind => 'w', proisstrict => 'f',
-  prorettype => 'float8', proargtypes => '', prosrc => 'window_cume_dist' },
+  proparallel => 'r', prorettype => 'float8', proargtypes => '',
+  prosrc => 'window_cume_dist' },
 { oid => '3105', descr => 'split rows into N groups',
-  proname => 'ntile', prokind => 'w', prorettype => 'int4',
-  proargtypes => 'int4', prosrc => 'window_ntile' },
+  proname => 'ntile', prokind => 'w', proparallel => 'r',
+  prorettype => 'int4', proargtypes => 'int4', prosrc => 'window_ntile' },
 { oid => '3106', descr => 'fetch the preceding row value',
-  proname => 'lag', prokind => 'w', prorettype => 'anyelement',
-  proargtypes => 'anyelement', prosrc => 'window_lag' },
+  proname => 'lag', prokind => 'w', proparallel => 'r',
+  prorettype => 'anyelement', proargtypes => 'anyelement',
+  prosrc => 'window_lag' },
 { oid => '3107', descr => 'fetch the Nth preceding row value',
-  proname => 'lag', prokind => 'w', prorettype => 'anyelement',
-  proargtypes => 'anyelement int4', prosrc => 'window_lag_with_offset' },
+  proname => 'lag', prokind => 'w', proparallel => 'r',
+  prorettype => 'anyelement', proargtypes => 'anyelement int4',
+  prosrc => 'window_lag_with_offset' },
 { oid => '3108', descr => 'fetch the Nth preceding row value with default',
-  proname => 'lag', prokind => 'w', prorettype => 'anyelement',
-  proargtypes => 'anyelement int4 anyelement',
+  proname => 'lag', prokind => 'w', proparallel => 'r',
+  prorettype => 'anyelement', proargtypes => 'anyelement int4 anyelement',
   prosrc => 'window_lag_with_offset_and_default' },
 { oid => '3109', descr => 'fetch the following row value',
-  proname => 'lead', prokind => 'w', prorettype => 'anyelement',
-  proargtypes => 'anyelement', prosrc => 'window_lead' },
+  proname => 'lead', prokind => 'w', proparallel => 'r',
+  prorettype => 'anyelement', proargtypes => 'anyelement',
+  prosrc => 'window_lead' },
 { oid => '3110', descr => 'fetch the Nth following row value',
-  proname => 'lead', prokind => 'w', prorettype => 'anyelement',
-  proargtypes => 'anyelement int4', prosrc => 'window_lead_with_offset' },
+  proname => 'lead', prokind => 'w', proparallel => 'r',
+  prorettype => 'anyelement', proargtypes => 'anyelement int4',
+  prosrc => 'window_lead_with_offset' },
 { oid => '3111', descr => 'fetch the Nth following row value with default',
-  proname => 'lead', prokind => 'w', prorettype => 'anyelement',
-  proargtypes => 'anyelement int4 anyelement',
+  proname => 'lead', prokind => 'w', proparallel => 'r',
+  prorettype => 'anyelement', proargtypes => 'anyelement int4 anyelement',
   prosrc => 'window_lead_with_offset_and_default' },
 { oid => '3112', descr => 'fetch the first row value',
-  proname => 'first_value', prokind => 'w', prorettype => 'anyelement',
-  proargtypes => 'anyelement', prosrc => 'window_first_value' },
+  proname => 'first_value', prokind => 'w', proparallel => 'r',
+  prorettype => 'anyelement', proargtypes => 'anyelement',
+  prosrc => 'window_first_value' },
 { oid => '3113', descr => 'fetch the last row value',
-  proname => 'last_value', prokind => 'w', prorettype => 'anyelement',
-  proargtypes => 'anyelement', prosrc => 'window_last_value' },
+  proname => 'last_value', prokind => 'w', proparallel => 'r',
+  prorettype => 'anyelement', proargtypes => 'anyelement',
+  prosrc => 'window_last_value' },
 { oid => '3114', descr => 'fetch the Nth row value',
-  proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
-  proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+  proname => 'nth_value', prokind => 'w', proparallel => 'r',
+  prorettype => 'anyelement', proargtypes => 'anyelement int4',
+  prosrc => 'window_nth_value' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
@@ -9957,41 +9968,41 @@
 # hypothetical-set aggregates (and their support functions)
 { oid => '3986', descr => 'rank of hypothetical row',
   proname => 'rank', provariadic => 'any', prokind => 'a', proisstrict => 'f',
-  prorettype => 'int8', proargtypes => 'any', proallargtypes => '{any}',
-  proargmodes => '{v}', prosrc => 'aggregate_dummy' },
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'any',
+  proallargtypes => '{any}', proargmodes => '{v}', prosrc => 'aggregate_dummy' },
 { oid => '3987', descr => 'aggregate final function',
   proname => 'rank_final', provariadic => 'any', proisstrict => 'f',
-  prorettype => 'int8', proargtypes => 'internal any',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'internal any',
   proallargtypes => '{internal,any}', proargmodes => '{i,v}',
   prosrc => 'hypothetical_rank_final' },
 { oid => '3988', descr => 'fractional rank of hypothetical row',
   proname => 'percent_rank', provariadic => 'any', prokind => 'a',
-  proisstrict => 'f', prorettype => 'float8', proargtypes => 'any',
-  proallargtypes => '{any}', proargmodes => '{v}',
+  proisstrict => 'f', proparallel => 'r', prorettype => 'float8',
+  proargtypes => 'any', proallargtypes => '{any}', proargmodes => '{v}',
   prosrc => 'aggregate_dummy' },
 { oid => '3989', descr => 'aggregate final function',
   proname => 'percent_rank_final', provariadic => 'any', proisstrict => 'f',
-  prorettype => 'float8', proargtypes => 'internal any',
+  proparallel => 'r', prorettype => 'float8', proargtypes => 'internal any',
   proallargtypes => '{internal,any}', proargmodes => '{i,v}',
   prosrc => 'hypothetical_percent_rank_final' },
 { oid => '3990', descr => 'cumulative distribution of hypothetical row',
   proname => 'cume_dist', provariadic => 'any', prokind => 'a',
-  proisstrict => 'f', prorettype => 'float8', proargtypes => 'any',
-  proallargtypes => '{any}', proargmodes => '{v}',
+  proisstrict => 'f', proparallel => 'r', prorettype => 'float8',
+  proargtypes => 'any', proallargtypes => '{any}', proargmodes => '{v}',
   prosrc => 'aggregate_dummy' },
 { oid => '3991', descr => 'aggregate final function',
   proname => 'cume_dist_final', provariadic => 'any', proisstrict => 'f',
-  prorettype => 'float8', proargtypes => 'internal any',
+  proparallel => 'r', prorettype => 'float8', proargtypes => 'internal any',
   proallargtypes => '{internal,any}', proargmodes => '{i,v}',
   prosrc => 'hypothetical_cume_dist_final' },
 { oid => '3992', descr => 'rank of hypothetical row without gaps',
   proname => 'dense_rank', provariadic => 'any', prokind => 'a',
-  proisstrict => 'f', prorettype => 'int8', proargtypes => 'any',
-  proallargtypes => '{any}', proargmodes => '{v}',
+  proisstrict => 'f', proparallel => 'r', prorettype => 'int8',
+  proargtypes => 'any', proallargtypes => '{any}', proargmodes => '{v}',
   prosrc => 'aggregate_dummy' },
 { oid => '3993', descr => 'aggregate final function',
   proname => 'dense_rank_final', provariadic => 'any', proisstrict => 'f',
-  prorettype => 'int8', proargtypes => 'internal any',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'internal any',
   proallargtypes => '{internal,any}', proargmodes => '{i,v}',
   prosrc => 'hypothetical_dense_rank_final' },
 
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index ad03807..ce4fab9 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -977,6 +977,32 @@ select * from tenk1 where two in
                            Filter: (stringu1 ~~ '%AAAA'::text)
 (11 rows)
 
+-- Window function calculation can't be pushed to workers.
+explain (costs off, verbose)
+  select count(*) from tenk1 where (unique1, two) in
+    (select unique1, row_number() over() from tenk1);
+                                             QUERY PLAN                                             
+----------------------------------------------------------------------------------------------------
+ Aggregate
+   Output: count(*)
+   ->  Hash Semi Join
+         Hash Cond: ((tenk1.unique1 = tenk1_1.unique1) AND (tenk1.two = (row_number() OVER (?))))
+         ->  Gather
+               Output: tenk1.unique1, tenk1.two
+               Workers Planned: 4
+               ->  Parallel Seq Scan on public.tenk1
+                     Output: tenk1.unique1, tenk1.two
+         ->  Hash
+               Output: tenk1_1.unique1, (row_number() OVER (?))
+               ->  WindowAgg
+                     Output: tenk1_1.unique1, row_number() OVER (?)
+                     ->  Gather
+                           Output: tenk1_1.unique1
+                           Workers Planned: 4
+                           ->  Parallel Index Only Scan using tenk1_unique1 on public.tenk1 tenk1_1
+                                 Output: tenk1_1.unique1
+(18 rows)
+
 -- to increase the parallel query test coverage
 SAVEPOINT settings;
 SET LOCAL force_parallel_mode = 1;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index aea2461..f904154 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -367,6 +367,11 @@ explain (costs off)
 select * from tenk1 where two in
 	(select two from tenk1 where stringu1 like '%AAAA' limit 3);
 
+-- Window function calculation can't be pushed to workers.
+explain (costs off, verbose)
+  select count(*) from tenk1 where (unique1, two) in
+    (select unique1, row_number() over() from tenk1);
+
 -- to increase the parallel query test coverage
 SAVEPOINT settings;
 SET LOCAL force_parallel_mode = 1;