Disable run condition optimization for some WindowFuncs
authorDavid Rowley <drowley@postgresql.org>
Wed, 1 May 2024 04:35:05 +0000 (16:35 +1200)
committerDavid Rowley <drowley@postgresql.org>
Wed, 1 May 2024 04:35:05 +0000 (16:35 +1200)
94985c210 added code to detect when WindowFuncs were monotonic and
allowed additional quals to be "pushed down" into the subquery to be
used as WindowClause runConditions in order to short-circuit execution
in nodeWindowAgg.c.

The Node representation of runConditions wasn't well selected and
because we do qual pushdown before planning the subquery, the planning
of the subquery could perform subquery pull-up of nested subqueries.
For WindowFuncs with args, the arguments could be changed after pushing
the qual down to the subquery.

This was made more difficult by the fact that the code duplicated the
WindowFunc inside an OpExpr to include in the WindowClauses runCondition
field.  This could result in duplication of subqueries and a pull-up of
such a subquery could result in another initplan parameter being issued
for the 2nd version of the subplan.  This could result in errors such as:

ERROR:  WindowFunc not found in subplan target lists

Here in the backbranches, we don't have the flexibility to improve the
Node representation to resolve this, so instead we just disable the
runCondition optimization for ntile() unless the argument is a Const,
(v16 only) and likewise for count(expr) (both v15 and v16).  count(*) is
unaffected.  All other window functions which support this optimization
all take zero arguments and therefore are unaffected.

Bug: #18170
Reported-by: Zuming Jiang
Discussion: https://postgr.es/m/18170-f1d17bf9a0d58b24@postgresql.org
Backpatch-through 15 (master will be fixed independently)

src/backend/utils/adt/int8.c
src/backend/utils/adt/windowfuncs.c
src/test/regress/expected/window.out
src/test/regress/sql/window.sql

index 123b4eb6bfbb6ae17012cb23f658868a6f90875b..41fbeec8fd7a098392067fe5719b6ba159fcfaf3 100644 (file)
@@ -833,6 +833,21 @@ int8inc_support(PG_FUNCTION_ARGS)
        SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
        MonotonicFunction monotonic = MONOTONICFUNC_NONE;
        int         frameOptions = req->window_clause->frameOptions;
+       WindowFunc *wfunc = req->window_func;
+
+       if (list_length(wfunc->args) == 1)
+       {
+           Node *expr = eval_const_expressions(NULL, linitial(wfunc->args));
+
+           /*
+            * Due to the Node representation of WindowClause runConditions in
+            * version prior to v17, we need to insist that the count arg is
+            * Const to allow safe application of the runCondition
+            * optimization.
+            */
+           if (!IsA(expr, Const))
+               PG_RETURN_POINTER(NULL);
+       }
 
        /* No ORDER BY clause then all rows are peers */
        if (req->window_clause->orderClause == NIL)
index b87a624fb2fc56762d66f4b9889de22fb97a3444..0c7cc55845ab84cb49a58e5bd89463a9bb24962e 100644 (file)
@@ -14,6 +14,7 @@
 #include "postgres.h"
 
 #include "nodes/supportnodes.h"
+#include "optimizer/optimizer.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
 
@@ -486,13 +487,29 @@ window_ntile_support(PG_FUNCTION_ARGS)
    if (IsA(rawreq, SupportRequestWFuncMonotonic))
    {
        SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
+       WindowFunc *wfunc = req->window_func;
 
-       /*
-        * ntile() is monotonically increasing as the number of buckets cannot
-        * change after the first call
-        */
-       req->monotonic = MONOTONICFUNC_INCREASING;
-       PG_RETURN_POINTER(req);
+       if (list_length(wfunc->args) == 1)
+       {
+           Node *expr = eval_const_expressions(NULL, linitial(wfunc->args));
+
+           /*
+            * Due to the Node representation of WindowClause runConditions in
+            * version prior to v17, we need to insist that ntile arg is Const
+            * to allow safe application of the runCondition optimization.
+            */
+           if (IsA(expr, Const))
+           {
+               /*
+                * ntile() is monotonically increasing as the number of
+                * buckets cannot change after the first call
+                */
+               req->monotonic = MONOTONICFUNC_INCREASING;
+               PG_RETURN_POINTER(req);
+           }
+       }
+
+       PG_RETURN_POINTER(NULL);
    }
 
    if (IsA(rawreq, SupportRequestOptimizeWindowClause))
