Re: Row pattern recognition

Henson Choi <assam258@gmail.com>

From: Henson Choi <assam258@gmail.com>
To: Tatsuo Ishii <ishii@postgresql.org>
Cc: zsolt.parragi@percona.com, vik@postgresfriends.org, er@xs4all.nl, jacob.champion@enterprisedb.com, david.g.johnston@gmail.com, peter@eisentraut.org, pgsql-hackers@postgresql.org
Date: 2026-03-16T05:39:47Z
Lists: pgsql-hackers
Hi Tatsuo,

Thank you for the careful review and excellent questions.

I was not able to find the definition for EEOP_GT. Is this a new one?
>

You're right -- EEOP_GT does not exist. I oversimplified the example.

In PostgreSQL, comparison operators like `>` are not dedicated opcodes.
They are dispatched through EEOP_FUNCEXPR variants as regular function
calls. For example, `price > PREV(price)`:

  - The parser resolves `>` to the appropriate pg_operator entry
  - The expression compiler emits an EEOP_FUNCEXPR variant
  - At runtime, op->d.func.fn_addr points to the operator function,

I am afraid ExecEvalExpr does not allow this because ExecEvalExpr does
> not accept arguments like WindowObject or WindowAggState that are
> necessary for accessing the tuplestore created for the Window
> aggregate node.


This is the key question. You are right that ExecEvalExpr's signature
does not accept WindowAggState, and I do not intend to change it.

The approach is to pass WindowAggState through the step's own payload,
the same way EEOP_WINDOW_FUNC carries WindowFuncExprState in
op->d.window_func.wfstate. Each ExprEvalStep has a union of per-opcode
payload structs. A new payload for RPR navigation would look like:

    struct
    {
        WindowAggState *winstate;   /* access to tuplestore */
        int64       offset;         /* signed offset: PREV=-1, NEXT=+1 */
    }           rpr_nav;

The expression compiler (ExecInitExprRec) populates this when it
encounters a PREV/NEXT node, storing the pointer to the enclosing
WindowAggState. At runtime, EEOP_RPR_NAV_SET retrieves it from the
payload and uses it to fetch the target row:

    /* pseudo-code for EEOP_RPR_NAV_SET */
    winstate = op->d.rpr_nav.winstate;
    /* 1. save current slot into winstate for later restore */
    winstate->saved_outertuple = econtext->ecxt_outertuple;
    /* 2. compute target position */
    target_pos = winstate->currentpos + op->d.rpr_nav.offset;
    /* 3. fetch target row from tuplestore via winstate */
    target_slot = fetch_row_from_tuplestore(winstate, target_pos);
    /* 4. swap */
    econtext->ecxt_outertuple = target_slot;

    /* pseudo-code for EEOP_RPR_NAV_RESTORE */
    winstate = op->d.rpr_nav.winstate;
    econtext->ecxt_outertuple = winstate->saved_outertuple;

The key difference from EEOP_WINDOW_FUNC: existing steps read
precomputed values, but PREV/NEXT requires fetching a *different*
row at runtime -- because the target depends on the current row
position during pattern matching, which is not known in advance.
The mechanism for passing state is the same (step payload), but
the runtime behavior is a departure from the existing pattern.

I have examined the alternatives carefully:

  - The precomputation approach (EEOP_WINDOW_FUNC style) does not apply
    here: there is no fixed set of values to precompute, because the
    target row depends on the current position during pattern matching.

  - The three-slot model (current implementation) cannot support variable
    offsets like PREV(price, 3), and requires varno rewriting that adds
    complexity elsewhere.

  - A callback-based approach (registering a fetch function in econtext)
    would still require passing WindowAggState somehow, just through a
    different indirection.

The step payload approach is the only path I have found that:

  - Leaves ExecEvalExpr's signature unchanged
  - Eliminates varno rewriting entirely
  - Supports variable offsets naturally
  - Extends to FIRST/LAST navigation later

If anyone sees a cleaner path, I would genuinely welcome the
discussion.

I plan to have an experimental implementation ready before next week.
The initial version will pass WindowAggState broadly; we can
narrow the interface to the minimum required during review.
Let's continue the discussion with actual code.

Best regards,
Henson

Commits

Same data as JSON: GET /api/v1/messages/:b64id/commits the thread's linked commits as JSON, with link sources. API reference →
  1. Adjust cross-version upgrade tests for seg_out() fix

  2. Rationalize error comments in partition split/merge tests

  3. Add fast path for foreign key constraint checks

  4. Fix assorted pretty-trivial memory leaks in the backend.

  5. Add temporal FOREIGN KEY contraints

  6. Add trailing commas to enum definitions

  7. Remove obsolete executor cleanup code