From 8f015efafa38903dd760827e9de7f954a45f1aae Mon Sep 17 00:00:00 2001 From: jian he Date: Sun, 16 Aug 2026 14:29:10 +0900 Subject: [PATCH] Take the failed match in nfa_match as an early exit The match phase tested nfa_eval_var_match() into a local and branched on it, putting the whole of the matched case -- roughly seventy lines, of which the END-chain walk is the deepest block in the file -- inside an if whose else does nothing but unlink the state and move on. Invert the test so the unlinking is an early continue, and the rest of the VAR case loses a level of nesting. The deepest line goes from seven tabs to six. Nothing else moves. The statement sequence is unchanged apart from the inversion and the not-matched body relocating ahead of it, the counts and elemIdx are written in the same order against the same values, and the local holding the test result is gone because the test now stands alone. The continue lands on the loop's third clause exactly as falling off the end of the body did, since the state that is being unlinked was the last thing the body would have touched. The one keep-site is deliberate. Reading depth and count before the test rather than after keeps them in their original order, and leaving prevPtr's advance at the bottom of the loop keeps the singly-linked-list filter in the shape the rest of this file writes it: one place that says keep, one place that says drop. nfa_match() sees every row of every RPR query, so rpr, rpr_base, rpr_nfa and rpr_explain cover this between them without a test of its own, and none of their expected output moves. --- src/backend/executor/execRPR.c | 192 ++++++++++++++++----------------- 1 file changed, 93 insertions(+), 99 deletions(-) diff --git a/src/backend/executor/execRPR.c b/src/backend/executor/execRPR.c index 5ac4ab21f2f..f87622db6ef 100644 --- a/src/backend/executor/execRPR.c +++ b/src/backend/executor/execRPR.c @@ -829,123 +829,117 @@ nfa_match(WindowAggState *winstate, RPRNFAContext *ctx, RPRVarMatch *varMatched, if (RPRElemIsVar(elem)) { - bool matched; int depth = elem->depth; int32 count = state->counts[depth]; - matched = nfa_eval_var_match(winstate, elem, varMatched); - - if (matched) + if (!nfa_eval_var_match(winstate, elem, varMatched)) { /* - * Increment count, saturating at RPR_COUNT_INF to avoid int32 - * overflow; a saturated count then compares as "unbounded". + * Not matched - remove state. Exit alternatives were already + * created by advance phase when count >= min was satisfied. */ - if (count < RPR_COUNT_INF) - count++; + *prevPtr = nextState; + nfa_state_free(winstate, state); + continue; + } - /* Max constraint should not be exceeded */ - Assert(elem->max == RPR_QUANTITY_INF || count <= elem->max); + /* + * Increment count, saturating at RPR_COUNT_INF to avoid int32 + * overflow; a saturated count then compares as "unbounded". + */ + if (count < RPR_COUNT_INF) + count++; - state->counts[depth] = count; + /* Max constraint should not be exceeded */ + Assert(elem->max == RPR_QUANTITY_INF || count <= elem->max); + + state->counts[depth] = count; + + /* + * For VAR at max count with END next, advance through END chain + * to reach the absorption comparison point. Only deterministic + * exits (count >= max, max finite) are handled; unbounded VARs + * stay for advance phase. + * + * In nested patterns like ((A (B C){2}){2})+, a VAR reaching its + * max triggers an exit cascade: inner END increments inner group + * count, which may itself reach max, requiring an exit to the + * next outer END. The loop below walks this chain. + * + * ABSORBABLE_BRANCH marks elements inside the absorbable region; + * ABSORBABLE marks the outermost comparison point where + * count-dominance is evaluated. We chain through BRANCH elements + * until reaching the ABSORBABLE point or an element that can + * still loop (count < max). + */ + if (RPRElemIsAbsorbableBranch(elem) && + !RPRElemIsAbsorbable(elem) && + count >= elem->max && + RPRElemIsEnd(&elements[elem->next])) + { + RPRPatternElement *endElem = &elements[elem->next]; + int endDepth = endElem->depth; + int32 endCount = state->counts[endDepth]; + + /* Increment group count */ + if (endCount < RPR_COUNT_INF) + endCount++; + Assert(endElem->max == RPR_QUANTITY_INF || + endCount <= endElem->max); + + state->elemIdx = elem->next; + state->counts[endDepth] = endCount; /* - * For VAR at max count with END next, advance through END - * chain to reach the absorption comparison point. Only - * deterministic exits (count >= max, max finite) are handled; - * unbounded VARs stay for advance phase. - * - * In nested patterns like ((A (B C){2}){2})+, a VAR reaching - * its max triggers an exit cascade: inner END increments - * inner group count, which may itself reach max, requiring an - * exit to the next outer END. The loop below walks this - * chain. - * - * ABSORBABLE_BRANCH marks elements inside the absorbable - * region; ABSORBABLE marks the outermost comparison point - * where count-dominance is evaluated. We chain through - * BRANCH elements until reaching the ABSORBABLE point or an - * element that can still loop (count < max). + * Leaf VAR exited (reached max): clear its own count so the + * next occupant enters with zero, as nfa_advance_var does on + * exit (this inline path replaces that exit). depth > + * endDepth, so this leaves the group count just written + * intact. */ - if (RPRElemIsAbsorbableBranch(elem) && - !RPRElemIsAbsorbable(elem) && - count >= elem->max && - RPRElemIsEnd(&elements[elem->next])) - { - RPRPatternElement *endElem = &elements[elem->next]; - int endDepth = endElem->depth; - int32 endCount = state->counts[endDepth]; + Assert(endDepth < depth); + state->counts[depth] = 0; - /* Increment group count */ - if (endCount < RPR_COUNT_INF) - endCount++; - Assert(endElem->max == RPR_QUANTITY_INF || - endCount <= endElem->max); - - state->elemIdx = elem->next; - state->counts[endDepth] = endCount; + /* + * Chain through END elements within the absorbable region + * (ABSORBABLE_BRANCH) until reaching the comparison point + * (ABSORBABLE). Continue only on must-exit path (count >= + * max) with END next. + */ + while (RPRElemIsAbsorbableBranch(endElem) && + !RPRElemIsAbsorbable(endElem) && + endCount >= endElem->max && + RPRElemIsEnd(&elements[endElem->next])) + { + RPRPatternElement *outerEnd = &elements[endElem->next]; + int outerDepth = outerEnd->depth; + int32 outerCount = state->counts[outerDepth]; /* - * Leaf VAR exited (reached max): clear its own count so - * the next occupant enters with zero, as nfa_advance_var - * does on exit (this inline path replaces that exit). - * depth > endDepth, so this leaves the group count just - * written intact. + * Exit this intermediate group: clear its own count + * (count-clear policy). It sits below the absorbable + * comparison point, so it is excluded from the dominance + * comparison; the comparison point where the chain stops + * keeps its count. */ - Assert(endDepth < depth); - state->counts[depth] = 0; + state->counts[endDepth] = 0; - /* - * Chain through END elements within the absorbable region - * (ABSORBABLE_BRANCH) until reaching the comparison point - * (ABSORBABLE). Continue only on must-exit path (count - * >= max) with END next. - */ - while (RPRElemIsAbsorbableBranch(endElem) && - !RPRElemIsAbsorbable(endElem) && - endCount >= endElem->max && - RPRElemIsEnd(&elements[endElem->next])) - { - RPRPatternElement *outerEnd = &elements[endElem->next]; - int outerDepth = outerEnd->depth; - int32 outerCount = state->counts[outerDepth]; - - /* - * Exit this intermediate group: clear its own count - * (count-clear policy). It sits below the absorbable - * comparison point, so it is excluded from the - * dominance comparison; the comparison point where - * the chain stops keeps its count. - */ - state->counts[endDepth] = 0; - - /* Increment outer group count */ - if (outerCount < RPR_COUNT_INF) - outerCount++; - Assert(outerEnd->max == RPR_QUANTITY_INF || - outerCount <= outerEnd->max); - - state->elemIdx = endElem->next; - state->counts[outerDepth] = outerCount; - - /* Advance to next END in chain */ - endElem = outerEnd; - endDepth = outerDepth; - endCount = outerCount; - } + /* Increment outer group count */ + if (outerCount < RPR_COUNT_INF) + outerCount++; + Assert(outerEnd->max == RPR_QUANTITY_INF || + outerCount <= outerEnd->max); + + state->elemIdx = endElem->next; + state->counts[outerDepth] = outerCount; + + /* Advance to next END in chain */ + endElem = outerEnd; + endDepth = outerDepth; + endCount = outerCount; } - /* else: stay at VAR for advance phase */ - } - else - { - /* - * Not matched - remove state. Exit alternatives were already - * created by advance phase when count >= min was satisfied. - */ - *prevPtr = nextState; - nfa_state_free(winstate, state); - continue; } + /* else: stay at VAR for advance phase */ } /* Non-VAR elements: keep as-is for advance phase */