index 86127887022cd53b8c14347d73ebe3123c13ea9d..1a9cc66a6babbd69846fca23743e218a5b7fb168 100644 (file)
@@ -3577,13 +3577,13 @@ EXPLAIN (COSTS OFF)
 SELECT * FROM
   (SELECT empno,
           salary,
-          count(empno) OVER (ORDER BY salary DESC) c
+          count(1) OVER (ORDER BY salary DESC) c
    FROM empsalary) emp
 WHERE c <= 3;
-                       QUERY PLAN                        
----------------------------------------------------------
+                QUERY PLAN                 
+-------------------------------------------
  WindowAgg
-   Run Condition: (count(empsalary.empno) OVER (?) <= 3)
+   Run Condition: (count(1) OVER (?) <= 3)
    ->  Sort
          Sort Key: empsalary.salary DESC
          ->  Seq Scan on empsalary
@@ -3592,7 +3592,7 @@ WHERE c <= 3;
 SELECT * FROM
   (SELECT empno,
           salary,
-          count(empno) OVER (ORDER BY salary DESC) c
+          count(1) OVER (ORDER BY salary DESC) c
    FROM empsalary) emp
 WHERE c <= 3;
  empno | salary | c 
@@ -3704,19 +3704,19 @@ WHERE rn < 3;
                ->  Seq Scan on empsalary
 (6 rows)
 
