nocfbot-0034-Correct-stale-RPR-comments-and-document-a-defensi.txt
text/plain
Filename: nocfbot-0034-Correct-stale-RPR-comments-and-document-a-defensi.txt
Type: text/plain
Part: 33
Message:
Re: Row pattern recognition
From 56045a8a856e0c9be20562ea71530010b5941dba Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Mon, 1 Jun 2026 13:45:48 +0900
Subject: [PATCH 34/38] Correct stale RPR comments and document a defensive
window check
Static analysis surfaced several comments that no longer match the code:
- plannodes.h: rewrite the RPRPattern.isAbsorbable comment to match the
implemented cases (isUnboundedStart / computeAbsorbability) and the
normalization that enables absorption; the old text wrongly called the
prefix/suffix merge unimplemented
- README.rpr: an unmatched row's reduced frame is empty, not "the row
itself"; the varId range is 0-250, not 0-251
- advanced.sgml: unmatched rows show NULL window functions and
initial-value aggregates (count() = 0), not all-NULL
- planner.c: note that the RPR fields in optimize_window_clauses' duplicate
check are reached but defensive -- RPR clauses are separated by their
frame options first -- kept for parity with transformWindowFuncCall
---
doc/src/sgml/advanced.sgml | 5 +++--
src/backend/executor/README.rpr | 6 +++---
src/backend/optimizer/plan/planner.c | 5 ++++-
src/include/nodes/plannodes.h | 21 ++++++++++++++-------
4 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 11c2416df51..1410a443609 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -600,8 +600,9 @@ DEFINE
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, columns are shown
- as NULL too. Example of a <literal>SELECT</literal> using
+ 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.
diff --git a/src/backend/executor/README.rpr b/src/backend/executor/README.rpr
index 449bf051153..08418588114 100644
--- a/src/backend/executor/README.rpr
+++ b/src/backend/executor/README.rpr
@@ -245,7 +245,7 @@ RPRPatternElement struct (16 bytes):
Field Size Description
---------------------------------------------------------
- varId 1B Variable ID (0-251) or control code (252-255)
+ varId 1B Variable ID (0-250) or control code (252-255)
depth 1B Group nesting depth
flags 1B Bit flags (see below)
reserved 1B Padding
@@ -1303,11 +1303,11 @@ XI-4. Execution Trace
XI-5. Final Result
- Row 0: unmatched -> frame = the row itself
+ 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 -> frame = the row itself
+ Row 4: unmatched -> reduced frame empty (window funcs NULL, count() 0)
Chapter XII Summary of Key Design Decisions
============================================================================
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index c6fc868cdca..f43cc0edb37 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6254,7 +6254,10 @@ optimize_window_clauses(PlannerInfo *root, WindowFuncLists *wflists)
/*
* Perform the same duplicate check that is done in
- * transformWindowFuncCall.
+ * transformWindowFuncCall. wc is never an RPR clause here
+ * (those are skipped above), and an RPR existing_wc differs
+ * in its frame options anyway, so the RPR-related comparisons
+ * are a defensive backstop for parity.
*/
if (equal(wc->partitionClause, existing_wc->partitionClause) &&
equal(wc->orderClause, existing_wc->orderClause) &&
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fd4bdf2cb31..d45d93d79a2 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -1296,15 +1296,22 @@ typedef struct RPRPattern
* Context absorption optimization.
*
* Absorption is only safe when later matches are guaranteed to be
- * suffixes of earlier matches. This requires simple pattern structure:
+ * suffixes of earlier matches, which requires the pattern to start with
+ * an unbounded greedy element. Phase-1 normalization (consecutive
+ * variable / group / ALT merging and prefix/suffix merging) rewrites the
+ * pattern toward that form first -- so e.g. A B (A B)+ is merged to
+ * (A B){2,} and then judged absorbable.
*
- * Case 1: No ALT, single unbounded element (A+, (A B)+)
- * Case 2: Top-level ALT with each branch being single unbounded (A+ | B+)
+ * computeAbsorbability() marks the absorbable cases (see isUnboundedStart):
+ * - simple unbounded VAR at the start: A+ B C
+ * - unbounded GROUP with fixed-length children: (A B)+, (A B{2})+
+ * - top-level ALT with independently absorbable branches: A+ | B+
+ * (handled in computeAbsorbabilityRecursive)
*
- * Complex patterns like A B (A B)+ could theoretically be transformed to
- * (A B){2,} for absorption, but this changes lexical order and is not
- * implemented. Similarly, (A|B)+ cannot be absorbed because different
- * start positions produce different match contents (not suffix relation).
+ * Not absorbable: an unbounded element not at the start (A B+), a
+ * reluctant quantifier (A+?), or an ALT inside a group ((A|B)+) -- there
+ * different start positions yield different match contents, so later
+ * matches are not suffixes of earlier ones.
*/
bool isAbsorbable; /* true if pattern supports context absorption */
} RPRPattern;
--
2.50.1 (Apple Git-155)