From c7a2addcc6ea0ff7855ce65e4813876e780309af Mon Sep 17 00:00:00 2001 From: Henson Choi Date: Wed, 15 Jul 2026 08:25:12 +0900 Subject: [PATCH] Bound the END search in row pattern absorption analysis isUnboundedStart walks the element array from a group's first child to the END that closes it, following ->next while the depth stays at or below the child's: while (e->depth >= startDepth) e = &pattern->elements[e->next]; This assumes idx really is the first child of an unbounded group, so an END sits one level out. When it is not -- a {1,1} group in a non-leading position, as in PATTERN (START (UP DOWN)) -- the call arrives with startDepth 0, no element is ever shallower than that, and the walk runs off the end of the sequence and loops without terminating. It is a planning loop with no CHECK_FOR_INTERRUPTS, so statement_timeout does not stop it and even a bare EXPLAIN hangs until the backend is killed. The pattern optimizer never feeds such a tree here in practice, because it unwraps a {1,1} group before the element array is built, but the analysis must not depend on that. Stop the walk at FIN or a missing link and let the existing END test reject the sequence, so a non-normalized tree yields "not absorbable" instead of spinning. The output is unchanged for every reachable pattern; this only removes the dependence on the optimizer having run first. No regression test can reach it, since the suite always plans through the optimizer. --- src/backend/optimizer/plan/rpr.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/backend/optimizer/plan/rpr.c b/src/backend/optimizer/plan/rpr.c index da918a908a7..e5a50662a20 100644 --- a/src/backend/optimizer/plan/rpr.c +++ b/src/backend/optimizer/plan/rpr.c @@ -1623,9 +1623,21 @@ isUnboundedStart(RPRPattern *pattern, RPRElemIdx idx) if (!isFixedLengthChildren(pattern, idx, startDepth)) return false; - /* Find the END element at startDepth - 1 */ + /* + * Find the END that closes the group beginning at idx, at startDepth - 1. + * + * This assumes idx is the first child of an unbounded group, so such an + * END exists. computeAbsorbability runs on the normalized tree, where a + * {1,1} group has been unwrapped and the only fixed-length sequences left + * are real group bodies -- but do not depend on that here. A {1,1} group + * in a non-leading position (START (UP DOWN)) reaches this walk with + * startDepth 0, where no element is ever shallower than startDepth, so + * stop at FIN or a missing link and let the END test below reject it + * rather than walk off the end. + */ e = &pattern->elements[idx]; - while (e->depth >= startDepth) + while (e->depth >= startDepth && !RPRElemIsFin(e) && + e->next != RPR_ELEMIDX_INVALID) e = &pattern->elements[e->next]; /* END must be unbounded greedy */ -- 2.50.1 (Apple Git-155)