v49-0006-Row-pattern-recognition-patch-docs.patch

application/octet-stream

Filename: v49-0006-Row-pattern-recognition-patch-docs.patch
Type: application/octet-stream
Part: 5
Message: Re: Row pattern recognition

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v49-0006
Subject: Row pattern recognition patch (docs).
File+
doc/src/sgml/advanced.sgml 128 4
doc/src/sgml/func/func-window.sgml 121 0
doc/src/sgml/perform.sgml 44 0
doc/src/sgml/ref/select.sgml 88 3
src/backend/executor/README.rpr 1672 0
From 20f87ea59d4a3bd7a87697f98f34cf816859595a Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Fri, 19 Jun 2026 20:18:45 +0900
Subject: [PATCH v49 6/9] Row pattern recognition patch (docs).

---
 doc/src/sgml/advanced.sgml         |  132 ++-
 doc/src/sgml/func/func-window.sgml |  121 ++
 doc/src/sgml/perform.sgml          |   44 +
 doc/src/sgml/ref/select.sgml       |   91 +-
 src/backend/executor/README.rpr    | 1672 ++++++++++++++++++++++++++++
 5 files changed, 2053 insertions(+), 7 deletions(-)
 create mode 100644 src/backend/executor/README.rpr

diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 3286c2cf0b2..bdc552ad75f 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -552,13 +552,137 @@ WHERE pos &lt; 3;
     two rows for each department).
    </para>
 
+   <para>
+    Row Pattern Common Syntax can be used to perform Row Pattern Recognition
+    in a query. The Row Pattern Common Syntax includes two sub
+    clauses: <literal>DEFINE</literal>
+    and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+    row pattern variables along with an expression. The expression must be a
+    logical expression, which means it must
+    return <literal>TRUE</literal>, <literal>FALSE</literal>
+    or <literal>NULL</literal>. The expression may comprise column references
+    and functions. Window functions, aggregate functions and subqueries are
+    not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price &lt;= 100,
+ UP AS price &gt; PREV(price),
+ DOWN AS price &lt; PREV(price)
+</programlisting>
+
+    Note that <function>PREV</function> returns the <literal>price</literal>
+    column in the previous row if it's called in a context of row pattern
+    recognition. Thus in the second line the row pattern variable "UP"
+    is <literal>TRUE</literal> when the price column in the current row is
+    greater than the price column in the previous row. Likewise, "DOWN"
+    is <literal>TRUE</literal> when the
+    <literal>price</literal> column in the current row is lower than
+    the <literal>price</literal> column in the previous row.
+   </para>
+   <para>
+    Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+    used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+    conditions defined in the <literal>DEFINE</literal> clause.  For example
+    the following <literal>PATTERN</literal> defines a sequence of rows starting
+    with a row satisfying "LOWPRICE", then one or more rows satisfying
+    "UP" and finally one or more rows satisfying "DOWN". Pattern variables can
+    be followed by quantifiers: "+" means one or more matches, "*" means zero
+    or more matches, "?" means zero or one match, "{n}" (n &gt; 0) means exactly
+    n matches, "{n,}" (n &gt;= 0) means at least n matches, "{,m}" (m &gt; 0) means
+    at most m matches, and "{n,m}" (0 &lt;= n &lt;= m, 0 &lt; m) means between n and m
+    matches.  Patterns can be grouped using parentheses and combined using
+    alternation (the vertical bar "|" for OR). For example, "(UP DOWN)+"
+    matches one or more repetitions of UP followed by DOWN.  If a sequence of
+    rows which satisfies the PATTERN is found, in the starting row all columns
+    or functions are shown in the target list. Note that aggregations only
+    look into the matched rows, rather than the whole frame. On the second or
+    subsequent rows all window functions are shown as NULL. Aggregates on
+    non-starting rows return their initial value: for example,
+    <function>count()</function> returns 0 and <function>sum()</function>
+    returns NULL. For rows that do not match the PATTERN, window functions
+    are likewise shown as NULL and aggregates return their initial value.
+    Example of a <literal>SELECT</literal> using
+    the <literal>DEFINE</literal> and <literal>PATTERN</literal> clause is as
+    follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+  LOWPRICE AS price &lt;= 100,
+  UP AS price &gt; PREV(price),
+  DOWN AS price &lt; PREV(price)
+);
+</programlisting>
+<screen>
+ company  |   tdate    | price | first_value | max | count 
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 |   100 |         100 | 200 |     4
+ company1 | 2023-07-02 |   200 |             |     |     0
+ company1 | 2023-07-03 |   150 |             |     |     0
+ company1 | 2023-07-04 |   140 |             |     |     0
+ company1 | 2023-07-05 |   150 |             |     |     0
+ company1 | 2023-07-06 |    90 |          90 | 130 |     4
+ company1 | 2023-07-07 |   110 |             |     |     0
+ company1 | 2023-07-08 |   130 |             |     |     0
+ company1 | 2023-07-09 |   120 |             |     |     0
+ company1 | 2023-07-10 |   130 |             |     |     0
+(10 rows)
+</screen>
+   </para>
+
+   <para>
+    Row Pattern Recognition internally uses a nondeterministic finite
+    automaton (NFA) to match patterns. For patterns with unbounded
+    quantifiers (e.g., <literal>A+</literal> or <literal>(A B)+</literal>),
+    the NFA may need to track many active matching contexts simultaneously,
+    which could potentially lead to O(n<superscript>2</superscript>)
+    complexity as the number of rows increases.
+   </para>
+
+   <para>
+    Before execution, <productname>PostgreSQL</productname> automatically
+    optimizes patterns to simplify their structure. This includes flattening
+    nested sequences and alternations, merging consecutive identical variables
+    (e.g., <literal>A{2,3} A{1,2}</literal> becomes <literal>A{3,5}</literal>),
+    removing duplicate alternatives
+    (e.g., <literal>(A | B | A)</literal> becomes <literal>(A | B)</literal>),
+    and simplifying nested quantifiers
+    (e.g., <literal>(A*)*</literal> becomes <literal>A*</literal>).
+    These optimizations reduce pattern complexity and also decrease
+    nesting depth, making the 253-level depth limit rarely encountered.
+    They are applied transparently and can be observed
+    in <command>EXPLAIN</command> output.
+   </para>
+
+   <para>
+    To mitigate the O(n<superscript>2</superscript>) complexity described
+    above, <productname>PostgreSQL</productname> also employs
+    a context absorption optimization. When a pattern starts with a greedy
+    unbounded element, newer matching contexts cannot produce longer matches
+    than older contexts. By detecting and eliminating these redundant
+    contexts, the matching complexity is reduced from
+    O(n<superscript>2</superscript>) to O(n) for many common patterns.
+   </para>
+
    <para>
     When a query involves multiple window functions, it is possible to write
     out each one with a separate <literal>OVER</literal> clause, but this is
-    duplicative and error-prone if the same windowing behavior is wanted
-    for several functions.  Instead, each windowing behavior can be named
-    in a <literal>WINDOW</literal> clause and then referenced in <literal>OVER</literal>.
-    For example:
+    duplicative and error-prone if the same windowing behavior is wanted for
+    several functions.  Instead, each windowing behavior can be named in
+    a <literal>WINDOW</literal> clause and then referenced
+    in <literal>OVER</literal>.  For example:
 
 <programlisting>
 SELECT sum(salary) OVER w, avg(salary) OVER w
diff --git a/doc/src/sgml/func/func-window.sgml b/doc/src/sgml/func/func-window.sgml
index bb41387f873..ab469b56fd7 100644
--- a/doc/src/sgml/func/func-window.sgml
+++ b/doc/src/sgml/func/func-window.sgml
@@ -278,6 +278,127 @@ IGNORE NULLS
    <function>nth_value</function>.
   </para>
 
+  <para>
+   Row Pattern Recognition navigation functions are listed in
+   <xref linkend="functions-rpr-navigation-table"/>.  These functions
+   can be used to describe the DEFINE clause of Row Pattern Recognition.
+  </para>
+
+   <table id="functions-rpr-navigation-table">
+    <title>Row Pattern Navigation Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>prev</primary>
+        </indexterm>
+        <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> [, <parameter>offset</parameter> <type>bigint</type> ] )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Returns <parameter>value</parameter> evaluated at the row that is
+        <parameter>offset</parameter> rows before the current row within
+        the partition;
+        returns NULL if the target row is outside the partition.
+        <parameter>offset</parameter> defaults to 1 if omitted.
+        <parameter>offset</parameter> must be a non-negative integer;
+        an offset of 0 refers to the current row itself.
+        <parameter>offset</parameter> must not be NULL.
+        Can only be used in a <literal>DEFINE</literal> clause.
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>next</primary>
+        </indexterm>
+        <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> [, <parameter>offset</parameter> <type>bigint</type> ] )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Returns <parameter>value</parameter> evaluated at the row that is
+        <parameter>offset</parameter> rows after the current row within
+        the partition;
+        returns NULL if the target row is outside the partition.
+        <parameter>offset</parameter> defaults to 1 if omitted.
+        <parameter>offset</parameter> must be a non-negative integer;
+        an offset of 0 refers to the current row itself.
+        <parameter>offset</parameter> must not be NULL.
+        Can only be used in a <literal>DEFINE</literal> clause.
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>first</primary>
+        </indexterm>
+        <function>first</function> ( <parameter>value</parameter> <type>anyelement</type> [, <parameter>offset</parameter> <type>bigint</type> ] )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Returns <parameter>value</parameter> evaluated at the row that is
+        <parameter>offset</parameter> rows after the match start row;
+        returns NULL if the target row is beyond the current row.
+        <parameter>offset</parameter> defaults to 0 if omitted, referring to the
+        match start row itself.
+        <parameter>offset</parameter> must be a non-negative integer.
+        <parameter>offset</parameter> must not be NULL.
+        Can only be used in a <literal>DEFINE</literal> clause.
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>last</primary>
+        </indexterm>
+        <function>last</function> ( <parameter>value</parameter> <type>anyelement</type> [, <parameter>offset</parameter> <type>bigint</type> ] )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Returns <parameter>value</parameter> evaluated at the row that is
+        <parameter>offset</parameter> rows before the current row within
+        the match;
+        returns NULL if the target row is before the match start row.
+        <parameter>offset</parameter> defaults to 0 if omitted, referring to the
+        current row itself.
+        <parameter>offset</parameter> must be a non-negative integer.
+        <parameter>offset</parameter> must not be NULL.
+        Can only be used in a <literal>DEFINE</literal> clause.
+       </para></entry>
+      </row>
+
+     </tbody>
+    </tgroup>
+   </table>
+
+   <para>
+    <function>PREV</function> and <function>NEXT</function> may wrap
+    <function>FIRST</function> or <function>LAST</function> for compound
+    navigation. For example,
+    <literal>PREV(FIRST(val, 2), 3)</literal> fetches the value at
+    3 rows before the row that is 2 rows after the match start.
+    The reverse nesting (<function>FIRST</function>/<function>LAST</function>
+    wrapping <function>PREV</function>/<function>NEXT</function>) is not
+    permitted. Same-category nesting (e.g.,
+    <function>PREV</function> inside <function>PREV</function>) is also
+    prohibited.
+   </para>
+
   <note>
    <para>
     The SQL standard defines a <literal>FROM FIRST</literal> or <literal>FROM LAST</literal>
diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml
index 604e8578a8d..01a83ab105d 100644
--- a/doc/src/sgml/perform.sgml
+++ b/doc/src/sgml/perform.sgml
@@ -701,6 +701,50 @@ FROM tenk1 t1 WHERE t1.ten = (SELECT (random() * 10)::integer);
     happen without the sub-<literal>SELECT</literal> construct.
    </para>
 
+   <para>
+    When examining query plans for Row Pattern Recognition with
+    <command>EXPLAIN</command>, the pattern output may include special
+    markers that indicate optimization opportunities. A double quote
+    <literal>"</literal> marks where pattern absorption can occur,
+    and a single quote <literal>'</literal> marks absorbable elements
+    within a branch. For example, using the <structname>stock</structname>
+    table from <xref linkend="tutorial-window"/>, a query that looks for
+    repeated up-then-down movements reports its pattern as
+    <literal>(up' down')+"</literal>:
+
+<screen>
+EXPLAIN (COSTS OFF)
+SELECT company, tdate, price, count(price) OVER w
+FROM stock
+WINDOW w AS (
+    PARTITION BY company
+    ORDER BY tdate
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    INITIAL
+    PATTERN ((UP DOWN)+)
+    DEFINE UP AS price &gt; PREV(price),
+           DOWN AS price &lt; PREV(price)
+);
+
+                                              QUERY PLAN
+-------------------------------------------------------------------&zwsp;------------------------------------
+ WindowAgg
+   Window: w AS (PARTITION BY company ORDER BY tdate ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   Pattern: (up' down')+"
+   Nav Mark Lookback: 1
+   -&gt;  Sort
+         Sort Key: company, tdate
+         -&gt;  Seq Scan on stock
+</screen>
+
+    Here the single quotes mark <literal>up</literal> and
+    <literal>down</literal> as absorbable within the group, while the
+    trailing double quote marks the repeated group itself as absorbable.
+    These markers are primarily useful for understanding internal
+    optimization behavior.
+   </para>
+
   </sect2>
 
   <sect2 id="using-explain-analyze">
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 09b6ce809bb..be5ed814853 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -1022,8 +1022,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
     The <replaceable class="parameter">frame_clause</replaceable> can be one of
 
 <synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [ <replaceable>row_pattern_common_syntax</replaceable> ]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [ <replaceable>row_pattern_common_syntax</replaceable> ]
 </synopsis>
 
     where <replaceable>frame_start</replaceable>
@@ -1130,9 +1130,94 @@ EXCLUDE NO OTHERS
     a given peer group will be in the frame or excluded from it.
    </para>
 
+   <para>
+    The
+    optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+    defines the <firstterm>Row Pattern Recognition condition</firstterm> for
+    this
+    window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+    includes the following subclauses.
+
+<synopsis>
+[ { AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW } ]
+[ INITIAL | SEEK ]
+PATTERN ( <replaceable class="parameter">pattern_variable_name</replaceable> [ <replaceable>quantifier</replaceable> ] [ ... ] )
+DEFINE <replaceable class="parameter">definition_variable_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+    <literal>AFTER MATCH SKIP PAST LAST ROW</literal> or <literal>AFTER MATCH
+    SKIP TO NEXT ROW</literal> controls how to proceed to the next row position
+    after a match is found. With <literal>AFTER MATCH SKIP PAST LAST
+    ROW</literal> (the default) the next row position is next to the last row of
+    the previous match. On the other hand, with <literal>AFTER MATCH SKIP TO NEXT
+    ROW</literal> the next row position is next to the first row of the previous
+    match. <literal>INITIAL</literal> or <literal>SEEK</literal> specifies from
+    which row in the frame pattern matching begins.
+    If <literal>INITIAL</literal> is specified, the match must start
+    from the first row in the frame. If <literal>SEEK</literal> is specified,
+    the set of matching rows does not necessarily start from the first row. The
+    default is <literal>INITIAL</literal>. Currently
+    only <literal>INITIAL</literal> is supported. <literal>DEFINE</literal>
+    defines definition variables along with a boolean
+    expression. <literal>PATTERN</literal> defines a sequence of rows that
+    satisfies certain conditions using variables defined
+    in the <literal>DEFINE</literal> clause (an empty <literal>PATTERN()</literal>
+    is not supported). Each pattern variable can be followed by a quantifier
+    to specify how many times it should match:
+    <literal>*</literal> (zero or more),
+    <literal>+</literal> (one or more),
+    <literal>?</literal> (zero or one),
+    <literal>{</literal><replaceable>n</replaceable><literal>}</literal> (exactly <replaceable>n</replaceable> times, n &gt; 0),
+    <literal>{</literal><replaceable>n</replaceable><literal>,}</literal> (at least <replaceable>n</replaceable> times, n &gt;= 0),
+    <literal>{,</literal><replaceable>m</replaceable><literal>}</literal> (at most <replaceable>m</replaceable> times, m &gt; 0), or
+    <literal>{</literal><replaceable>n</replaceable><literal>,</literal><replaceable>m</replaceable><literal>}</literal>
+    (between <replaceable>n</replaceable> and <replaceable>m</replaceable> times, 0 &lt;= n &lt;= m, 0 &lt; m).
+    Reluctant quantifiers (e.g., <literal>*?</literal>, <literal>+?</literal>,
+    <literal>??</literal>, <literal>{</literal><replaceable>n</replaceable><literal>,</literal><replaceable>m</replaceable><literal>}?</literal>)
+    are supported.
+    The exclusion (<literal>{-</literal> and <literal>-}</literal>)
+    is not supported.
+    Patterns can be grouped using parentheses, and alternation (OR) can be
+    expressed using the vertical bar <literal>|</literal>.
+    For example, <literal>(A B)+</literal> matches one or more repetitions
+    of the sequence A followed by B, and <literal>A | B</literal> matches
+    either A or B.
+    If a pattern variable is not defined in
+    the <literal>DEFINE</literal> clause, it is not automatically added
+    to the <literal>DEFINE</literal> clause. Instead, the executor evaluates
+    the variable as <literal>TRUE</literal> at execution time, behaving as if
+    the following definition existed.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+    Conversely, variables defined in the <literal>DEFINE</literal> clause
+    but not used in the <literal>PATTERN</literal> clause are rejected
+    with an error.
+   </para>
+
+   <para>
+    Note that the maximum number of unique pattern variables
+    used in the <literal>PATTERN</literal> clause is 240.
+    If this limit is exceeded, an error will be raised.
+    Additionally, the maximum nesting depth of pattern groups
+    (parentheses) is 253 levels.
+    However, pattern optimizations such as flattening nested sequences
+    and simplifying nested quantifiers may reduce the effective depth,
+    so this limit is rarely reached in practice.
+   </para>
+
+   <para>
+    The SQL standard defines more subclauses: <literal>MEASURES</literal>
+    and <literal>SUBSET</literal>. They are not currently supported
+    in <productname>PostgreSQL</productname>. Also in the standard there are
+    more variations in <literal>AFTER MATCH</literal> clause.
+   </para>
+
    <para>
     The purpose of a <literal>WINDOW</literal> clause is to specify the
-    behavior of <firstterm>window functions</firstterm> appearing in the query's
+    behavior of <firstterm>window functions</firstterm> appearing in the
+    query's
     <link linkend="sql-select-list"><command>SELECT</command> list</link> or
     <link linkend="sql-orderby"><literal>ORDER BY</literal></link> clause.
     These functions
diff --git a/src/backend/executor/README.rpr b/src/backend/executor/README.rpr
new file mode 100644
index 00000000000..00af86681b8
--- /dev/null
+++ b/src/backend/executor/README.rpr
@@ -0,0 +1,1672 @@
+============================================================================
+  PostgreSQL Row Pattern Recognition: Flat-Array Stream NFA Guide
+============================================================================
+
+  This README's target audience is developers with a basic
+  understanding of the PostgreSQL executor and planner architecture.
+  Also it would be better for them to understand the specification of
+  the row pattern recognition in the SQL standard [1][2]. If you do
+  not have access to the SQL standard, Oracle's manual or Trino's
+  manual can be alternatives for them.
+
+  This README's scope is the entire process from PATTERN/DEFINE clause
+  parsing to NFA runtime execution.
+
+  Related code:
+    - src/backend/parser/parse_rpr.c          (parser phase)
+    - src/backend/optimizer/plan/rpr.c        (optimizer phase)
+    - src/backend/executor/nodeWindowAgg.c    (executor phase, window agg)
+    - src/backend/executor/execRPR.c          (executor phase, NFA engine)
+    - src/include/executor/execRPR.h          (NFA public API)
+    - src/include/nodes/plannodes.h           (plan node definitions)
+    - src/include/nodes/execnodes.h           (execution state definitions)
+    - src/include/optimizer/rpr.h             (types and constants)
+    - src/backend/optimizer/plan/createplan.c (nav offset computation)
+
+============================================================================
+
+What is a Flat-Array Stream NFA?
+
+  The NFA (Nondeterministic Finite Automaton) in this implementation
+  is not a traditional state-transition graph but a flat array of
+  fixed-size 16-byte elements. At runtime, it processes the row stream
+  in a forward-only manner, expanding epsilon transitions eagerly
+  without backtracking.
+
+  - Flat-Array: Pattern compiled into a flat array,
+                not a graph (Chapter IV)
+  - Stream:     Rows consumed sequentially in one direction,
+                never revisited (Chapter XII)
+  - NFA:        Nondeterministic execution where multiple states
+                coexist within a single context (Chapter VI)
+
+Chapter I  Row Pattern Recognition Overview
+============================================================================
+
+Normative reference: ISO/IEC 19075-5 (SQL Technical Report, Part 5: Row
+pattern recognition in SQL).  Subclause numbers cited throughout this code
+base refer to that document.  Where Chapters 4 (FROM clause) and 6 (WINDOW
+clause) describe parallel material, this implementation cites the Chapter 6
+subclause first because it targets Feature R020.
+
+Row Pattern Recognition (hereafter RPR) is a feature introduced in SQL:2016
+that matches regex-based patterns against ordered row sets.
+
+The SQL standard defines two forms:
+
+  Feature R010: MATCH_RECOGNIZE (FROM clause)
+    - Dedicated table operator
+    - Provides dedicated functions such as MATCH_NUMBER(), CLASSIFIER()
+    - Supports ONE ROW PER MATCH / ALL ROWS PER MATCH
+
+  Feature R020: RPR in a window (WINDOW clause)
+    - Integrated into the existing window function framework
+    - Supports ALL ROWS PER MATCH only
+    - No MATCH_NUMBER()
+
+This implementation targets Feature R020.
+
+The basic syntax is as follows:
+
+  SELECT ...
+  OVER (
+    PARTITION BY ...
+    ORDER BY ...
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    [INITIAL | SEEK]   -- SEEK is defined in the standard but not implemented
+    AFTER MATCH SKIP TO NEXT ROW | SKIP PAST LAST ROW
+    PATTERN ( <regex> )
+    DEFINE <variable> AS <condition>, ...
+  )
+
+The PATTERN clause is a regular expression over row pattern variables.
+The DEFINE clause specifies boolean conditions that determine whether each
+variable evaluates to true for the current row.
+
+Example:
+
+  PATTERN (A+ B)
+  DEFINE A AS price > PREV(price),
+         B AS price < PREV(price)
+
+This pattern matches "a span where prices rise consecutively then drop."
+
+Chapter II  Overall Processing Pipeline
+============================================================================
+
+RPR processing is divided into three phases:
+
+  +------------------------------------------------------------+
+  |  1. Parsing (Parser)                                       |
+  |     SQL text -> PATTERN AST + DEFINE expression tree       |
+  |                                                            |
+  |  2. Compilation (Optimizer/Planner)                        |
+  |     PATTERN AST -> optimization -> flat NFA element array  |
+  |                                                            |
+  |  3. Execution (Executor)                                   |
+  |     Row-by-row matching via NFA simulation                 |
+  +------------------------------------------------------------+
+
+Each phase uses independent data structures, and the interfaces between
+phases are well-defined:
+
+  Parser -> Planner:    WindowClause.rpPattern (RPRPatternNode tree)
+                        WindowClause.defineClause (TargetEntry list)
+
+  Planner -> Executor:  WindowAgg.rpPattern (RPRPattern struct)
+                        WindowAgg.defineClause (TargetEntry list)
+
+Chapter III  Parsing Phase
+============================================================================
+
+III-1. Entry Point
+
+  transformWindowDefinitions() (parse_clause.c)
+    +-- transformRPR() (parse_rpr.c)
+
+transformRPR() is invoked when RPCommonSyntax is present and performs the
+following:
+
+  (1) Frame option validation
+      - Only ROWS is allowed (RANGE, GROUPS are not)
+      - The start boundary must be CURRENT ROW
+      - EXCLUDE option is not allowed
+
+  (2) Transcription to WindowClause
+      - Copies rpPattern, rpSkipTo, initial fields
+
+  (3) DEFINE clause transformation (transformDefineClause)
+
+III-2. PATTERN AST (Abstract Syntax Tree)
+
+The parser transforms the PATTERN clause into an RPRPatternNode tree.
+Each node has one of the following four types:
+
+  RPR_PATTERN_VAR    Variable reference. Name stored in varName field.
+  RPR_PATTERN_SEQ    Concatenation. Children node list in children.
+  RPR_PATTERN_ALT    Alternation (or). Branch node list in children.
+  RPR_PATTERN_GROUP  Group (parentheses). Body node list in children.
+
+All nodes have min/max fields to express quantifiers:
+
+  A       -> VAR(A, min=1, max=1)
+  A+      -> VAR(A, min=1, max=INF)
+  A*      -> VAR(A, min=0, max=INF)
+  A?      -> VAR(A, min=0, max=1)
+  A{3,5}  -> VAR(A, min=3, max=5)
+
+If the reluctant field is true, the quantifier is reluctant (non-greedy).
+
+Example: PATTERN ((A+ B) | C*)
+
+  ALT
+  +-- SEQ
+  |   +-- VAR(A, 1, INF)
+  |   +-- VAR(B, 1, 1)
+  +-- VAR(C, 0, INF)
+
+III-3. DEFINE Clause Transformation
+
+transformDefineClause() processes each DEFINE variable as follows:
+
+  (1) Checks for duplicate variable names
+  (2) Transforms the expression via transformExpr()
+  (3) Extracts Var nodes via pull_var_clause() and ensures each is
+      present in the query targetlist, so the planner propagates the
+      referenced columns through the plan tree
+  (4) Wraps in a TargetEntry with the variable name set in resname
+
+After all variables are processed:
+  (5) Coerces each expression to Boolean type (coerce_to_boolean)
+
+Variables that are used in PATTERN but not defined in DEFINE are implicitly
+evaluated as TRUE (matching all rows).
+
+Chapter IV  Compilation Phase
+============================================================================
+
+IV-1. Entry Point
+
+  create_windowagg_plan() (createplan.c)
+    +-- buildDefineVariableList()    Build variable name list from DEFINE
+    +-- buildRPRPattern()           NFA compilation (6 phases)
+
+IV-2. The 6 Phases of buildRPRPattern()
+
+  Phase 1: AST optimization (optimizeRPRPattern)
+  Phase 2: Statistics collection (scanRPRPattern)
+  Phase 3: Memory allocation (makeRPRPattern)
+  Phase 4: NFA element fill (fillRPRPattern)
+  Phase 5: Finalization (finalizeRPRPattern)
+  Phase 6: Absorbability analysis (computeAbsorbability)
+
+IV-3. Phase 1: AST Optimization
+
+After copying the parser-generated AST, the following optimizations are
+applied:
+
+  (a) SEQ flattening: Unwrap nested SEQ nodes
+      SEQ(A, SEQ(B, C)) -> SEQ(A, B, C)
+
+  (b) Consecutive variable merging: Merge consecutive occurrences of the
+      same variable into a single quantifier
+      A A -> A{2}
+      A{2,3} A{1,2} -> A{3,5}
+
+  (c) Consecutive group merging: Merge repeated identical groups
+      (A B)+ (A B)+ -> (A B){2,INF}
+
+  (d) Consecutive ALT merging: Merge repeated identical ALT nodes
+      (A | B) (A | B) (A | B) -> (A | B){3}
+
+  (e) Prefix/suffix merging: Merge identical sequences before/after
+      a group
+      A B (A B)+ -> (A B){2,INF}
+
+  (f) ALT flattening and deduplication
+      (A | (B | C)) -> (A | B | C)
+      (A | B | A) -> (A | B)
+
+  (g) Quantifier multiplication: Collapse nested quantifiers when safe
+      (A+)+ -> A+
+      (A{2,3}){5} -> A{10,15}
+      (A{2,})* stays as-is (count 1 unreachable; A* would be wrong)
+
+  (h) Single-child unwrap
+      SEQ(A) -> A,  (A){1,1} -> A
+
+IV-4. Phase 4: NFA Element Array Generation
+
+Transforms the optimized AST into a flat array of RPRPatternElement.
+This is the core data structure used for NFA simulation at runtime.
+
+RPRPatternElement struct (16 bytes):
+
+  Field      Size     Description
+  ---------------------------------------------------------
+  varId      1B      Variable ID (0-0xEF) or control code (0xFC-0xFF)
+  depth      1B      Group nesting depth
+  flags      1B      Bit flags (see below)
+  reserved   1B      Padding
+  min        4B      Quantifier lower bound
+  max        4B      Quantifier upper bound
+  next       2B      Next element index (sequential flow)
+  jump       2B      Branch target index (for ALT/GROUP)
+
+Pattern variables occupy varId 0 to RPR_VARID_MAX (0xEF) inclusive,
+giving 240 distinct variables.  Any varId with the high nibble set
+(0xF0-0xFF) is reserved for control elements; 0xF0-0xFB are currently
+spare.
+
+Control codes:
+
+  RPR_VARID_BEGIN (0xFC)  Group start marker
+  RPR_VARID_END   (0xFD)  Group end marker
+  RPR_VARID_ALT   (0xFE)  Alternation start marker
+  RPR_VARID_FIN   (0xFF)  Pattern completion marker
+
+Element flags (1 byte, bitmask):
+
+  0x01  RPR_ELEM_RELUCTANT          (VAR, BEGIN, END)
+        Non-greedy quantifier.  Prefers shorter match: try exit-loop
+        first, then repeat.  Set on VAR for simple (A+?),
+        on BEGIN+END for group ((...)+?).
+
+  0x02  RPR_ELEM_EMPTY_LOOP         (END)
+        Group body can produce empty match (all children nullable).
+        Creates a fast-forward exit clone alongside the normal
+        loop-back so cycle detection doesn't kill legitimate
+        matches. (IV-4b)
+
+  0x04  RPR_ELEM_ABSORBABLE_BRANCH  (VAR, BEGIN, END, ALT)
+        Element lies within an absorbable region.  Used at runtime to
+        track whether the current NFA state is in an absorbable
+        context. See "IV-5. Absorbability Analysis" and
+        "VIII-2. Solution: Context Absorption" for more details about
+        absorption.
+
+  0x08  RPR_ELEM_ABSORBABLE         (VAR, END)
+        Absorption judgment point.  Where to compare consecutive
+        iterations for absorption.
+          - Simple unbounded VAR (A+): set on the VAR itself
+          - Unbounded GROUP ((A B)+): set on the END element only
+
+  Accessor macros:
+    RPRElemIsReluctant(e)        (e)->flags & 0x01
+    RPRElemCanEmptyLoop(e)       (e)->flags & 0x02
+    RPRElemIsAbsorbableBranch(e) (e)->flags & 0x04
+    RPRElemIsAbsorbable(e)       (e)->flags & 0x08
+
+Example: PATTERN (A+ B | C)
+
+  AST: ALT(SEQ(VAR(A,1,INF), VAR(B,1,1)), VAR(C,1,1))
+
+  Compilation result:
+
+  idx  varId  depth  min  max  next  jump  Description
+  ------------------------------------------------------------
+   0   ALT    0      1    1    1     -1    Alternation start
+   1   A(0)   1      1    INF  2     3     Branch 1: A+
+   2   B(1)   1      1    1    4     -1    Branch 1: B -> FIN
+   3   C(2)   1      1    1    4     -1    Branch 2: C -> FIN
+   4   FIN    0      1    1    -1    -1    Pattern completion
+
+  - idx 0: ALT marker. next(=1) is the start of the first branch
+  - idx 1: Variable A. next(=2) is B, jump(=3) is the start of the second
+           branch
+  - idx 2: Variable B. next(=4) is FIN
+  - idx 3: Variable C. next(=4) is FIN
+  - idx 4: FIN marker. Match completion signal
+
+Roles of next and jump:
+
+  - next: The next element to move to "after consuming" the current element.
+          For VAR, the next position after a successful match.
+          For BEGIN/END, the next position inside/outside the group.
+
+  - jump: The element to "skip to."
+          In ALT, a jump from one branch to the next branch.
+          In BEGIN, a skip path to END+1 (for groups with min=0).
+          In END, a loop-back to the start of the group body.
+
+Example: PATTERN ((A B)+)
+
+  idx  varId    depth  min  max  next  jump  Description
+  --------------------------------------------------------------
+   0   BEGIN    0      1    INF  1     4     Group start
+   1   A(0)     1      1    1    2     -1    A
+   2   B(1)     1      1    1    3     -1    B
+   3   END      0      1    INF  4     1     Group end
+   4   FIN      0      1    1    -1    -1    Pattern completion
+
+  - idx 0: BEGIN. next(=1) enters the group body.
+           jump(=4) skips to after END = FIN (used when min=0).
+  - idx 3: END. next(=4) exits the group.
+           jump(=1) loops back to the start of the group body.
+
+IV-4a. Reluctant Flag (RPR_ELEM_RELUCTANT)
+
+The reluctant flag is set during Phase 4 (fillRPRPattern) when the AST node
+has reluctant == true. It reverses the priority of quantifier expansion at
+runtime:
+
+  Greedy (default):  try loop-back first, then exit  (prefer longer match)
+  Reluctant:         try exit first, then loop-back   (prefer shorter match)
+
+The flag is set on all elements that carry the quantifier:
+
+  Simple VAR (A+?):     RPR_ELEM_RELUCTANT on the VAR element
+  Group ((...)+?):      RPR_ELEM_RELUCTANT on BEGIN and END elements
+
+At runtime (nfa_advance), the flag controls Depth-First Search
+(DFS) exploration order:
+
+  VAR with quantifier:
+    Greedy:    primary path = next (continue), clone = jump (skip)
+    Reluctant: primary path = jump (skip), clone = next (continue)
+
+  END element:
+    Greedy:    primary path = jump (loop-back), clone = next (exit)
+    Reluctant: primary path = next (exit), clone = jump (loop-back)
+
+  BEGIN with min=0:
+    Greedy:    primary path = next (enter group), clone = jump (skip)
+    Reluctant: primary path = jump (skip), clone = next (enter group)
+
+The absorption optimization requires greedy quantifiers. Reluctant
+quantifiers are excluded from absorbability analysis (see IV-5).
+
+IV-4b. Empty Loop Flag (RPR_ELEM_EMPTY_LOOP)
+
+The empty-loop flag is set during Phase 4 (fillRPRPatternGroup) on the END
+element when the group body is nullable -- i.e., every path through the
+body can match zero rows (all children are nullable).
+
+Example patterns that trigger this flag:
+
+  (A?)*    A is nullable (min=0), so group body is nullable -> END gets flag
+  (A? B?)+ Both children nullable -> body nullable -> END gets flag
+  (A | B*) B* is nullable, making the ALT nullable -> END gets flag
+
+The flag works in conjunction with the empty match cycle detection
+(elemIdx visited bitmap). Without this flag, cycle detection alone would
+cause legitimate matches to fail.
+
+Problem example: (A*){2,3}
+  - Iteration 1: A* consumes all available rows -> count=1, END reached
+  - Loop-back for iteration 2: A* matches zero rows -> END reached again
+  - Cycle detection sees the same elemIdx on the same row -> state killed
+  - count never reaches min(2) -> match fails (incorrect)
+
+With the RPR_ELEM_EMPTY_LOOP flag, nfa_advance_end creates two paths:
+the normal loop-back (which cycle detection will eventually kill) and
+a fast-forward exit clone that bypasses the loop entirely.
+(See IX-4(c) for detailed runtime behavior.)
+
+IV-5. Absorbability Analysis (RPR_ELEM_ABSORBABLE)
+
+Context absorption is an optimization technique that reduces O(n^2) to O(n).
+(Runtime behavior is described in Chapter VIII.)
+
+This phase determines whether the pattern has a structure suitable for the
+absorption optimization and sets flags on the relevant elements:
+
+  RPR_ELEM_ABSORBABLE         Absorption comparison point
+  RPR_ELEM_ABSORBABLE_BRANCH  Element within an absorbable region
+
+Eligibility conditions:
+
+  (1) SKIP PAST LAST ROW (not NEXT ROW)
+  (2) Frame end is UNBOUNDED FOLLOWING
+
+Structural conditions (isUnboundedStart + computeAbsorbabilityRecursive):
+
+  Case 1: Simple VAR+ (e.g., A+)
+          -> ABSORBABLE | ABSORBABLE_BRANCH set on the VAR
+  Case 2: GROUP+ with fixed-length children (min == max, recursively)
+          e.g., (A B)+, (A B{2})+, ((A (B C){2}){2})+
+          -> ABSORBABLE_BRANCH on all elements within the group,
+             ABSORBABLE | ABSORBABLE_BRANCH on END
+
+          Why this is safe: when every child has min == max, the group
+          is semantically equivalent to unrolling its body into {1,1}
+          elements.  E.g., (A B{2})+ behaves like (A B B)+.  Each
+          iteration consumes a fixed number of rows, so an earlier
+          context's count always dominates a later one's (monotonicity).
+
+  Case 3: GROUP+ whose body starts with VAR+ (e.g., (A+ B)+)
+          -> Recurses from BEGIN into the body, applying Case 1.
+            ABSORBABLE | ABSORBABLE_BRANCH set on A.
+            B and END get no flags -> absorption stops once past A.
+
+Absorbability is determined per-element, not per-pattern.
+Absorption comparison is performed only when a state resides at an
+element with the RPR_ELEM_ABSORBABLE flag. Once a state leaves the
+flagged region, absorption is permanently disabled for that state.
+
+Through this mechanism, the runtime guarantees monotonicity:
+"a context that started earlier always subsumes a context that
+started later."
+
+Chapter V  NFA Runtime Data Structures
+============================================================================
+
+V-1. RPRNFAState -- NFA State
+
+A single NFA state represents "how far the pattern has progressed."
+
+  Field         Description
+  -----------------------------------------------------------
+  elemIdx       Index of the current pattern element
+  counts[]      Repetition count per group depth
+  isAbsorbable  Whether the state is in an absorbable region
+  next          Next state in the linked list
+
+The size of the counts array is rpPattern->maxDepth (= maximum group
+nesting depth + 1), allocated as a flexible array member at the end of
+the struct.
+
+Example: In PATTERN ((A B)+ C), a state waiting for B in the 3rd iteration
+
+  Element array: [0:BEGIN(d0) 1:A(d1) 2:B(d1) 3:END(d0) 4:C(d0) 5:FIN]
+
+  elemIdx = 2 (B, depth 1)
+  counts[0] = 2 (depth 0: depth of END. Group completed 2 iterations)
+  counts[1] = 1 (depth 1: depth of B. A matched in current iteration)
+
+  Counts are indexed by depth, not by elemIdx.
+  counts[0] is incremented when passing through END(depth 0),
+  and the group repetition count is preserved even when
+  the state is at B(depth 1).
+
+Definition of two states being "equal":
+
+  Two states are equal if they have the same elemIdx and the same counts
+  up to the depth of that element.
+  nfa_states_equal() compares counts[0..elem->depth] using memcmp.
+  Only counts at or below the depth of the current element are meaningful.
+
+V-2. RPRNFAContext -- Matching Context
+
+A single context represents "a matching attempt started from a specific
+start row."
+
+  Field                 Description
+  ---------------------------------------------------------------------
+  states                Linked list of active NFA states
+  matchStartRow         Row number where matching started
+  matchEndRow           Row number where matching completed
+                        (-1 if incomplete)
+  lastProcessedRow      Last row processed
+  matchedState          State that reached FIN (for greedy fallback)
+  hasAbsorbableState    Whether this context can absorb other contexts
+  allStatesAbsorbable   Whether this context can be absorbed
+  next, prev            Doubly-linked list
+
+Since the NFA is nondeterministic, multiple states can coexist
+simultaneously within a single context.
+
+Example: In PATTERN (A | B) C, if the first row matches both A and B,
+two states coexist within the context:
+
+  State 1: elemIdx=3 (waiting for C, via branch A)
+  State 2: elemIdx=3 (waiting for C, via branch B)
+
+In this case, since the (elemIdx, counts) of the two states are equal,
+nfa_add_state_unique() retains only State 1 (branch A), which was
+added first.
+Because DFS processes the first branch of ALT first, the state via A
+is registered first, and the state via B is discarded as a duplicate.
+This is the preferment guarantee.
+
+V-3. RPR Fields of WindowAggState
+
+  nfaContext / nfaContextTail   Doubly-linked list of active contexts
+  nfaContextFree                Reuse pool for contexts
+  nfaStateFree                  Reuse pool for states
+  nfaVarMatched                 Per-row cache: varMatched[varId]
+  nfaVisitedElems               Bitmap for cycle detection
+  nfaVisitedMinWord             Lowest bitmapword index touched since last reset
+  nfaVisitedMaxWord             Highest bitmapword index touched since last reset
+  nfaStateSize                  Precomputed size of RPRNFAState
+  defineMatchStartDependent     DEFINE vars needing per-context evaluation (match_start_dependent)
+  nfaLastProcessedRow           Last row processed by NFA (-1 = none)
+
+  EXPLAIN ANALYZE instrumentation counters are omitted here; see
+  execnodes.h for the full list.
+
+Memory management:
+
+  States and contexts are managed through their own free lists.
+  Instead of palloc, they are obtained from the reuse pool, and
+  returned to the pool upon deallocation.
+  This reduces the overhead of frequent allocation/deallocation.
+
+Chapter VI  NFA Execution: 3-Phase Model
+============================================================================
+
+VI-1. Entry Point and Overall Flow
+
+When the window function processes each row, row_is_in_reduced_frame()
+is called. This function determines whether the current row belongs to
+a matched frame, and if necessary, calls update_reduced_frame() to
+drive the NFA.
+
+Flow of update_reduced_frame():
+
+  (1) Find or create a context for the target row
+  (2) Enter the row processing loop
+  (3) After the loop ends, record the match result
+
+Pseudocode of the row processing loop:
+
+  targetCtx = ExecRPRGetHeadContext(pos)
+  if targetCtx == NULL:
+      targetCtx = ExecRPRStartContext(pos)
+
+  for currentPos = startPos; targetCtx->states != NULL; currentPos++:
+      if not nfa_evaluate_row(currentPos):  -- row does not exist
+          ExecRPRFinalizeAllContexts()      -- finalize all contexts
+          ExecRPRCleanupDeadContexts()      -- clean up after finalization
+          break
+
+      ExecRPRProcessRow(currentPos)         -- 3-phase processing
+      ExecRPRStartContext(currentPos + 1)   -- pre-create next start point
+      ExecRPRCleanupDeadContexts()          -- remove dead contexts
+
+Key point: Processing a single row may require processing multiple rows
+ahead. Due to the nature of window functions, determining the frame for
+row N requires looking at rows beyond N.
+
+VI-2. Context Creation: ExecRPRStartContext()
+
+Creates a new context and performs the initial advance.
+
+  (1) Allocate context via nfa_context_make()
+  (2) Set matchStartRow = pos
+  (3) Create initial state: elemIdx=0 (first pattern element),
+      counts=all zero
+  (4) Call nfa_advance() with currentPos = pos - 1 (no row consumed
+      yet)
+
+The initial advance expands epsilon transitions at the beginning of
+the pattern. For example, the initial advance for PATTERN ((A | B) C):
+
+  Start: elemIdx=0 (ALT)
+    -> Expand ALT branches
+      -> elemIdx=1 (A) -- VAR, so add state; stop here
+      -> elemIdx=2 (B) -- VAR, so add state; stop here
+
+  Result: Two states in the context {waiting for A, waiting for B}
+
+During the initial advance, reaching FIN is not recorded as a match.
+This is to prevent empty matches.
+
+VI-3. Row Evaluation: nfa_evaluate_row()
+
+Evaluates all variable conditions in the DEFINE clause at once for
+the current row.
+
+  for each defineClause[i]:
+      result = ExecEvalExpr(defineClause[i])
+      varMatched[i] = (not null and true)
+
+To support row navigation operators (PREV, NEXT, FIRST, LAST),
+a 1-slot model is used: only ecxt_outertuple is set to the current
+row.  Navigation is handled by EEOP_RPR_NAV_SET/RESTORE opcodes
+emitted during DEFINE expression compilation:
+
+  NAV_SET:     save ecxt_outertuple, swap in target row via nav_slot
+  (evaluate):  argument expression reads from swapped slot
+  NAV_RESTORE: restore original ecxt_outertuple
+
+Compound navigation (PREV(FIRST()), NEXT(FIRST()), PREV(LAST()),
+NEXT(LAST())) is flattened by the parser into a single RPRNavExpr
+with a compound kind (RPR_NAV_PREV_FIRST, etc.).  The executor
+computes the target position in two steps: first the inner reference
+point (match_start + N or currentpos - N) with match-range validation,
+then the outer adjustment (+/- M) with partition-range validation.
+If either step is out of range, the result is NULL.
+
+nav_slot caches the last fetched position (nav_slot_pos) to avoid
+redundant tuplestore lookups when multiple navigation calls target
+the same row.
+
+The varMatched array is referenced later in Phase 1 (Match).
+
+VI-4. Per-Context Re-evaluation (match_start_dependent variables)
+
+DEFINE variables that depend on match_start (those containing FIRST,
+LAST-with-offset, or compound PREV_FIRST/NEXT_FIRST/PREV_LAST/NEXT_LAST)
+are identified at plan time via defineMatchStartDependent.  The shared
+evaluation in nfa_evaluate_row() uses the head context's matchStartRow
+for FIRST/LAST base position.
+
+When processing a context whose matchStartRow differs from the shared
+value, nfa_reevaluate_dependent_vars() temporarily sets nav_match_start
+to that context's matchStartRow and re-evaluates only the dependent
+variables.  The original nav_match_start and currentpos are saved and
+restored after re-evaluation.
+
+Summary of evaluation strategy by navigation content:
+
+  Navigation content               evaluation
+  -------------------------------------------------------
+  No navigation                    shared (once per row)
+  PREV/NEXT only                   shared (once per row)
+  LAST (no offset)                 shared (once per row)
+  LAST (with offset)               per-context
+  FIRST (any)                      per-context
+  Compound (inner FIRST)           per-context
+  Compound (inner LAST, no off.)   shared (once per row)
+  Compound (inner LAST, w/off.)    per-context
+
+VI-5. Tuplestore Mark and Trim (nodeWindowAgg.c)
+
+Navigation functions require access to past rows via the tuplestore.
+To allow tuplestore_trim() to free rows that are no longer reachable,
+the planner computes two offsets (see compute_define_metadata):
+
+  navMaxOffset (Nav Mark Lookback):
+    Maximum backward reach from currentpos.  Contributed by PREV,
+    LAST-with-offset, and compound PREV_LAST/NEXT_LAST.
+    Mark position: currentpos - navMaxOffset.
+
+  navFirstOffset (Nav Mark Lookahead):
+    Minimum forward offset from match_start.  Contributed by FIRST
+    and compound PREV_FIRST/NEXT_FIRST.  Can be negative when
+    compound PREV_FIRST looks before match_start.
+    Mark position: oldest_context->matchStartRow + navFirstOffset.
+
+The actual mark is set to: min(lookback_mark, lookahead_mark).
+This ensures all rows reachable by any navigation function are retained.
+
+When offsets contain non-constant expressions (Param), the planner sets
+navMaxOffsetKind/navFirstOffsetKind to RPR_NAV_OFFSET_NEEDS_EVAL and the
+executor evaluates them at init time.  On overflow, the kind is set to
+RPR_NAV_OFFSET_RETAIN_ALL, disabling trim for that dimension.
+
+VI-6. ExecRPRProcessRow(): 3-Phase Processing
+
+NFA processing for a single row is divided into three phases:
+
+  +--------------------------------------------+
+  |  Phase 1: MATCH (convergence)              |
+  |  Compare the current row against each VAR  |
+  |  state. Remove states that fail to match.  |
+  |                                            |
+  |  Phase 2: ABSORB (absorption)              |
+  |  Merge duplicate contexts to prevent       |
+  |  state explosion.                          |
+  |                                            |
+  |  Phase 3: ADVANCE (expansion)              |
+  |  Expand epsilon transitions to prepare     |
+  |  for the next row.                         |
+  +--------------------------------------------+
+
+This ordering is important:
+
+  - Match executes first to "consume the current row."
+  - Absorb executes immediately after Match, when states have been updated.
+  - Advance executes last to prepare "states waiting for the next row."
+
+Chapter VII  Phase 1: Match
+============================================================================
+
+nfa_match() iterates through each state in the context:
+
+  (1) Check whether the state's elemIdx is a VAR element
+  (2) Compare against the current row using nfa_eval_var_match()
+  (3) Match success: increment repetition count, retain state
+  (4) Match failure: remove state
+
+Match determination (nfa_eval_var_match):
+
+  If varId is within the range of defineVariableList:
+      Use the value of varMatched[varId]
+
+  If varId exceeds the range (variable not defined in DEFINE):
+      Unconditionally true (matches all rows)
+
+Immediate advance for simple VARs:
+
+  For a VAR with min=1, max=1 where the next element is END,
+  the Match phase processes through END immediately.
+  This is necessary for accurate state comparison in Phase 2 (Absorb).
+
+  Example: In PATTERN ((A B)+), when A matches, it immediately advances
+  to B, and when B matches, it immediately advances through END to
+  complete the group count. This enables absorption comparison with
+  other contexts.
+
+Chapter VIII  Phase 2: Absorb (Context Absorption)
+============================================================================
+
+Absorption is the runtime optimization that collapses contexts which
+have converged on identical future behavior.  Two contexts are
+treated as equivalent when one's bookkeeping (elemIdx and per-depth
+iteration counts) is dominated by another's; the younger one is then
+discarded.  The optimization is safe because pattern matching is
+monotonic -- an earlier context's reachable matches always contain a
+later context's.  This is what reduces the naive O(N^2) state count
+to O(N).
+
+VIII-1. Problem
+
+In the current implementation, a new context is started for each row
+processed.
+Applying PATTERN (A+) to 10 rows produces 10 contexts,
+each of which tracks state independently.
+
+If there are N rows, the total number of states becomes O(N^2):
+
+  Context 1 (started at row 1): can match A up to N times
+  Context 2 (started at row 2): can match A up to N-1 times
+  ...
+  Context N (started at row N): can match A 1 time
+
+VIII-2. Solution: Context Absorption
+
+Key observation: a context started earlier contains
+all matches of a later-started context (monotonicity principle).
+
+If Context 1 started at row 1 and matched A 5 times,
+the state where Context 2 (started at row 2) matched A 4 times
+is already contained within Context 1.
+
+Therefore Context 2 can be "absorbed" into Context 1.
+
+Worked example for PATTERN (A+) over 3 rows (each matches A):
+
+  After row 1:
+    Ctx_1 (started row 1): state at A with counts[0] = 1
+
+  After row 2:
+    Ctx_1: state at A with counts[0] = 2
+    Ctx_2 (started row 2): state at A with counts[0] = 1
+    -> Same elemIdx; Ctx_1.count (2) dominates Ctx_2.count (1).
+    -> Ctx_2 absorbed.
+
+  After row 3:
+    Ctx_1: state at A with counts[0] = 3
+    Ctx_3 (started row 3): state at A with counts[0] = 1
+    -> Ctx_1.count (3) dominates Ctx_3.count (1).
+    -> Ctx_3 absorbed.
+
+Total active contexts stays at O(1) instead of growing with N.
+
+VIII-3. Absorption Conditions
+
+Planner-time prerequisites (all must hold for absorption to be enabled):
+
+  (a) SKIP PAST LAST ROW.  SKIP TO NEXT ROW creates overlapping
+      contexts that cannot be safely absorbed.
+  (b) Unbounded frame (ROWS BETWEEN CURRENT ROW AND UNBOUNDED
+      FOLLOWING).  Limited frames apply differently to each context,
+      breaking the monotonicity principle.
+  (c) No match_start_dependent navigation in DEFINE.
+
+      Mechanism: each context has a different matchStartRow, so FIRST
+      resolves to a different row for each context at the same
+      currentpos.  An earlier context's DEFINE result no longer
+      subsumes a later one's, making count-dominance comparison
+      invalid.  Rather than comparing matchStartRow at runtime
+      (which would complicate the absorb path), any match_start
+      dependency disables absorption entirely.
+
+      Navigation content              match_start dep.  absorption
+      ------------------------------------------------------------
+      No navigation                   none              safe
+      PREV/NEXT only                  none              safe
+      LAST (no offset)                none              safe
+      LAST (with offset)              boundary check    unsafe
+      FIRST (any)                     direct            unsafe
+      Compound (inner FIRST)          direct            unsafe
+      Compound (inner LAST, no off.)  none              safe
+      Compound (inner LAST, w/off.)   boundary check    unsafe
+
+  The "match_start dep." column classifies how the navigation ties a
+  DEFINE result to the context's matchStartRow:
+
+    none            Independent of matchStartRow.  The result depends
+                    only on currentpos (or a fixed offset from it), so
+                    every context evaluates it identically.
+    direct          Computed from matchStartRow itself -- FIRST counts
+                    forward from match start -- so the resolved row,
+                    and thus the result, differs per context.
+    boundary check  The resolved row is currentpos-relative (LAST with
+                    a backward offset, or a compound whose inner LAST
+                    carries an offset), but its in-range test is taken
+                    against the match range [matchStartRow, currentpos].
+                    The range bound differs per context, so the result
+                    can too.
+
+  Only "none" is safe for absorption; "direct" and "boundary check"
+  both make an earlier context's result stop subsuming a later one's
+  (see (c) above).
+
+Runtime conditions (evaluated per context pair):
+
+  (1) The pattern is marked as isAbsorbable (see IV-5)
+  (2) allStatesAbsorbable of the target context is true
+  (3) An earlier context "covers" all states of the target
+
+Cover condition (nfa_states_covered) -- "count-dominance":
+
+  A state with the same elemIdx exists in the earlier context,
+  and the count at that depth is greater than or equal -- then it is
+  covered.  The earlier context's per-depth iteration count thus
+  dominates the later one's; this is the count-dominance comparison
+  referenced in VIII-3(c).
+
+VIII-4. Dual-Flag Design
+
+Two boolean flags make the absorption decision efficient:
+
+  hasAbsorbableState (monotonic: only true->false transition possible)
+    "Does this context have the ability to absorb other contexts?"
+    true if at least one absorbable state exists.
+    Transitions to false when states are removed leaving no absorbable
+    states.
+    Once false, it never becomes true again.
+
+  allStatesAbsorbable (dynamic: can fluctuate)
+    "Can this context be absorbed?"
+    true if all states are in an absorbable region.
+    Becomes false when a non-absorbable state is added; reverts to true
+    when it is removed.
+
+VIII-5. Absorption Order
+
+nfa_absorb_contexts() traverses from tail (newest) to head (oldest).
+
+  for ctx = tail to head:
+      if ctx.allStatesAbsorbable:
+          for older = ctx.prev to head:
+              if older.hasAbsorbableState:
+                  if nfa_states_covered(older, ctx):
+                      free(ctx)  -- absorbed
+                      break
+
+Since inspection starts from the newest context, the most recently started
+(= having the shortest match) context is absorbed first.
+
+Chapter IX  Phase 3: Advance (Epsilon Transition Expansion)
+============================================================================
+
+IX-1. Overview
+
+nfa_advance() expands epsilon transitions from each state after Match,
+generating "new states waiting for the next row."
+
+An epsilon transition is a transition that moves without consuming a row:
+
+  - ALT: branch to each alternative
+  - BEGIN: enter group (or skip if min=0)
+  - END: loop-back within group (or exit when condition is met)
+  - FIN: record match completion
+  - VAR loop/exit: repeat/exit according to the quantifier
+
+Expansion stops upon reaching a VAR element, and the state is added.
+This is because VAR is the element that "will consume the next row."
+
+IX-2. Processing Order: DFS and Preferment
+
+advance processes states in lexicographic order,
+performing Depth-First Search (DFS) on each state.
+
+This DFS order is what guarantees the SQL standard's "preferment":
+
+  The branch that appears first in the PATTERN text takes precedence.
+
+Example: PATTERN (A | B) C
+
+  The first branch A of the ALT takes precedence over the second branch B.
+  When both A and B can match, the match via A is selected.
+
+nfa_add_state_unique() prevents duplicate addition of the same state,
+so the state added first (= from the preferred branch) is retained.
+
+IX-3. Routing Function: nfa_route_to_elem()
+
+All inter-element transitions in the advance phase go through
+nfa_route_to_elem().
+This function branches its behavior based on the type of the next element:
+
+  If the next element is VAR:
+    (1) Add the state to the context (nfa_add_state_unique)
+    (2) If the VAR has min=0, also add a skip path (recurse via next)
+    -> Expansion stops here (VAR is the element that "will consume the next
+       row")
+
+  If the next element is non-VAR (ALT, BEGIN, END, FIN):
+    -> Recursively call nfa_advance_state() to continue expansion
+
+With this structure, advance recursively follows epsilon transitions
+until reaching a VAR, consistently stopping only at VAR elements.
+
+IX-4. Per-Element advance Behavior
+
+(a) ALT (nfa_advance_alt)
+
+  Upon encountering an ALT element, all branches are expanded in order.
+  The first element of each branch is connected via a jump pointer.
+
+  idx=0 (ALT) -> branch 1 start (next) -> branch 2 start (jump) -> ...
+
+  nfa_advance_state() is recursively called for each branch.
+
+(b) BEGIN (nfa_advance_begin)
+
+  Handles group entry.
+  jump points to the element after END (= first element outside the group).
+
+  Greedy (default):
+    (1) Enter the group body (move via next, reset the count at that depth)
+    (2) If min=0, also add a group skip path (move via jump)
+
+  Reluctant:
+    Order reversed -- skip path first, group entry second.
+    If the skip path reaches FIN, the group entry path is not generated
+    (shortest match preferred).
+
+(c) END (nfa_advance_end)
+
+  Handles group termination. This is the core of the repetition logic.
+
+  Let count be the count at the current depth:
+
+  count < min:
+    Loop-back (move via jump, repeat the group body)
+
+    If the RPR_ELEM_EMPTY_LOOP flag is set:
+      In addition to loop-back, also add a fast-forward exit path.
+      This is because the body may produce an empty match, causing count
+      to never reach min. fast-forward resets counts[depth] to 0
+      and exits via next (treating the remaining required iterations
+      as empty matches).
+
+  min <= count < max:
+    Greedy: loop-back first, exit second
+    Reluctant: exit first, loop-back second
+               If the exit path reaches FIN, loop-back is omitted.
+
+  count >= max:
+    Unconditional exit (move via next)
+
+  On exit: reset counts[depth] = 0, and if the next element is an outer END,
+  increment the count at the outer depth.
+
+(d) VAR (nfa_advance_var)
+
+  Handles repeat/exit for a VAR element with a quantifier.
+
+  Let count be the count at the current depth:
+
+  count < min:
+    Unconditional loop (stay at the same elemIdx, wait for the next row)
+
+  min <= count < max:
+    Greedy: loop first, exit (next) second
+    Reluctant: exit first, loop second
+               If the exit path reaches FIN, loop is omitted.
+
+  count >= max:
+    Unconditional exit (move via next)
+
+  On exit: reset counts[depth] = 0.
+
+(e) FIN
+
+  Match success. The current state is moved to matchedState for recording,
+  and matchEndRow is set to the current row.
+
+  Upon reaching FIN, all remaining unprocessed states are removed
+  (early termination). By DFS order, the path that reached FIN first
+  has the highest preferment, so the rest are inferior paths.
+  This is the core mechanism that guarantees preferment.
+
+  In SKIP PAST LAST ROW mode, upon reaching FIN, subsequent contexts
+  that started within the match range are immediately pruned.
+
+IX-5. State Deduplication: nfa_add_state_unique()
+
+When adding a new state to a context, it is compared against existing
+states;
+if an identical state already exists, it is not added.
+
+Comparison criteria: elemIdx + counts[0..elem->depth] (see V-1)
+
+This deduplication is the core mechanism that suppresses NFA state
+explosion.
+Because DFS order causes preferred-branch states to be added first,
+identical states from lower-priority branches are automatically discarded.
+
+IX-6. Cycle Detection: nfaVisitedElems
+
+When a group body can produce an empty match,
+looping back from END may cause an infinite loop.
+
+Example: PATTERN ((A?)*)
+
+  A? has min=0, so it can pass through without matching.
+  If the outer group repeats: BEGIN -> A? skip -> END -> BEGIN -> ...
+
+To prevent this:
+
+  (1) At compile time: set the RPR_ELEM_EMPTY_LOOP flag on the END
+      of groups whose body is nullable.
+      The runtime effect of this flag is described in IX-4(c):
+      when count < min, a fast-forward exit path is added,
+      resolving the deadlock where count cannot increase due to empty
+      matches.
+
+  (2) At runtime: initialize the nfaVisitedElems bitmap immediately before
+      DFS expansion of each state within advance (once per state).
+      During DFS, epsilon elements (END, ALT, BEGIN) are marked in the
+      bitmap at nfa_advance_state entry.  VAR elements are marked later
+      when added to the state list (nfa_add_state_unique), so that
+      legitimate loop-back to the same VAR in a new group iteration
+      (e.g., END -> ALT -> same VAR) is not blocked.
+      If a previously visited elemIdx is revisited, that path is terminated.
+
+  Note: the bitmap tracks only elemIdx and does not consider counts.
+  Therefore, legitimate revisits to the same elemIdx but with different
+  counts may also be blocked.  This only occurs when the group body is
+  nullable (all paths can match empty), causing END -> loop-back ->
+  skip -> END within a single DFS.  In such cases the END element has
+  the RPR_ELEM_EMPTY_LOOP flag, so the fast-forward exit (IX-4(c))
+  provides an alternative path that bypasses the cycle.
+
+Chapter X  Match Result Processing
+============================================================================
+
+X-1. Match Result
+
+RPR tracks the current match result as a single entry in WindowAggState
+with four fields: rpr_match_valid, rpr_match_matched, rpr_match_start,
+and rpr_match_length.  When rpr_match_valid is true, the entry describes
+the match result for the position at rpr_match_start: rpr_match_matched
+indicates success or failure, and rpr_match_length gives the number of
+rows consumed.  A match with rpr_match_length 0 represents an empty match
+(pattern matched but consumed no rows).  When rpr_match_valid is false,
+the position has not been evaluated yet (RF_NOT_DETERMINED).
+
+A row's status against the current match result can be obtained by
+calling get_reduced_frame_status().
+
+X-2. AFTER MATCH SKIP
+
+Determines the starting point for the next match attempt after a successful
+match:
+
+  SKIP TO NEXT ROW:
+    New match attempt begins from the row after the match start row.
+    Overlapping matches are possible.
+
+  SKIP PAST LAST ROW:
+    New match attempt begins from the row after the match end row.
+    Only non-overlapping matches are possible.
+
+X-3. INITIAL vs SEEK
+
+  Standard definition (ISO/IEC 19075-5 6.12):
+  INITIAL: "is used to look for a match whose first row is R."
+  SEEK:    "is used to permit a search for the first match anywhere
+           from R through the end of the full window frame."
+  In either case, if there is no match, the reduced window frame is empty.
+  The default is INITIAL.
+
+  Current implementation:
+  SEEK is not supported (the parser raises an error).
+  Only INITIAL is supported, searching only for matches starting at each
+  row position pos.
+
+X-4. Bounded Frame Handling
+
+  With RPR, the frame mode is always ROWS and the frame start must be
+  CURRENT ROW. The frame end must be UNBOUNDED FOLLOWING or a positive
+  offset (n >= 1) FOLLOWING; a CURRENT ROW end or a zero offset is
+  rejected, since it would reduce the frame to the single current row.
+
+  When the frame is bounded (e.g., ROWS BETWEEN CURRENT ROW AND 5
+  FOLLOWING), ExecRPRProcessRow receives hasLimitedFrame=true and
+  frameOffset indicating the upper bound.  Before the match phase,
+  any context whose match has exceeded the frame boundary
+  (currentPos >= matchStartRow + frameOffset + 1) is finalized early
+  by forcing a mismatch.  This prevents matches from extending beyond
+  the window frame.  The sum is clamped to PG_INT64_MAX on overflow.
+
+  Note that bounded frames also disable context absorption at the
+  planner level (see VIII-3(b)), since the frame boundary breaks the
+  monotonicity assumption required for correct absorption.
+
+Chapter XI  Worked Example: Full Execution Trace
+============================================================================
+
+XI-1. Query
+
+  SELECT company, tdate, price,
+         first_value(price) OVER w AS start_price,
+         last_value(price) OVER w AS end_price
+  FROM stock
+  WINDOW w AS (
+    PARTITION BY company
+    ORDER BY tdate
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    AFTER MATCH SKIP PAST LAST ROW
+    PATTERN (A+ B)
+    DEFINE A AS price > PREV(price),
+           B AS price < PREV(price)
+  );
+
+XI-2. Data
+
+  Row#    tdate       price
+  --------------------------
+  0       2024-01-01  100
+  1       2024-01-02  110
+  2       2024-01-03  120
+  3       2024-01-04  115
+  4       2024-01-05  130
+
+XI-3. Compilation Result
+
+  PATTERN (A+ B) -> unchanged after optimization
+
+  idx  varId  depth  min  max  next  jump
+  -----------------------------------------
+   0   A(0)   0      1    INF  1     -1     A+
+   1   B(1)   0      1    1    2     -1     B
+   2   FIN    0      1    1    -1    -1
+
+  DEFINE: A -> "price > PREV(price)", B -> "price < PREV(price)"
+  isAbsorbable = true (A+ is a simple unbounded VAR)
+
+XI-4. Execution Trace
+
+--- Row 0 (price=100) ---
+
+  update_reduced_frame(0) called.
+
+  Context C0 created (matchStartRow=0).
+  Initial advance: elemIdx=0(A) -> VAR, so state is added.
+  C0.states = [{elemIdx=0, counts=[0]}]
+
+  nfa_evaluate_row(0):
+    A: price(100) > PREV(price) -> no PREV -> false
+    B: price(100) < PREV(price) -> no PREV -> false
+    varMatched = [false, false]
+
+  ExecRPRProcessRow(0):
+    Phase 1 (Match): A(0) state vs varMatched[0]=false -> state removed
+    C0.states = [] (empty)
+
+    Phase 2 (Absorb): skipped (no states)
+    Phase 3 (Advance): skipped (no states)
+
+  C0.states is empty, so the loop terminates.
+  matchEndRow < matchStartRow -> unmatched.
+
+--- Row 1 (price=110) ---
+
+  update_reduced_frame(1) called.
+
+  Context C1 created (matchStartRow=1).
+  Initial advance: C1.states = [{elemIdx=0, counts=[0]}]
+
+  nfa_evaluate_row(1):
+    A: 110 > PREV(100) -> true
+    B: 110 < PREV(100) -> false
+    varMatched = [true, false]
+
+  ExecRPRProcessRow(1):
+    Phase 1 (Match): A(0) match succeeds -> counts[0]++ -> counts=[1]
+    C1.states = [{elemIdx=0, counts=[1]}]
+
+    Phase 3 (Advance):
+      State {elemIdx=0, counts=[1]}: A+ (min=1, count=1, max=INF)
+        count >= min, so:
+        Greedy -> loop first: keep {elemIdx=0, counts=[1]}
+                  exit: reset counts[0]=0, next(=1) -> {elemIdx=1,
+                        counts=[0]}
+    C1.states = [{elemIdx=0, counts=[1]}, {elemIdx=1, counts=[0]}]
+
+--- Row 2 (price=120) ---
+
+  Context C2 created (matchStartRow=2).
+  Initial advance: C2.states = [{elemIdx=0, counts=[0]}]
+
+  nfa_evaluate_row(2):
+    A: 120 > PREV(110) -> true
+    B: 120 < PREV(110) -> false
+    varMatched = [true, false]
+
+  C1 ExecRPRProcessRow(2):
+    Phase 1 (Match):
+      {elemIdx=0, counts=[1]}: A matches -> counts=[2]
+      {elemIdx=1, counts=[0]}: B does not match -> removed
+    C1.states = [{elemIdx=0, counts=[2]}]
+
+  C2 ExecRPRProcessRow(2):
+    Phase 1 (Match):
+      {elemIdx=0, counts=[0]}: A matches -> counts=[1]
+    C2.states = [{elemIdx=0, counts=[1]}]
+
+    Phase 2 (Absorb):
+      Does C1 (started earlier) cover C2?
+        C1: {elemIdx=0, counts=[2]}, C2: {elemIdx=0, counts=[1]}
+        Same elemIdx, C1.counts >= C2.counts -> covered
+      C2 absorbed. -> removed.
+
+    Phase 3 (Advance):
+      {elemIdx=0, counts=[2]}: Greedy -> loop + exit
+        Loop: {elemIdx=0, counts=[2]}
+        Exit: reset counts[0]=0, next(=1) -> {elemIdx=1, counts=[0]}
+    C1.states = [{elemIdx=0, counts=[2]}, {elemIdx=1, counts=[0]}]
+
+  Context C3 created (matchStartRow=3).
+
+--- Row 3 (price=115) ---
+
+  nfa_evaluate_row(3):
+    A: 115 > PREV(120) -> false
+    B: 115 < PREV(120) -> true
+    varMatched = [false, true]
+
+  ExecRPRProcessRow(3):
+    Phase 1 (Match):
+      {elemIdx=0, counts=[2]}: A does not match -> removed
+      {elemIdx=1, counts=[0]}: B matches -> counts=[1]
+    C1.states = [{elemIdx=1, counts=[1]}]
+
+    Phase 3 (Advance):
+      {elemIdx=1, counts=[1]}: B (min=1, max=1)
+        count(1) >= max(1) -> unconditional exit
+        Reset counts[0]=0, next = 2 (FIN)
+      FIN reached -> matchEndRow = 3, matchedState recorded.
+      Early termination: no remaining states, so completed immediately.
+    C1.states = [] (empty after reaching FIN)
+
+  C1.states is empty and matchEndRow=3 >= matchStartRow=1 -> match succeeds.
+
+  rpr_match_start = 1, rpr_match_length = 3
+
+--- Row 4 (price=130) ---
+
+  update_reduced_frame(4) called.
+  C3 was already created but matchStartRow=3, so it is not applicable.
+  New context C4 created (matchStartRow=4).
+
+  nfa_evaluate_row(4):
+    A: 130 > PREV(115) -> true
+    B: 130 < PREV(115) -> false
+
+  ... No subsequent rows, so ExecRPRFinalizeAllContexts() is called.
+  Match incomplete -> unmatched.
+
+XI-5. Final Result
+
+  Row 0: unmatched     -> reduced frame empty (window funcs NULL, count() 0)
+  Row 1: match head    -> frame = rows 1 through 3
+  Row 2: inside match  -> skipped
+  Row 3: inside match  -> skipped
+  Row 4: unmatched     -> reduced frame empty (window funcs NULL, count() 0)
+
+Chapter XII  Summary of Key Design Decisions
+============================================================================
+
+XII-1. Flat Array vs Tree-Based NFA
+
+  The compiled pattern is stored as a flat array of fixed-size 16-byte
+  RPRPatternElement structs rather than as a tree.
+
+  The array is contiguous and cache-friendly, elements reference each
+  other by 2-byte index instead of by pointer, and the whole structure
+  can be serialized with memcpy when passed to plan nodes.
+
+XII-2. Forward-only Execution vs Backtracking
+
+  The NFA is simulated forward-only, tracking a set of live states,
+  rather than by backtracking.
+
+  Backtracking would take exponential time in the worst case, whereas
+  forward-only NFA simulation is polynomial.  Forward-only also fits the
+  window pipeline, which delivers sorted rows sequentially: it needs no
+  re-fetching of earlier rows, and each row's DEFINE conditions (SQL
+  expressions such as PREV or running aggregates, with high re-evaluation
+  cost) are evaluated only once.  DFS order yields preferment naturally,
+  with greedy or reluctant behavior per quantifier obtained by reversing
+  that order.
+
+XII-3. Per-Context Management
+
+  A separate match context is maintained for each start row.
+
+  This supports overlapping matches under SKIP TO NEXT ROW, determines
+  each row's frame independently, and lets the absorption optimization
+  eliminate redundant contexts in O(n).
+
+XII-4. Memory Pool Management
+
+  NFA states are managed through a custom free list, and both RPRNFAState
+  and RPRNFAContext are allocated in a partition-lifespan memory context
+  that is freed in release_partition.
+
+  NFA states are created and destroyed in large numbers per row, so the
+  free list avoids palloc/pfree overhead.  Their size varies (the
+  counts[] array), but maxDepth is fixed within a single query, so all
+  states have the same size.
+
+XII-5. Execution Optimization Summary
+
+  The following optimizations make the NFA simulation practical.
+
+  -- Compile-time --
+
+  (1) AST Optimization (IV-3)
+
+    Simplifies the AST before converting the pattern to an NFA.
+    Reduces the number of NFA elements through consecutive variable
+    merging (A A -> A{2}), SEQ flattening, quantifier multiplication,
+    and other transformations.
+
+    Significance: Reducing the element count directly shrinks the state
+    space, decreasing the cost of all subsequent runtime phases (match,
+    absorb, advance).
+
+  -- Runtime: advance phase --
+
+  (2) Group Skip (IX-4(b))
+
+    At the BEGIN of a group with min=0, uses jump to skip the entire
+    group. Moves directly to the first element outside the group without
+    exploring the group body. Greedy enters then skips; Reluctant skips
+    then enters.
+
+    Significance: For optional groups (min=0), immediately generates
+    a skip path without exploring the body, avoiding unnecessary DFS
+    expansion.
+
+  (3) State Deduplication (IX-5)
+
+    During advance, DFS may generate states with the same (elemIdx,
+    counts) combination through multiple paths. Additionally, for
+    group absorption, nfa_match performs inline advance from bounded
+    VARs (count >= max) within the absorbable region (ABSORBABLE_BRANCH)
+    through END chains to reach the judgment point (ABSORBABLE END).
+    This process can also produce duplicate states reaching the same END.
+    nfa_add_state_unique() blocks duplicate addition of identical states
+    in both cases.
+
+    Significance: Prevents exponential growth of the state count in
+    ALT branches and quantifier expansion. Since DFS order causes the
+    preferred branch's state to be registered first, identical states
+    from lower-priority branches are automatically discarded, thereby
+    also guaranteeing preferment.
+
+  (4) Cycle Detection and Fast-Forward (IX-6, IX-4(c))
+
+    When a nullable group body (e.g., A?) repeats empty matches,
+    the END -> BEGIN loop-back can continue indefinitely.
+
+    Two mechanisms resolve this:
+    - A visited bitmap (nfaVisitedElems) blocks revisitation of the
+      same element, preventing infinite loops (safety)
+    - At an END with the RPR_ELEM_EMPTY_LOOP flag set, when
+      count < min, the remaining required iterations are treated as
+      empty matches and a fast-forward exit path out of the group is
+      added (correctness)
+
+    Significance: Cycle detection guarantees termination, and
+    fast-forward guarantees that the min condition is satisfied.
+    Without these, patterns containing nullable groups would fall
+    into infinite loops or fail to match.
+
+  (5) Match Pruning (IX-4(e))
+
+    When a state reaches FIN during advance, all remaining unprocessed
+    states of that context are removed. Because of DFS order, the path
+    that reaches FIN first has the highest preferment, so the remaining
+    paths are inferior.
+
+    Significance: Once the best match is determined, exploration of
+    inferior paths is immediately terminated. This mechanism achieves
+    both preferment guarantees and performance optimization.
+
+  -- Runtime: inter-context --
+
+  (6) Early Termination (SKIP PAST LAST ROW)
+
+    In SKIP PAST LAST ROW mode, when a match is found, subsequent
+    contexts whose start rows fall within the match range are pruned
+    immediately without further processing.
+    In SKIP TO NEXT ROW mode, overlapping contexts are preserved
+    because each row requires its own independent match.
+
+    Significance: Prunes subsequent contexts whose start rows overlap
+    with a prior match range, avoiding unnecessary processing.
+
+  (7) Context Absorption (Chapter VIII)
+
+    If an independent context is created for each row, O(n^2) states
+    accumulate. By exploiting the monotonicity that an earlier-started
+    context subsumes the states of a later-started context, redundant
+    contexts are eliminated early.
+
+    Absorbability is determined per-element; comparison is performed
+    only at elements with the RPR_ELEM_ABSORBABLE flag (see IV-5).
+
+    Significance: Keeps the number of active contexts at a constant
+    level, achieving O(n^2) -> O(n) time complexity. Without this,
+    performance degrades sharply on long partitions.
+
+Appendix A. Key Function Index
+============================================================================
+
+  Function                      File                  Role
+  --------------------------------------------------------------------------
+  transformRPR                  parse_rpr.c           Parser entry point
+  transformDefineClause         parse_rpr.c           DEFINE transformation
+  collectPatternVariables       rpr.c                 Variable collection
+  buildDefineVariableList       rpr.c                 DEFINE variable list
+  buildRPRPattern               rpr.c                 NFA compilation main
+  optimizeRPRPattern            rpr.c                 AST optimization
+  fillRPRPattern                rpr.c                 NFA element generation
+  finalizeRPRPattern            rpr.c                 Finalization
+  computeAbsorbability          rpr.c                 Absorption analysis
+  update_reduced_frame          nodeWindowAgg.c       Execution main loop
+  nfa_evaluate_row              nodeWindowAgg.c       DEFINE evaluation
+  ExecRPRStartContext           execRPR.c             Context creation
+  ExecRPRProcessRow             execRPR.c             3-phase processing
+  nfa_match                     execRPR.c             Phase 1
+  nfa_absorb_contexts           execRPR.c             Phase 2
+  nfa_advance                   execRPR.c             Phase 3
+  nfa_advance_state             execRPR.c             Per-state branching
+  nfa_route_to_elem             execRPR.c             Element routing
+  nfa_advance_alt               execRPR.c             ALT handling
+  nfa_advance_begin             execRPR.c             BEGIN handling
+  nfa_advance_end               execRPR.c             END handling
+  nfa_advance_var               execRPR.c             VAR handling
+  nfa_add_state_unique          execRPR.c             Deduplication
+  nfa_states_covered            execRPR.c             Absorption check
+  nfa_reevaluate_dependent_vars execRPR.c             Per-context re-eval
+  ExecRPRGetHeadContext         execRPR.c             Context lookup
+  ExecRPRFreeContext            execRPR.c             Context deallocation
+  ExecRPRCleanupDeadContexts    execRPR.c             Dead context cleanup
+  ExecRPRFinalizeAllContexts    execRPR.c             Partition-end finalize
+  ExecRPRRecordContextSuccess   execRPR.c             Stats: match success
+  ExecRPRRecordContextFailure   execRPR.c             Stats: match failure
+  compute_define_metadata       createplan.c          Trim offset computation
+
+Appendix B. Data Structure Relationship Diagram
+============================================================================
+
+  Parser Layer
+  --------
+  RPCommonSyntax
+    |--- rpSkipTo: RPSkipTo
+    |--- initial: bool
+    +--- rpPattern: RPRPatternNode* (tree)
+         |--- nodeType: VAR | SEQ | ALT | GROUP
+         |--- min, max: quantifier
+         |--- varName: variable name (VAR only)
+         +--- children: List* (SEQ/ALT/GROUP only)
+
+  Planner Layer
+  ----------
+  WindowAgg (plan node)
+    |--- rpSkipTo: RPSkipTo
+    |--- defineClause: List<TargetEntry>
+    +--- rpPattern: RPRPattern*
+         |--- numVars: int
+         |--- varNames: char**
+         |--- maxDepth: RPRDepth
+         |--- isAbsorbable: bool
+         |--- numElements: int
+         +--- elements: RPRPatternElement[]  (flat array)
+              |--- varId      (1B)
+              |--- depth      (1B)
+              |--- flags      (1B)
+              |--- reserved   (1B)
+              |--- min, max   (4B + 4B)
+              +--- next, jump (2B + 2B)
+
+  Executor Layer
+  ----------
+  WindowAggState
+    |--- rpSkipTo: RPSkipTo (AFTER MATCH SKIP mode)
+    |--- rpPattern: RPRPattern* (copied from plan)
+    |--- defineVariableList: List<String> (variable names, DEFINE order)
+    |--- defineClauseList: List<ExprState>
+    |--- nfaVarMatched: bool[] (per-row cache)
+    |--- defineMatchStartDependent: Bitmapset* (match_start_dependent
+    |        DEFINE vars; see VI-4)
+    |--- nfaVisitedElems: bitmapword* (cycle detection)
+    |--- nfaVisitedMinWord / nfaVisitedMaxWord: int16
+    |        (touched-word range for fast reset)
+    |--- nfaLastProcessedRow: int64 (-1 = none)
+    |--- nfaStateSize: Size (pre-calculated RPRNFAState allocation size)
+    |--- nfaContext <-> nfaContextTail (doubly-linked list)
+    |   +--- RPRNFAContext
+    |       |--- states: RPRNFAState* (linked list)
+    |       |   |--- elemIdx
+    |       |   |--- counts[]
+    |       |   +--- isAbsorbable
+    |       |--- matchStartRow, matchEndRow
+    |       |--- lastProcessedRow
+    |       |--- matchedState (cloned on FIN arrival)
+    |       |--- hasAbsorbableState
+    |       +--- allStatesAbsorbable
+    |--- nfaContextFree (recycling pool)
+    +--- nfaStateFree (recycling pool)
+
+Appendix C. NFA Element Array Examples
+============================================================================
+
+C-1. PATTERN (A B C)
+
+  idx  varId  depth  min  max  next  jump
+  ------------------------------------------
+   0   A      0      1    1    1     -1
+   1   B      0      1    1    2     -1
+   2   C      0      1    1    3     -1
+   3   FIN    0      1    1    -1    -1
+
+C-2. PATTERN (A+ B*)
+
+  idx  varId  depth  min  max  next  jump  flags
+  ------------------------------------------------------------------------
+   0   A      0      1    INF  1     -1    ABSORBABLE | ABSORBABLE_BRANCH
+   1   B      0      0    INF  2     -1
+   2   FIN    0      1    1    -1    -1
+
+  Only A+ is the absorption point (Case 1). Once past A,
+  absorption is permanently disabled for that state.
+
+C-3. PATTERN (A | B | C)
+
+  idx  varId  depth  min  max  next  jump
+  ----------------------------------------
+   0   ALT    0      1    1    1     -1    alternation start
+   1   A      1      1    1    4     2     branch 1 -> FIN, jump -> branch 2
+   2   B      1      1    1    4     3     branch 2 -> FIN, jump -> branch 3
+   3   C      1      1    1    4     -1    branch 3 -> FIN
+   4   FIN    0      1    1    -1    -1
+
+C-4. PATTERN ((A B)+ C)
+
+  idx  varId    depth  min  max  next  jump  flags
+  --------------------------------------------------------------------------
+   0   BEGIN    0      1    INF  1     4     ABSORBABLE_BRANCH
+   1   A        1      1    1    2     -1    ABSORBABLE_BRANCH
+   2   B        1      1    1    3     -1    ABSORBABLE_BRANCH
+   3   END      0      1    INF  4     1     ABSORBABLE | ABSORBABLE_BRANCH
+   4   C        0      1    1    5     -1
+   5   FIN      0      1    1    -1    -1
+
+  Case 2: GROUP+ with {1,1} body VARs. A, B are branches;
+  END is the absorption point. Compare with C-6 (Case 3).
+
+C-5. PATTERN ((A | B)+? C)
+
+  idx  varId    depth  min  max   next  jump  flags
+  -------------------------------------------------------------------
+   0   BEGIN    0      1    INF   1     5     RELUCTANT, group start
+   1   ALT      1      1    1     2     -1    alternation start
+   2   A        2      1    1     4     3     branch 1
+   3   B        2      1    1     4     -1    branch 2
+   4   END      0      1    INF   5     1     RELUCTANT, group end
+   5   C        0      1    1     6     -1
+   6   FIN      0      1    1     -1    -1
+
+C-6. PATTERN ((A+ B)+ C)  -- Absorbability flag example
+
+  idx  varId    depth  min  max   next  jump  flags
+  ---------------------------------------------------------------------------
+   0   BEGIN    0      1    INF   1     4     ABSORBABLE_BRANCH, group start
+   1   A        1      1    INF   2     -1    ABSORBABLE | ABSORBABLE_BRANCH
+   2   B        1      1    1     3     -1
+   3   END      0      1    INF   4     1     group end
+   4   C        0      1    1     5     -1
+   5   FIN      0      1    1     -1    -1
+
+  Recurses from BEGIN into the body -> A matches Case 1 (simple VAR+).
+  A gets ABSORBABLE | ABSORBABLE_BRANCH, BEGIN gets ABSORBABLE_BRANCH.
+  B and END get no flags -> absorption stops once the state advances to B.
+  (See IV-5 Case 3)
+
+C-7. PATTERN ((A+ B | C*)+ D)  -- Per-branch absorption in ALT
+
+  idx  varId    depth  min  max   next  jump  flags
+  ---------------------------------------------------------------------------
+   0   BEGIN    0      1    INF   1     6     ABSORBABLE_BRANCH
+   1   ALT      1      1    1     2     -1    ABSORBABLE_BRANCH
+   2   A        2      1    INF   3     4     ABSORBABLE | ABSORBABLE_BRANCH
+   3   B        2      1    1     5     -1
+   4   C        2      0    INF   5     -1    ABSORBABLE | ABSORBABLE_BRANCH
+   5   END      0      1    INF   6     1     EMPTY_LOOP
+   6   D        0      1    1     7     -1
+   7   FIN      0      1    1     -1    -1
+
+  ALT branches are checked independently for absorbability.
+  Branch 1: A+ matches Case 1 -> A gets ABSORBABLE. B has no flag.
+  Branch 2: C* matches Case 1 -> C gets ABSORBABLE.
+  Both A and C get ABSORBABLE_BRANCH as part of their respective branch
+  paths.
+  END has EMPTY_LOOP: branch 2 (C*) is nullable, making the group body
+  nullable.
+  BEGIN and ALT get ABSORBABLE_BRANCH (on the path to absorbable elements).
+
+
+References:
+
+[1] ISO/IEC 19075-5 Information technology - Guidance for the use of
+    database language SQL - Part 5: Row pattern recognition
+
+[2] ISO/IEC 9075-2 Information technology - Database languages - SQL -
+    Part 2: Foundation (SQL/Foundation)
+
+============================================================================
+  End of document
+============================================================================
-- 
2.43.0