From 93e3337f67f9dc17c4918bfaa866da9bd36d9a40 Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 11 Jul 2026 13:37:13 +0800 Subject: [PATCH] Stop evaluating navigation arguments in case rows not exists Avoid evaluating RPRNavExpr arguments when the referenced navigation row does not exist, and let eval_const_expressions handle T_RPRNavExpr instead. Based on: https://github.com/assam258-5892/postgres/commits/RPR --- src/backend/executor/execExpr.c | 19 ++++++ src/backend/executor/execExprInterp.c | 19 ++++++ src/backend/optimizer/util/clauses.c | 34 +++++++++++ src/test/regress/expected/rpr_base.out | 82 ++++++++++++++++++++++++++ src/test/regress/sql/rpr_base.sql | 33 +++++++++++ 5 files changed, 187 insertions(+) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 64b0df6c9ba..f9b8f06816e 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -1182,6 +1182,7 @@ ExecInitExprRec(Expr *node, ExprState *state, RPRNavState *rprnavstate = makeNode(RPRNavState); RPRNavExpr *nav = (RPRNavExpr *) node; WindowAggState *winstate; + int skip_arg_step; bool find_navexpr = false; Assert(state->parent && IsA(state->parent, WindowAggState)); @@ -1225,9 +1226,27 @@ ExecInitExprRec(Expr *node, ExprState *state, ExprEvalPushStep(state, &scratch); + /* + * If the target row does not exist, the EEOP_RPR_NAV_SET step + * has already stored a nav_null_slot; skip evaluation of the + * argument expression and go straight to RESTORE. The + * EEOP_RPR_NAV_SET step writes a definitive resnull (false + * when the target row exists), so the jump condition is + * always up to date. + */ + skip_arg_step = state->steps_len; + scratch.opcode = EEOP_JUMP_IF_NULL; + scratch.resvalue = resv; + scratch.resnull = resnull; + scratch.d.jump.jumpdone = -1; /* set below */ + ExprEvalPushStep(state, &scratch); + /* Compile the argument expression normally */ ExecInitExprRec(nav->arg, state, resv, resnull); + /* out-of-range jump lands on the RESTORE step */ + state->steps[skip_arg_step].d.jump.jumpdone = state->steps_len; + /* Emit RESTORE opcode: restore original slot */ scratch.opcode = EEOP_RPR_NAV_RESTORE; scratch.resvalue = resv; diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index d8f4494fb5b..b7bfaf4e3d8 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -6169,11 +6169,30 @@ ExecEvalRPRNavSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext) * EEOP_RPR_NAV_RESTORE is a harmless no-op. */ if (target_pos == winstate->currentpos) + { + /* target row trivially exists; see comment below */ + *op->resnull = false; return; + } /* Fetch target row slot (returns nav_null_slot if out of range) */ target_slot = ExecRPRNavGetSlot(winstate, target_pos); + /* + * Report whether the target row exists through resnull, which the jump + * step tests before the argument expression gets to overwrite it: null + * when the row is out of range, so the jump skips the argument, and a + * definitive false otherwise, since resnull may still hold a stale value + * from a previous evaluation. + */ + if (target_slot == winstate->nav_null_slot) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + return; + } + *op->resnull = false; + /* * Update econtext to point to the target slot. Also decompress the new * slot's attributes since FETCHSOME already ran for the original slot. diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index aa8886ec210..c4320b4f442 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2838,6 +2838,40 @@ eval_const_expressions_mutator(Node *node, newexpr->ignore_nulls = expr->ignore_nulls; newexpr->location = expr->location; + return (Node *) newexpr; + } + case T_RPRNavExpr: + { + RPRNavExpr *expr = (RPRNavExpr *) node; + Expr *arg; + Expr *offset_arg; + Expr *compound_offset_arg; + RPRNavExpr *newexpr; + + /* Now, recursively simplify the arg */ + arg = (Expr *) + eval_const_expressions_mutator((Node *) expr->arg, context); + + /* ... and the offset_arg expression */ + offset_arg = (Expr *) + eval_const_expressions_mutator((Node *) expr->offset_arg, + context); + + /* ... and the compound_offset_arg expression */ + compound_offset_arg = (Expr *) + eval_const_expressions_mutator((Node *) expr->compound_offset_arg, + context); + + /* And build the replacement RPRNavExpr node */ + newexpr = makeNode(RPRNavExpr); + newexpr->kind = expr->kind; + newexpr->arg = arg; + newexpr->offset_arg = offset_arg; + newexpr->compound_offset_arg = compound_offset_arg; + newexpr->resulttype = expr->resulttype; + newexpr->resultcollid = expr->resultcollid; + newexpr->location = expr->location; + return (Node *) newexpr; } case T_FuncExpr: diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out index 4ae8d81445b..2dd2f9f7b2c 100644 --- a/src/test/regress/expected/rpr_base.out +++ b/src/test/regress/expected/rpr_base.out @@ -1797,6 +1797,88 @@ DROP FUNCTION rpr_nested(int, int); CREATE TABLE rpr_nav (id INT, val INT); INSERT INTO rpr_nav VALUES (1, 10), (2, 20), (3, 15), (4, 25), (5, 30); +-- Avoid evaluating the inner argument expression when the previous row is out +-- of range or does not exist +SELECT id, count(*) OVER w AS cnt +FROM rpr_nav t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS PREV(val is not null) is null); + id | cnt +----+----- + 1 | 1 + 2 | 0 + 3 | 0 + 4 | 0 + 5 | 0 +(5 rows) + +SELECT id, count(*) OVER w AS cnt +FROM rpr_nav t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS LAST(val is not null) is null); + id | cnt +----+----- + 1 | 0 + 2 | 0 + 3 | 0 + 4 | 0 + 5 | 0 +(5 rows) + +SELECT id, count(*) OVER w AS cnt +FROM rpr_nav t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS FIRST(val is not null) is null); + id | cnt +----+----- + 1 | 0 + 2 | 0 + 3 | 0 + 4 | 0 + 5 | 0 +(5 rows) + +SELECT id, count(*) OVER w AS cnt +FROM rpr_nav t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS LAST(val is not null) is null); + id | cnt +----+----- + 1 | 0 + 2 | 0 + 3 | 0 + 4 | 0 + 5 | 0 +(5 rows) + +SELECT id, count(*) OVER w AS cnt +FROM rpr_nav t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS PREV(val is null) is null); + id | cnt +----+----- + 1 | 1 + 2 | 0 + 3 | 0 + 4 | 0 + 5 | 0 +(5 rows) + +WITH t(id, v) AS (VALUES (1, 10), (2, 20)) +SELECT id, count(*) OVER w AS cnt +FROM t +WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS PREV(v IS NULL)); + id | cnt +----+----- + 1 | 0 + 2 | 0 +(2 rows) + +-- test DEFINE clause expression constant folding. +WITH t(id, v) AS (VALUES (1, 10)) +SELECT id, count(*) OVER w AS cnt +FROM t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS PREV(v IS NULL)); + id | cnt +----+----- + 1 | 0 +(1 row) + -- PREV function - reference previous row in pattern SELECT id, val, COUNT(*) OVER w as cnt FROM rpr_nav diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql index 72083964d14..8cb05137ab5 100644 --- a/src/test/regress/sql/rpr_base.sql +++ b/src/test/regress/sql/rpr_base.sql @@ -1330,6 +1330,39 @@ CREATE TABLE rpr_nav (id INT, val INT); INSERT INTO rpr_nav VALUES (1, 10), (2, 20), (3, 15), (4, 25), (5, 30); +-- Avoid evaluating the inner argument expression when the previous row is out +-- of range or does not exist +SELECT id, count(*) OVER w AS cnt +FROM rpr_nav t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS PREV(val is not null) is null); + +SELECT id, count(*) OVER w AS cnt +FROM rpr_nav t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS LAST(val is not null) is null); + +SELECT id, count(*) OVER w AS cnt +FROM rpr_nav t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS FIRST(val is not null) is null); + +SELECT id, count(*) OVER w AS cnt +FROM rpr_nav t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS LAST(val is not null) is null); + +SELECT id, count(*) OVER w AS cnt +FROM rpr_nav t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS PREV(val is null) is null); + +WITH t(id, v) AS (VALUES (1, 10), (2, 20)) +SELECT id, count(*) OVER w AS cnt +FROM t +WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS PREV(v IS NULL)); + +-- test DEFINE clause expression constant folding. +WITH t(id, v) AS (VALUES (1, 10)) +SELECT id, count(*) OVER w AS cnt +FROM t +WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A) DEFINE A AS PREV(v IS NULL)); + -- PREV function - reference previous row in pattern SELECT id, val, COUNT(*) OVER w as cnt FROM rpr_nav -- 2.50.1 (Apple Git-155)