--- likewise with count(empno) instead of row_number()
+-- likewise with count(1) instead of row_number()
 EXPLAIN (COSTS OFF)
 SELECT * FROM
   (SELECT empno,
           depname,
           salary,
-          count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
+          count(1) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
 WHERE c <= 3;
                          QUERY PLAN                         
 ------------------------------------------------------------
  WindowAgg
-   Run Condition: (count(empsalary.empno) OVER (?) <= 3)
+   Run Condition: (count(1) OVER (?) <= 3)
    ->  Sort
          Sort Key: empsalary.depname, empsalary.salary DESC
          ->  Seq Scan on empsalary
@@ -3727,7 +3727,7 @@ SELECT * FROM
   (SELECT empno,
           depname,
           salary,
-          count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
+          count(1) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
 WHERE c <= 3;
  empno |  depname  | salary | c 
@@ -3749,13 +3749,13 @@ SELECT * FROM
   (SELECT empno,
           depname,
           salary,
-          count(empno) OVER () c
+          count(1) OVER () c
    FROM empsalary) emp
 WHERE c = 1;
-                       QUERY PLAN                       
---------------------------------------------------------
+                QUERY PLAN                
+------------------------------------------
  WindowAgg
-   Run Condition: (count(empsalary.empno) OVER (?) = 1)
+   Run Condition: (count(1) OVER (?) = 1)
    ->  Seq Scan on empsalary
 (3 rows)
 
@@ -3763,7 +3763,7 @@ WHERE c = 1;
 EXPLAIN (COSTS OFF)
 SELECT * FROM
   (SELECT *,
-          count(salary) OVER (PARTITION BY depname || '') c1, -- w1
+          count(1) OVER (PARTITION BY depname || '') c1, -- w1
           row_number() OVER (PARTITION BY depname) rn, -- w2
           count(*) OVER (PARTITION BY depname) c2, -- w2
           count(*) OVER (PARTITION BY '' || depname) c3, -- w3
@@ -3775,7 +3775,7 @@ SELECT * FROM
  Subquery Scan on e
    ->  WindowAgg
          Filter: (((row_number() OVER (?)) <= 1) AND ((ntile(2) OVER (?)) < 2))
-         Run Condition: (count(empsalary.salary) OVER (?) <= 3)
+         Run Condition: (count(1) OVER (?) <= 3)
          ->  Sort
                Sort Key: (((empsalary.depname)::text || ''::text))
                ->  WindowAgg
@@ -3791,7 +3791,7 @@ SELECT * FROM
 -- Ensure we correctly filter out all of the run conditions from each window
 SELECT * FROM
   (SELECT *,
-          count(salary) OVER (PARTITION BY depname || '') c1, -- w1
+          count(1) OVER (PARTITION BY depname || '') c1, -- w1
           row_number() OVER (PARTITION BY depname) rn, -- w2
           count(*) OVER (PARTITION BY depname) c2, -- w2
           count(*) OVER (PARTITION BY '' || depname) c3, -- w3
@@ -3804,32 +3804,6 @@ SELECT * FROM
  sales     |     3 |   4800 | 08-01-2007  |  3 |  1 |  3 |  3 |  1
 (2 rows)
 
--- Ensure we remove references to reduced outer joins as nulling rels in run
--- conditions
-EXPLAIN (COSTS OFF)
-SELECT 1 FROM
-  (SELECT ntile(e2.salary) OVER (PARTITION BY e1.depname) AS c
-   FROM empsalary e1 LEFT JOIN empsalary e2 ON TRUE
-   WHERE e1.empno = e2.empno) s
-WHERE s.c = 1;
-                       QUERY PLAN                        
----------------------------------------------------------
- Subquery Scan on s
-   Filter: (s.c = 1)
-   ->  WindowAgg
-         Run Condition: (ntile(e2.salary) OVER (?) <= 1)
-         ->  Sort
-               Sort Key: e1.depname
-               ->  Merge Join
-                     Merge Cond: (e1.empno = e2.empno)
-                     ->  Sort
-                           Sort Key: e1.empno
-                           ->  Seq Scan on empsalary e1
-                     ->  Sort
-                           Sort Key: e2.empno
-                           ->  Seq Scan on empsalary e2
-(14 rows)
-
 -- Tests to ensure we don't push down the run condition when it's not valid to
 -- do so.
 -- Ensure we don't push down when the frame options show that the window
@@ -3889,6 +3863,42 @@ WHERE c = 1;
                ->  Seq Scan on empsalary
 (6 rows)
 
+-- Ensure we don't use a run condition when the WindowFunc arg contains a Var
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(empno) OVER (ORDER BY empno DESC) c
+   FROM empsalary) emp
+WHERE c = 1;
+                  QUERY PLAN                  
+----------------------------------------------
+ Subquery Scan on emp
+   Filter: (emp.c = 1)
+   ->  WindowAgg
+         ->  Sort
+               Sort Key: empsalary.empno DESC
+               ->  Seq Scan on empsalary
+(6 rows)
+
+-- As above but with ntile().
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          ntile(empno::int) OVER (ORDER BY empno DESC) nt
+   FROM empsalary) emp
+WHERE nt = 1;
+                  QUERY PLAN                  
+----------------------------------------------
+ Subquery Scan on emp
+   Filter: (emp.nt = 1)
+   ->  WindowAgg
+         ->  Sort
+               Sort Key: empsalary.empno DESC
+               ->  Seq Scan on empsalary
+(6 rows)
+
 -- Ensure we don't use a run condition when the WindowFunc contains subplans
 EXPLAIN (COSTS OFF)
 SELECT * FROM
index c0ad51ca54468d94d11b7970d84c05aa4bef579e..e35c16a82761f89fac07e3f52442b32475a7b118 100644 (file)
@@ -1120,14 +1120,14 @@ EXPLAIN (COSTS OFF)
 SELECT * FROM
   (SELECT empno,
           salary,
-          count(empno) OVER (ORDER BY salary DESC) c
+          count(1) OVER (ORDER BY salary DESC) c
    FROM empsalary) emp
 WHERE c <= 3;
 
 SELECT * FROM
   (SELECT empno,
           salary,
-          count(empno) OVER (ORDER BY salary DESC) c
+          count(1) OVER (ORDER BY salary DESC) c
    FROM empsalary) emp
 WHERE c <= 3;
 
@@ -1183,13 +1183,13 @@ SELECT empno, depname FROM
    FROM empsalary) emp
 WHERE rn < 3;
 
--- likewise with count(empno) instead of row_number()
+-- likewise with count(1) instead of row_number()
 EXPLAIN (COSTS OFF)
 SELECT * FROM
   (SELECT empno,
           depname,
           salary,
-          count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
+          count(1) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
 WHERE c <= 3;
 
@@ -1198,7 +1198,7 @@ SELECT * FROM
   (SELECT empno,
           depname,
           salary,
-          count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
+          count(1) OVER (PARTITION BY depname ORDER BY salary DESC) c
    FROM empsalary) emp
 WHERE c <= 3;
 
@@ -1209,7 +1209,7 @@ SELECT * FROM
   (SELECT empno,
           depname,
           salary,
-          count(empno) OVER () c
+          count(1) OVER () c
    FROM empsalary) emp
 WHERE c = 1;
 
@@ -1217,7 +1217,7 @@ WHERE c = 1;
 EXPLAIN (COSTS OFF)
 SELECT * FROM
   (SELECT *,
-          count(salary) OVER (PARTITION BY depname || '') c1, -- w1
+          count(1) OVER (PARTITION BY depname || '') c1, -- w1
           row_number() OVER (PARTITION BY depname) rn, -- w2
           count(*) OVER (PARTITION BY depname) c2, -- w2
           count(*) OVER (PARTITION BY '' || depname) c3, -- w3
@@ -1228,7 +1228,7 @@ SELECT * FROM
 -- Ensure we correctly filter out all of the run conditions from each window
 SELECT * FROM
   (SELECT *,
-          count(salary) OVER (PARTITION BY depname || '') c1, -- w1
+          count(1) OVER (PARTITION BY depname || '') c1, -- w1
           row_number() OVER (PARTITION BY depname) rn, -- w2
           count(*) OVER (PARTITION BY depname) c2, -- w2
           count(*) OVER (PARTITION BY '' || depname) c3, -- w3
@@ -1236,15 +1236,6 @@ SELECT * FROM
    FROM empsalary
 ) e WHERE rn <= 1 AND c1 <= 3 AND nt < 2;
 
--- Ensure we remove references to reduced outer joins as nulling rels in run
--- conditions
-EXPLAIN (COSTS OFF)
-SELECT 1 FROM
-  (SELECT ntile(e2.salary) OVER (PARTITION BY e1.depname) AS c
-   FROM empsalary e1 LEFT JOIN empsalary e2 ON TRUE
-   WHERE e1.empno = e2.empno) s
-WHERE s.c = 1;
-
 -- Tests to ensure we don't push down the run condition when it's not valid to
 -- do so.
 
@@ -1278,6 +1269,24 @@ SELECT * FROM
    FROM empsalary) emp
 WHERE c = 1;
 
+-- Ensure we don't use a run condition when the WindowFunc arg contains a Var
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(empno) OVER (ORDER BY empno DESC) c
+   FROM empsalary) emp
+WHERE c = 1;
+
+-- As above but with ntile().
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          ntile(empno::int) OVER (ORDER BY empno DESC) nt
+   FROM empsalary) emp
+WHERE nt = 1;
+
 -- Ensure we don't use a run condition when the WindowFunc contains subplans
 EXPLAIN (COSTS OFF)
 SELECT * FROM