From 518d23ab243023ab94c5e4f4bca4d8a00c177ba1 Mon Sep 17 00:00:00 2001 From: Henson Choi Date: Thu, 26 Feb 2026 01:29:17 +0900 Subject: [PATCH 09/10] Fix use-after-free in NFA alternation and optional-VAR routing --- src/backend/executor/nodeWindowAgg.c | 42 +++++------ src/test/regress/expected/rpr_explain.out | 52 ++++++------- src/test/regress/expected/rpr_nfa.out | 90 ++++++++++++++++++++++- src/test/regress/sql/rpr_nfa.sql | 73 +++++++++++++++++- 4 files changed, 202 insertions(+), 55 deletions(-) diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index 0aa5df3e153..cd41eff394e 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -6070,15 +6070,17 @@ nfa_route_to_elem(WindowAggState *winstate, RPRNFAContext *ctx, { if (RPRElemIsVar(nextElem)) { - nfa_add_state_unique(winstate, ctx, state); - if (RPRElemCanSkip(nextElem)) - { - RPRNFAState *skipState; + RPRNFAState *skipState = NULL; + /* Create skip state before add_unique, which may free state */ + if (RPRElemCanSkip(nextElem)) skipState = nfa_state_create(winstate, nextElem->next, state->counts, state->isAbsorbable); + + nfa_add_state_unique(winstate, ctx, state); + + if (skipState != NULL) nfa_advance_state(winstate, ctx, skipState, currentPos, initialAdvance); - } } else { @@ -6099,7 +6101,6 @@ nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx, RPRPattern *pattern = winstate->rpPattern; RPRPatternElement *elements = pattern->elements; RPRElemIdx altIdx = elem->next; - bool first = true; while (altIdx >= 0 && altIdx < pattern->numElements) { @@ -6110,25 +6111,16 @@ nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx, if (altElem->depth <= elem->depth) break; - if (first) - { - state->elemIdx = altIdx; - newState = state; - first = false; - } - else - { - newState = nfa_state_create(winstate, altIdx, - state->counts, state->isAbsorbable); - } + /* Create independent state for each branch */ + newState = nfa_state_create(winstate, altIdx, + state->counts, state->isAbsorbable); /* Recursively process this branch before next */ nfa_advance_state(winstate, ctx, newState, currentPos, initialAdvance); altIdx = altElem->jump; } - /* ALT must have at least one branch */ - Assert(!first); + nfa_state_free(winstate, state); } /* @@ -6229,12 +6221,12 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx, currentPos, initialAdvance); /* - * Fast-forward fallback for nullable bodies. E.g. (A?){2,3} when - * A doesn't match: the loop-back produces empty iterations that - * cycle detection would kill. Instead, exit directly treating all - * remaining required iterations as empty. Route to elem->next - * (not nfa_advance_end) to avoid creating competing greedy/reluctant - * loop states. + * Fast-forward fallback for nullable bodies. E.g. (A?){2,3} when A + * doesn't match: the loop-back produces empty iterations that cycle + * detection would kill. Instead, exit directly treating all + * remaining required iterations as empty. Route to elem->next (not + * nfa_advance_end) to avoid creating competing greedy/reluctant loop + * states. */ if (ffState != NULL) { diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out index dacb8d6907f..340408cc454 100644 --- a/src/test/regress/expected/rpr_explain.out +++ b/src/test/regress/expected/rpr_explain.out @@ -255,7 +255,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: a (b | c) Storage: Memory Maximum Storage: NkB - NFA States: 3 peak, 28 total, 0 merged + NFA States: 3 peak, 35 total, 0 merged NFA Contexts: 2 peak, 21 total, 6 pruned NFA: 7 matched (len 2/2/2.0), 0 mismatched NFA: 0 absorbed, 7 skipped (len 1/1/1.0) @@ -295,7 +295,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: a ((b | c) (d | e))* Storage: Memory Maximum Storage: NkB - NFA States: 4 peak, 49 total, 0 merged + NFA States: 4 peak, 61 total, 0 merged NFA Contexts: 3 peak, 31 total, 24 pruned NFA: 6 matched (len 1/1/1.0), 0 mismatched -> Function Scan on generate_series s (actual rows=30.00 loops=1) @@ -380,7 +380,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b | c) (d | e) Storage: Memory Maximum Storage: NkB - NFA States: 5 peak, 363 total, 0 merged + NFA States: 6 peak, 524 total, 0 merged NFA Contexts: 3 peak, 101 total, 20 pruned NFA: 20 matched (len 2/2/2.0), 40 mismatched (len 2/2/2.0) NFA: 0 absorbed, 20 skipped (len 1/1/1.0) @@ -507,7 +507,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b){8} Storage: Memory Maximum Storage: NkB - NFA States: 16 peak, 548 total, 0 merged + NFA States: 17 peak, 995 total, 0 merged NFA Contexts: 8 peak, 101 total, 1 pruned NFA: 12 matched (len 8/8/8.0), 3 mismatched (len 2/4/3.0) NFA: 0 absorbed, 84 skipped (len 1/7/4.0) @@ -548,7 +548,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b){2} (c | d) Storage: Memory Maximum Storage: NkB - NFA States: 6 peak, 111 total, 0 merged + NFA States: 7 peak, 181 total, 0 merged NFA Contexts: 3 peak, 41 total, 12 pruned NFA: 9 matched (len 3/3/3.0), 1 mismatched (len 2/2/2.0) NFA: 0 absorbed, 18 skipped (len 1/2/1.5) @@ -589,7 +589,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b){2} c Storage: Memory Maximum Storage: NkB - NFA States: 5 peak, 109 total, 0 merged + NFA States: 6 peak, 177 total, 0 merged NFA Contexts: 3 peak, 41 total, 2 pruned NFA: 12 matched (len 3/3/3.0), 2 mismatched (len 2/2/2.0) NFA: 0 absorbed, 24 skipped (len 1/2/1.5) @@ -629,7 +629,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b){3,} Storage: Memory Maximum Storage: NkB - NFA States: 6 peak, 161 total, 0 merged + NFA States: 7 peak, 243 total, 0 merged NFA Contexts: 3 peak, 41 total, 0 pruned NFA: 1 matched (len 40/40/40.0), 0 mismatched NFA: 0 absorbed, 39 skipped (len 1/2/1.0) @@ -669,7 +669,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b | c)+ d Storage: Memory Maximum Storage: NkB - NFA States: 15 peak, 753 total, 0 merged + NFA States: 16 peak, 1004 total, 0 merged NFA Contexts: 4 peak, 101 total, 0 pruned NFA: 25 matched (len 4/4/4.0), 0 mismatched NFA: 0 absorbed, 75 skipped (len 1/3/2.0) @@ -710,7 +710,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | a b)+ Storage: Memory Maximum Storage: NkB - NFA States: 5 peak, 204 total, 0 merged + NFA States: 6 peak, 306 total, 0 merged NFA Contexts: 3 peak, 101 total, 99 pruned NFA: 1 matched (len 1/1/1.0), 0 mismatched -> Function Scan on generate_series s (actual rows=100.00 loops=1) @@ -749,7 +749,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b)+ Storage: Memory Maximum Storage: NkB - NFA States: 5 peak, 3336 total, 0 merged + NFA States: 6 peak, 5004 total, 0 merged NFA Contexts: 3 peak, 1001 total, 333 pruned NFA: 334 matched (len 1/2/2.0), 0 mismatched NFA: 0 absorbed, 333 skipped (len 1/1/1.0) @@ -1579,8 +1579,8 @@ WINDOW w AS ( "Pattern": "(a | b){8}", + "Storage": "Memory", + "Maximum Storage": 0, + - "NFA States Peak": 16, + - "NFA States Total": 548, + + "NFA States Peak": 17, + + "NFA States Total": 995, + "NFA States Merged": 0, + "NFA Contexts Peak": 8, + "NFA Contexts Total": 101, + @@ -1971,7 +1971,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b) (c | d | e) Storage: Memory Maximum Storage: NkB - NFA States: 5 peak, 282 total, 0 merged + NFA States: 6 peak, 423 total, 0 merged NFA Contexts: 3 peak, 101 total, 40 pruned NFA: 20 matched (len 2/2/2.0), 20 mismatched (len 2/2/2.0) NFA: 0 absorbed, 20 skipped (len 1/1/1.0) @@ -2337,7 +2337,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b)+ c Storage: Memory Maximum Storage: NkB - NFA States: 8 peak, 2004 total, 0 merged + NFA States: 9 peak, 3006 total, 0 merged NFA Contexts: 3 peak, 501 total, 1 pruned NFA: 166 matched (len 3/3/3.0), 1 mismatched (len 2/2/2.0) NFA: 0 absorbed, 332 skipped (len 1/2/1.5) @@ -2832,7 +2832,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b) c Storage: Memory Maximum Storage: NkB - NFA States: 3 peak, 202 total, 0 merged + NFA States: 4 peak, 303 total, 0 merged NFA Contexts: 3 peak, 101 total, 40 pruned NFA: 20 matched (len 2/2/2.0), 20 mismatched (len 2/2/2.0) NFA: 0 absorbed, 20 skipped (len 1/1/1.0) @@ -2876,7 +2876,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b | c | d) e Storage: Memory Maximum Storage: NkB - NFA States: 5 peak, 404 total, 0 merged + NFA States: 6 peak, 505 total, 0 merged NFA Contexts: 3 peak, 101 total, 0 pruned NFA: 20 matched (len 2/2/2.0), 60 mismatched (len 2/2/2.0) NFA: 0 absorbed, 20 skipped (len 1/1/1.0) @@ -2916,7 +2916,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b)+ c Storage: Memory Maximum Storage: NkB - NFA States: 8 peak, 204 total, 0 merged + NFA States: 9 peak, 306 total, 0 merged NFA Contexts: 3 peak, 51 total, 1 pruned NFA: 16 matched (len 3/3/3.0), 1 mismatched (len 2/2/2.0) NFA: 0 absorbed, 32 skipped (len 1/2/1.5) @@ -2954,7 +2954,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b | c | d | e) Storage: Memory Maximum Storage: NkB - NFA States: 6 peak, 505 total, 0 merged + NFA States: 7 peak, 606 total, 0 merged NFA Contexts: 2 peak, 101 total, 0 pruned NFA: 100 matched (len 1/1/1.0), 0 mismatched -> Function Scan on generate_series s (actual rows=100.00 loops=1) @@ -2991,7 +2991,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b) c d Storage: Memory Maximum Storage: NkB - NFA States: 3 peak, 122 total, 0 merged + NFA States: 4 peak, 183 total, 0 merged NFA Contexts: 3 peak, 61 total, 16 pruned NFA: 15 matched (len 3/3/3.0), 14 mismatched (len 2/2/2.0) NFA: 0 absorbed, 15 skipped (len 1/1/1.0) @@ -3029,7 +3029,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b) c (d | e) f Storage: Memory Maximum Storage: NkB - NFA States: 4 peak, 219 total, 0 merged + NFA States: 5 peak, 337 total, 0 merged NFA Contexts: 3 peak, 101 total, 67 pruned NFA: 0 matched, 33 mismatched (len 2/4/3.0) -> Function Scan on generate_series s (actual rows=100.00 loops=1) @@ -3066,7 +3066,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a+" | b+") c Storage: Memory Maximum Storage: NkB - NFA States: 4 peak, 162 total, 0 merged + NFA States: 5 peak, 223 total, 0 merged NFA Contexts: 3 peak, 61 total, 1 pruned NFA: 20 matched (len 2/2/2.0), 19 mismatched (len 2/2/2.0) NFA: 0 absorbed, 20 skipped (len 1/1/1.0) @@ -3104,7 +3104,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: a b (c | d) Storage: Memory Maximum Storage: NkB - NFA States: 3 peak, 75 total, 0 merged + NFA States: 3 peak, 89 total, 0 merged NFA Contexts: 3 peak, 61 total, 32 pruned NFA: 14 matched (len 3/3/3.0), 0 mismatched NFA: 0 absorbed, 14 skipped (len 1/1/1.0) @@ -3143,7 +3143,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: a ((b | c) d | e) Storage: Memory Maximum Storage: NkB - NFA States: 4 peak, 29 total, 0 merged + NFA States: 4 peak, 37 total, 0 merged NFA Contexts: 3 peak, 21 total, 17 pruned NFA: 0 matched, 3 mismatched (len 3/3/3.0) -> Function Scan on generate_series s (actual rows=20.00 loops=1) @@ -3181,7 +3181,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (c (a | b) | d) Storage: Memory Maximum Storage: NkB - NFA States: 4 peak, 47 total, 0 merged + NFA States: 5 peak, 73 total, 0 merged NFA Contexts: 3 peak, 21 total, 10 pruned NFA: 5 matched (len 1/1/1.0), 5 mismatched (len 2/2/2.0) -> Function Scan on generate_series s (actual rows=20.00 loops=1) @@ -3341,7 +3341,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b)+ Storage: Memory Maximum Storage: NkB - NFA States: 5 peak, 162 total, 0 merged + NFA States: 6 peak, 243 total, 0 merged NFA Contexts: 2 peak, 41 total, 0 pruned NFA: 1 matched (len 40/40/40.0), 0 mismatched NFA: 0 absorbed, 39 skipped (len 1/1/1.0) @@ -3379,7 +3379,7 @@ WINDOW w AS ( Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) Pattern: (a | b){2,3} c Storage: Memory Maximum Storage: NkB - NFA States: 7 peak, 200 total, 0 merged + NFA States: 8 peak, 320 total, 0 merged NFA Contexts: 3 peak, 61 total, 2 pruned NFA: 19 matched (len 3/3/3.0), 1 mismatched (len 2/2/2.0) NFA: 0 absorbed, 38 skipped (len 1/2/1.5) diff --git a/src/test/regress/expected/rpr_nfa.out b/src/test/regress/expected/rpr_nfa.out index 2ace2df13ca..03ee174d359 100644 --- a/src/test/regress/expected/rpr_nfa.out +++ b/src/test/regress/expected/rpr_nfa.out @@ -1944,6 +1944,92 @@ WINDOW w AS ( 3 | {B,_} | | (3 rows) +-- Optional first branch in ALT with quantifier: (A? | B){1,2} +-- First branch A? exit path may loop back to ALT and trigger cycle +-- detection during DFS. All branches must receive correct counts. +WITH test_alt_opt_first AS ( + SELECT * FROM (VALUES + (1, ARRAY['B']), + (2, ARRAY['B']), + (3, ARRAY['B']) + ) AS t(id, flags) +) +SELECT id, flags, + first_value(id) OVER w AS match_start, + last_value(id) OVER w AS match_end +FROM test_alt_opt_first +WINDOW w AS ( + ORDER BY id + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + AFTER MATCH SKIP TO NEXT ROW + PATTERN (((A? | B){1,2})) + DEFINE + A AS 'A' = ANY(flags), + B AS 'B' = ANY(flags) +); + id | flags | match_start | match_end +----+-------+-------------+----------- + 1 | {B} | 1 | 2 + 2 | {B} | 2 | 3 + 3 | {B} | 3 | 3 +(3 rows) + +-- Mixed A/B rows across iterations of (A? | B){1,2} +WITH test_alt_opt_mixed AS ( + SELECT * FROM (VALUES + (1, ARRAY['A']), + (2, ARRAY['B']), + (3, ARRAY['A','B']) + ) AS t(id, flags) +) +SELECT id, flags, + first_value(id) OVER w AS match_start, + last_value(id) OVER w AS match_end +FROM test_alt_opt_mixed +WINDOW w AS ( + ORDER BY id + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + AFTER MATCH SKIP TO NEXT ROW + PATTERN (((A? | B){1,2})) + DEFINE + A AS 'A' = ANY(flags), + B AS 'B' = ANY(flags) +); + id | flags | match_start | match_end +----+-------+-------------+----------- + 1 | {A} | 1 | 2 + 2 | {B} | 2 | 3 + 3 | {A,B} | 3 | 3 +(3 rows) + +-- Reluctant variant: (A?? | B){1,2} +WITH test_alt_opt_reluctant AS ( + SELECT * FROM (VALUES + (1, ARRAY['B']), + (2, ARRAY['B']), + (3, ARRAY['B']) + ) AS t(id, flags) +) +SELECT id, flags, + first_value(id) OVER w AS match_start, + last_value(id) OVER w AS match_end +FROM test_alt_opt_reluctant +WINDOW w AS ( + ORDER BY id + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + AFTER MATCH SKIP TO NEXT ROW + PATTERN (((A?? | B){1,2})) + DEFINE + A AS 'A' = ANY(flags), + B AS 'B' = ANY(flags) +); + id | flags | match_start | match_end +----+-------+-------------+----------- + 1 | {B} | 1 | 2 + 2 | {B} | 2 | 3 + 3 | {B} | 3 | 3 +(3 rows) + -- ============================================================ -- Deep Nested Groups -- ============================================================ @@ -3381,7 +3467,7 @@ WINDOW w AS ( -- A never matches. Need 2 empty iterations to satisfy min=2. -- Per standard: STR06=(STRE STRE) is valid for min=2. -- Expected: empty match for every row --- BUG: visited bitmap blocks second empty iteration → match failure +-- XXX: visited bitmap blocks second empty iteration → match failure WITH test_728_min2 AS ( SELECT * FROM (VALUES (1, ARRAY['B']), @@ -3482,7 +3568,7 @@ WINDOW w AS ( (6 rows) -- (A? B?){2,3}: pure empty body (nothing matches) --- All NULL: same known bug as test_728_min2 (initialAdvance blocks FIN +-- XXX: All NULL: same issue as test_728_min2 (initialAdvance blocks FIN -- for zero-length matches at context start) WITH test_728_multi_empty AS ( SELECT * FROM (VALUES diff --git a/src/test/regress/sql/rpr_nfa.sql b/src/test/regress/sql/rpr_nfa.sql index 27426b1ac4f..a3f01b60bc4 100644 --- a/src/test/regress/sql/rpr_nfa.sql +++ b/src/test/regress/sql/rpr_nfa.sql @@ -1413,6 +1413,74 @@ WINDOW w AS ( B AS 'B' = ANY(flags) ); +-- Optional first branch in ALT with quantifier: (A? | B){1,2} +-- First branch A? exit path may loop back to ALT and trigger cycle +-- detection during DFS. All branches must receive correct counts. +WITH test_alt_opt_first AS ( + SELECT * FROM (VALUES + (1, ARRAY['B']), + (2, ARRAY['B']), + (3, ARRAY['B']) + ) AS t(id, flags) +) +SELECT id, flags, + first_value(id) OVER w AS match_start, + last_value(id) OVER w AS match_end +FROM test_alt_opt_first +WINDOW w AS ( + ORDER BY id + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + AFTER MATCH SKIP TO NEXT ROW + PATTERN (((A? | B){1,2})) + DEFINE + A AS 'A' = ANY(flags), + B AS 'B' = ANY(flags) +); + +-- Mixed A/B rows across iterations of (A? | B){1,2} +WITH test_alt_opt_mixed AS ( + SELECT * FROM (VALUES + (1, ARRAY['A']), + (2, ARRAY['B']), + (3, ARRAY['A','B']) + ) AS t(id, flags) +) +SELECT id, flags, + first_value(id) OVER w AS match_start, + last_value(id) OVER w AS match_end +FROM test_alt_opt_mixed +WINDOW w AS ( + ORDER BY id + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + AFTER MATCH SKIP TO NEXT ROW + PATTERN (((A? | B){1,2})) + DEFINE + A AS 'A' = ANY(flags), + B AS 'B' = ANY(flags) +); + +-- Reluctant variant: (A?? | B){1,2} +WITH test_alt_opt_reluctant AS ( + SELECT * FROM (VALUES + (1, ARRAY['B']), + (2, ARRAY['B']), + (3, ARRAY['B']) + ) AS t(id, flags) +) +SELECT id, flags, + first_value(id) OVER w AS match_start, + last_value(id) OVER w AS match_end +FROM test_alt_opt_reluctant +WINDOW w AS ( + ORDER BY id + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + AFTER MATCH SKIP TO NEXT ROW + PATTERN (((A?? | B){1,2})) + DEFINE + A AS 'A' = ANY(flags), + B AS 'B' = ANY(flags) +); + -- ============================================================ -- Deep Nested Groups -- ============================================================ @@ -2547,7 +2615,7 @@ WINDOW w AS ( -- A never matches. Need 2 empty iterations to satisfy min=2. -- Per standard: STR06=(STRE STRE) is valid for min=2. -- Expected: empty match for every row --- BUG: visited bitmap blocks second empty iteration → match failure +-- XXX: visited bitmap blocks second empty iteration → match failure WITH test_728_min2 AS ( SELECT * FROM (VALUES (1, ARRAY['B']), @@ -2626,7 +2694,7 @@ WINDOW w AS ( ); -- (A? B?){2,3}: pure empty body (nothing matches) --- All NULL: same known bug as test_728_min2 (initialAdvance blocks FIN +-- XXX: All NULL: same issue as test_728_min2 (initialAdvance blocks FIN -- for zero-length matches at context start) WITH test_728_multi_empty AS ( SELECT * FROM (VALUES @@ -2705,3 +2773,4 @@ WINDOW w AS ( A AS price > 100, B AS TRUE ); + -- 2.50.1 (Apple Git-155)