diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index e1a5bc7e95..030463cb42 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -2283,8 +2283,6 @@ expression_tree_walker_impl(Node *node, return true; if (WALK(wc->endOffset)) return true; - if (WALK(wc->runCondition)) - return true; } break; case T_CTECycleClause: @@ -2629,8 +2627,6 @@ query_tree_walker_impl(Query *query, return true; if (WALK(wc->endOffset)) return true; - if (WALK(wc->runCondition)) - return true; } } @@ -3316,7 +3312,6 @@ expression_tree_mutator_impl(Node *node, MUTATE(newnode->orderClause, wc->orderClause, List *); MUTATE(newnode->startOffset, wc->startOffset, Node *); MUTATE(newnode->endOffset, wc->endOffset, Node *); - MUTATE(newnode->runCondition, wc->runCondition, List *); return (Node *) newnode; } break; @@ -3646,7 +3641,6 @@ query_tree_mutator_impl(Query *query, FLATCOPY(newnode, wc, WindowClause); MUTATE(newnode->startOffset, wc->startOffset, Node *); MUTATE(newnode->endOffset, wc->endOffset, Node *); - MUTATE(newnode->runCondition, wc->runCondition, List *); resultlist = lappend(resultlist, (Node *) newnode); } diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 84c4de488a..ae2e930931 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -2208,7 +2208,7 @@ set_dummy_rel_pathlist(RelOptInfo *rel) * the run condition will handle all of the required filtering. * * Returns true if 'opexpr' was found to be useful and was added to the - * WindowClauses runCondition. We also set *keep_original accordingly and add + * WindowFunc's runCondition. We also set *keep_original accordingly and add * 'attno' to *run_cond_attrs offset by FirstLowInvalidHeapAttributeNumber. * If the 'opexpr' cannot be used then we set *keep_original to true and * return false. @@ -2361,7 +2361,7 @@ find_window_run_conditions(Query *subquery, RangeTblEntry *rte, Index rti, *keep_original = true; runopexpr = opexpr; - /* determine the operator to use for the runCondition qual */ + /* determine the operator to use for the WindwFuncRunCondition */ runoperator = get_opfamily_member(opinfo->opfamily_id, opinfo->oplefttype, opinfo->oprighttype, @@ -2372,27 +2372,15 @@ find_window_run_conditions(Query *subquery, RangeTblEntry *rte, Index rti, if (runopexpr != NULL) { - Expr *newexpr; + WindowFuncRunCondition *runcond; - /* - * Build the qual required for the run condition keeping the - * WindowFunc on the same side as it was originally. - */ - if (wfunc_left) - newexpr = make_opclause(runoperator, - runopexpr->opresulttype, - runopexpr->opretset, (Expr *) wfunc, - otherexpr, runopexpr->opcollid, - runopexpr->inputcollid); - else - newexpr = make_opclause(runoperator, - runopexpr->opresulttype, - runopexpr->opretset, - otherexpr, (Expr *) wfunc, - runopexpr->opcollid, - runopexpr->inputcollid); + runcond = makeNode(WindowFuncRunCondition); + runcond->opno = runoperator; + runcond->inputcollid = runopexpr->inputcollid; + runcond->wfunc_left = wfunc_left; + runcond->arg = copyObject(otherexpr); - wclause->runCondition = lappend(wclause->runCondition, newexpr); + wfunc->runCondition = lappend(wfunc->runCondition, runcond); /* record that this attno was used in a run condition */ *run_cond_attrs = bms_add_member(*run_cond_attrs, @@ -2406,9 +2394,9 @@ find_window_run_conditions(Query *subquery, RangeTblEntry *rte, Index rti, /* * check_and_push_window_quals - * Check if 'clause' is a qual that can be pushed into a WindowFunc's - * WindowClause as a 'runCondition' qual. These, when present, allow - * some unnecessary work to be skipped during execution. + * Check if 'clause' is a qual that can be pushed into a WindowFunc + * as a 'runCondition' qual. These, when present, allow some unnecessary + * work to be skipped during execution. * * 'run_cond_attrs' will be populated with all targetlist resnos of subquery * targets (offset by FirstLowInvalidHeapAttributeNumber) that we pushed diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 610f4a56d6..fa85f0e0ff 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2697,7 +2697,7 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path) wc->inRangeColl, wc->inRangeAsc, wc->inRangeNullsFirst, - wc->runCondition, + best_path->runCondition, best_path->qual, best_path->topwindow, subplan); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 2e2458b128..35923ba5bc 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -865,9 +865,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, EXPRKIND_LIMIT); wc->endOffset = preprocess_expression(root, wc->endOffset, EXPRKIND_LIMIT); - wc->runCondition = (List *) preprocess_expression(root, - (Node *) wc->runCondition, - EXPRKIND_TARGET); } parse->limitOffset = preprocess_expression(root, parse->limitOffset, @@ -4474,9 +4471,11 @@ create_one_window_path(PlannerInfo *root, { WindowClause *wc = lfirst_node(WindowClause, l); List *window_pathkeys; + List *runcondition = NIL; int presorted_keys; bool is_sorted; bool topwindow; + ListCell *lc2; window_pathkeys = make_pathkeys_for_window(root, wc, @@ -4524,7 +4523,6 @@ create_one_window_path(PlannerInfo *root, * we do need to account for the increase in tlist width. */ int64 tuple_width = window_target->width; - ListCell *lc2; window_target = copy_pathtarget(window_target); foreach(lc2, wflists->windowFuncs[wc->winref]) @@ -4546,17 +4544,53 @@ create_one_window_path(PlannerInfo *root, topwindow = foreach_current_index(l) == list_length(activeWindows) - 1; /* - * Accumulate all of the runConditions from each intermediate - * WindowClause. The top-level WindowAgg must pass these as a qual so - * that it filters out unwanted tuples correctly. + * Collect the WindowFuncRunConditions from each WindowFunc and + * convert them into OpExprs */ - if (!topwindow) - topqual = list_concat(topqual, wc->runCondition); + foreach (lc2, wflists->windowFuncs[wc->winref]) + { + ListCell *lc3; + WindowFunc *wfunc = lfirst_node(WindowFunc, lc2); + + foreach (lc3, wfunc->runCondition) + { + WindowFuncRunCondition *rc = + lfirst_node(WindowFuncRunCondition, lc3); + Expr *opexpr; + Expr *leftop; + Expr *rightop; + + if (rc->wfunc_left) + { + leftop = copyObject(wfunc); + rightop = copyObject(rc->arg); + } + else + { + leftop = copyObject(rc->arg); + rightop = copyObject(wfunc); + } + + opexpr = make_opclause(rc->opno, + BOOLOID, + false, + leftop, + rightop, + InvalidOid, + rc->inputcollid); + + runcondition = lappend(runcondition, opexpr); + + if (!topwindow) + topqual = lappend(topqual, opexpr); + } + } path = (Path *) create_windowagg_path(root, window_rel, path, window_target, wflists->windowFuncs[wc->winref], - wc, topwindow ? topqual : NIL, topwindow); + runcondition, wc, + topwindow ? topqual : NIL, topwindow); } add_path(window_rel, path); diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index aa83dd3636..0237ff57d5 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -2122,14 +2122,6 @@ perform_pullup_replace_vars(PlannerInfo *root, parse->returningList = (List *) pullup_replace_vars((Node *) parse->returningList, rvcontext); - foreach(lc, parse->windowClause) - { - WindowClause *wc = lfirst_node(WindowClause, lc); - - if (wc->runCondition != NIL) - wc->runCondition = (List *) - pullup_replace_vars((Node *) wc->runCondition, rvcontext); - } if (parse->onConflict) { parse->onConflict->onConflictSet = (List *) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 94eb56a1e7..c9b4a0f5eb 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2545,6 +2545,7 @@ eval_const_expressions_mutator(Node *node, newexpr->inputcollid = expr->inputcollid; newexpr->args = args; newexpr->aggfilter = aggfilter; + newexpr->runCondition = expr->runCondition; newexpr->winref = expr->winref; newexpr->winstar = expr->winstar; newexpr->winagg = expr->winagg; diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 8dbf790e89..1618be8c8e 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -3467,6 +3467,7 @@ create_windowagg_path(PlannerInfo *root, Path *subpath, PathTarget *target, List *windowFuncs, + List *rundcondition, WindowClause *winclause, List *qual, bool topwindow) @@ -3491,6 +3492,7 @@ create_windowagg_path(PlannerInfo *root, pathnode->subpath = subpath; pathnode->winclause = winclause; pathnode->qual = qual; + pathnode->runCondition = rundcondition; pathnode->topwindow = topwindow; /* diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 4b50278fd0..219bf34c33 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -2950,7 +2950,6 @@ transformWindowDefinitions(ParseState *pstate, rangeopfamily, rangeopcintype, &wc->endInRangeFunc, windef->endOffset); - wc->runCondition = NIL; wc->winref = winref; result = lappend(result, wc); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 9300c7b9ab..2b7d66bef3 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -3734,6 +3734,7 @@ transformJsonAggConstructor(ParseState *pstate, JsonAggConstructor *agg_ctor, /* wincollid and inputcollid will be set by parse_collate.c */ wfunc->args = args; wfunc->aggfilter = aggfilter; + wfunc->runCondition = NIL; /* winref will be set by transformWindowFuncCall */ wfunc->winstar = false; wfunc->winagg = true; diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index fdb3e6df33..42109535bb 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -834,6 +834,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, wfunc->winstar = agg_star; wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE); wfunc->aggfilter = agg_filter; + wfunc->runCondition = NIL; wfunc->location = location; /* diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 476d55dd24..9eb53b44dd 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1492,8 +1492,6 @@ typedef struct WindowClause int frameOptions; /* frame_clause options, see WindowDef */ Node *startOffset; /* expression for starting bound, if any */ Node *endOffset; /* expression for ending bound, if any */ - /* qual to help short-circuit execution */ - List *runCondition pg_node_attr(query_jumble_ignore); /* in_range function for startOffset */ Oid startInRangeFunc pg_node_attr(query_jumble_ignore); /* in_range function for endOffset */ diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 534692bee1..c8cebfe375 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -2299,6 +2299,7 @@ typedef struct WindowAggPath Path *subpath; /* path representing input source */ WindowClause *winclause; /* WindowClause we'll be using */ List *qual; /* lower-level WindowAgg runconditions */ + List *runCondition; /* List of WindowFuncRunConditions */ bool topwindow; /* false for all apart from the WindowAgg * that's closest to the root of the plan */ } WindowAggPath; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 4a154606d2..c17d049bb5 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -561,6 +561,8 @@ typedef struct WindowFunc List *args; /* FILTER expression, if any */ Expr *aggfilter; + /* List of WindowFuncRunConditions to help short-circuit execution */ + List *runCondition pg_node_attr(query_jumble_ignore); /* index of associated WindowClause */ Index winref; /* true if argument list was really '*' */ @@ -571,6 +573,21 @@ typedef struct WindowFunc int location; } WindowFunc; + +typedef struct WindowFuncRunCondition +{ + Expr xpr; + + /* PG_OPERATOR OID of the operator */ + Oid opno; + /* OID of collation that operator should use */ + Oid inputcollid pg_node_attr(query_jumble_ignore); + /* true of WindowFunc belongs on the left of the resulting OpExpr */ + bool wfunc_left; + /* argument to the run condition */ + Expr *arg; +} WindowFuncRunCondition; + /* * SubscriptingRef: describes a subscripting operation over a container * (array, etc). diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index c43d97b48a..38faf607b2 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -250,6 +250,7 @@ extern WindowAggPath *create_windowagg_path(PlannerInfo *root, Path *subpath, PathTarget *target, List *windowFuncs, + List *runcondition, WindowClause *winclause, List *qual, bool topwindow); diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index 60de2cbf96..95404e7432 100644 --- a/src/test/regress/expected/window.out +++ b/src/test/regress/expected/window.out @@ -4207,6 +4207,23 @@ WHERE s.c = 1; -> Seq Scan on empsalary e2 (14 rows) +-- A case where the WindowFunc has Vars from other query levels +EXPLAIN (COSTS OFF) +SELECT 1 FROM + (SELECT ntile(s1.x) OVER () AS c + FROM (SELECT (SELECT 1) AS x) AS s1) s +WHERE s.c = 1; + QUERY PLAN +-------------------------------------------------- + Subquery Scan on s + Filter: (s.c = 1) + -> WindowAgg + Run Condition: (ntile($0) OVER (?) <= 1) + InitPlan 1 (returns $0) + -> Result + -> Result +(7 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 diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql index 678aa1a21c..7da89ebf5c 100644 --- a/src/test/regress/sql/window.sql +++ b/src/test/regress/sql/window.sql @@ -1377,6 +1377,13 @@ SELECT 1 FROM WHERE e1.empno = e2.empno) s WHERE s.c = 1; +-- A case where the WindowFunc has Vars from other query levels +EXPLAIN (COSTS OFF) +SELECT 1 FROM + (SELECT ntile(s1.x) OVER () AS c + FROM (SELECT (SELECT 1) AS x) AS s1) s +WHERE s.c = 1; + -- Tests to ensure we don't push down the run condition when it's not valid to -- do so.