From cb4d129a2ca68759e9f6fdbc07b756239164cc58 Mon Sep 17 00:00:00 2001 From: jian he Date: Sat, 11 Jul 2026 13:37:13 +0800 Subject: [PATCH v51 2/3] 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 | 17 ++++++ src/backend/optimizer/util/clauses.c | 36 +++++++++++ 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 de13ac693f..80794fe47c 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -1201,6 +1201,7 @@ ExecInitExprRec(Expr *node, ExprState *state, */ RPRNavExpr *nav = (RPRNavExpr *) node; WindowAggState *winstate; + int skip_arg_step; Assert(state->parent && IsA(state->parent, WindowAggState)); winstate = (WindowAggState *) state->parent; @@ -1214,9 +1215,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 33960b757d..3901fa2745 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -6156,11 +6156,28 @@ 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); + /* + * If the target row does exist, resnull must still be set to a definitive + * false, since it may hold a stale value from a previous evaluation and + * the jump step tests it before the argument expression overwrites it. +1 */ + 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 aa8886ec21..c13376e987 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2838,6 +2838,42 @@ 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->offset = expr->offset; + newexpr->compound_offset = expr->compound_offset; + 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 b5da8de096..9da3141eef 100644 --- a/src/test/regress/expected/rpr_base.out +++ b/src/test/regress/expected/rpr_base.out @@ -1724,6 +1724,88 @@ DETAIL: Pattern has 32768 elements, maximum is 32767. 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 eed5ea958a..274bcbebf1 100644 --- a/src/test/regress/sql/rpr_base.sql +++ b/src/test/regress/sql/rpr_base.sql @@ -1273,6 +1273,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.34.1