From a066f0d6b6e9e9926c1a34dddcc9f95a2fbc51ff Mon Sep 17 00:00:00 2001 From: Henson Choi Date: Sat, 29 Aug 2026 16:13:59 +0900 Subject: [PATCH] Evaluate an RPR DEFINE clause in the context reserved for it WindowAgg builds a third ExprContext when a DEFINE clause is present and resets it twice over, once per row in rpr_prepare_row() and once per NFA context in nfa_reevaluate_dependent_vars(). The predicate's own scratch never reached it. ExecEvalExpr() does not switch memory contexts: its econtext argument carries the slots and parameters the expression reads, not the target of its allocations, and the header comment leaves the switch to the caller. That scratch therefore went wherever the current context happened to point, which is ExecutorState, and ExecutorState lives as long as the query. A DEFINE that allocates left one result behind per evaluation and the backend grew through the whole scan. Call ExecEvalExprSwitchContext() instead. Nothing outlives the switch: the result is reduced to a bool before anything else runs, and the only other allocations this file makes name partcontext outright. The navigation opcodes were already right. ExecEvalRPRNavRestore() copies a pass-by-reference result into econtext->ecxt_per_tuple_memory under a switch of its own. So the context did receive the navigation copies and nothing else, which is how a leak survived having a context built to hold it. Measured on 300k rows of 200-character values with a small shared_buffers, so the table's own pages do not dominate. With DEFINE a AS upper(s) > '', peak resident size falls from 94MB to 18MB, against 11MB for the same scan carrying no window at all. A predicate the match start feeds is re-evaluated once per context rather than once per row and pays accordingly: upper(FIRST(s)) > '' falls from 407MB to 18MB. Results and run time are unchanged. No test accompanies this. A leak of this shape shows up in resident size, which the regression suite does not observe. --- src/backend/executor/execRPR.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/backend/executor/execRPR.c b/src/backend/executor/execRPR.c index 68664568556..b10aa7df60f 100644 --- a/src/backend/executor/execRPR.c +++ b/src/backend/executor/execRPR.c @@ -771,7 +771,14 @@ nfa_eval_var_match(WindowAggState *winstate, RPRPatternElement *elem, Datum result; bool isnull; - result = ExecEvalExpr(exprState, winstate->rprContext, &isnull); + /* + * Switch into rprContext's per-tuple memory: ExecEvalExpr() does not + * do it for us, and the predicate's scratch has to land in the + * context that rpr_prepare_row() and nfa_reevaluate_dependent_vars() + * reset, not in the caller's longer-lived one. + */ + result = ExecEvalExprSwitchContext(exprState, winstate->rprContext, + &isnull); varMatched[varId] = (!isnull && DatumGetBool(result)) ? RPR_VAR_TRUE : RPR_VAR_FALSE; }