Hi Tatsuo,
Thank you very much for taking the time to start the review. I will reply
in step, one patch at a time, from 0001 up to wherever your review has
reached -- so far that is 0001..0004 -- and I will keep following along as
you continue.
For convenience I have attached just the four patches you have reviewed,
keeping their original v50 names but renamed to nocfbot-* so that cfbot does
not pick them up:
nocfbot-0001 Remove blank-line changes unrelated to row pattern
recognition
nocfbot-0002 Remove unnecessary includes from the row pattern
recognition patch
nocfbot-0003 Recognize row pattern navigation operations by name in
DEFINE
nocfbot-0004 Use a dedicated ExprContext for RPR DEFINE clause evaluation
0001, 0002 and 0004 are unchanged from what you reviewed; I include them
only so this reply stands on its own for the reviewed range. 0003 folds in
the three points you raised below.
> 0001 and 0002 look good to me.
Thank you for confirming -- I have left them as they are.
>> v50-0003 Recognize row pattern navigation operations by name in DEFINE
>
> + if (argnames != NIL)
> + ereport(ERROR,
> + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
> + errmsg("row pattern navigation operations cannot use named arguments"),
> + parser_errposition(pstate, location)));
>
> Shouldn't the error code for the named arguments case be
> ERRCODE_SYNTAX_ERROR? As far as I know the SQL standard does not allow
> to use named arguments with row pattern navigation operations. So it's
> not a required feature.
You are right, thank you. The standard does not provide for named arguments
here at all, so this is a syntax violation rather than an unimplemented
feature. I have changed the errcode to ERRCODE_SYNTAX_ERROR, which also
makes it consistent with the VARIADIC and argument-count checks alongside
it.
> Also errmsg is different from surroundings. Probably "cannot use named
> arguments with row pattern navigation function %s" is better?
Thank you for the suggested wording; I have adopted it as is:
errmsg("cannot use named arguments with row pattern navigation function
%s",
navname)
It now reads in the same "cannot use X with row pattern navigation
function %s" shape as the VARIADIC message just above it. I have also
updated the expected output in rpr.sql to match (ERROR: cannot use named
arguments with row pattern navigation function PREV).
> + * * offset_arg / compound_offset_arg must not contain column refs
> + * or nested navigation operations
>
> Needs '*' in front of "or nested....".
Thank you for catching this. The last line is a continuation, but the '*'
bullets ran together with the comment's own '*' and hid that. I switched
the bullets to '-' to match the sibling rule list in this file, so the
continuation now reads clearly:
* ... enforces, for each outer RPRNavExpr (per ISO/IEC 19075-5 5.6.4
* nesting rules):
* - arg must contain at least one column reference
* - PREV/NEXT wrapping FIRST/LAST flattens to a compound kind
* - Other nestings are rejected (FIRST(PREV()), PREV(PREV()), ...)
* - offset_arg / compound_offset_arg must not contain column refs
* or nested navigation operations
I hope this addresses what you had in mind; please let me know if you would
prefer a different form.
>> v50-0004 Use a dedicated ExprContext for RPR DEFINE clause evaluation
>
> Looks good to me.
Thank you for confirming -- left as is.
> Will continue reviewes tomorrow.
Thank you, I look forward to it. I will respond to each further patch as
your review reaches it, in the same one-at-a-time fashion.
Best regards,
Henson
From b9a26b03d3034449512db22a407431ef0ea30557 Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Tue, 16 Jun 2026 23:26:16 +0900
Subject: [PATCH nocfbot 1/4] Remove blank-line changes unrelated to row
pattern recognition
The RPR patch added or dropped blank lines in five existing files with no
related code change. Revert them so the files differ from the base only
where RPR actually touches them.
---
src/backend/executor/nodeWindowAgg.c | 2 --
src/backend/optimizer/plan/setrefs.c | 1 +
src/backend/parser/parse_clause.c | 1 +
src/backend/utils/adt/ruleutils.c | 1 -
src/backend/utils/adt/windowfuncs.c | 1 +
5 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/executor/nodeWindowAgg.c
b/src/backend/executor/nodeWindowAgg.c
index cb6a484b7de..5b2385f1d8d 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -177,7 +177,6 @@ typedef struct WindowStatePerAggData
bool restart; /* need to restart this agg in
this cycle? */
} WindowStatePerAggData;
-
static void initialize_windowaggregate(WindowAggState *winstate,
WindowStatePerFunc perfuncstate,
WindowStatePerAgg peraggstate);
@@ -1086,7 +1085,6 @@ next_tuple:
ExecClearTuple(agg_row_slot);
}
-
/* The frame's end is not supposed to move backwards, ever */
Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
diff --git a/src/backend/optimizer/plan/setrefs.c
b/src/backend/optimizer/plan/setrefs.c
index 813a326bd78..6e4f3fd61e2 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -214,6 +214,7 @@ static List
*set_windowagg_runcondition_references(PlannerInfo *root,
static void record_elided_node(PlannerGlobal *glob, int plan_node_id,
NodeTag elided_type,
Bitmapset *relids);
+
/*****************************************************************************
*
* SUBPLAN REFERENCES
diff --git a/src/backend/parser/parse_clause.c
b/src/backend/parser/parse_clause.c
index 550ea4eb9c0..8eb367aa579 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -102,6 +102,7 @@ static Node *transformFrameOffset(ParseState *pstate, int
frameOptions,
Oid
rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
Node *clause);
+
/*
* transformFromClause -
* Process the FROM clause and add items to the query's range table,
diff --git a/src/backend/utils/adt/ruleutils.c
b/src/backend/utils/adt/ruleutils.c
index 415da6417d4..4eb7e35bee4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7311,7 +7311,6 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
get_rule_orderby(wc->orderClause, targetList, false, context);
needspace = true;
}
-
/* framing clause is never inherited, so print unless it's default */
if (wc->frameOptions & FRAMEOPTION_NONDEFAULT)
{
diff --git a/src/backend/utils/adt/windowfuncs.c
b/src/backend/utils/adt/windowfuncs.c
index 3869f6c8994..d15aa0c75db 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -41,6 +41,7 @@ static bool rank_up(WindowObject winobj);
static Datum leadlag_common(FunctionCallInfo fcinfo,
bool forward, bool
withoffset, bool withdefault);
+
/*
* utility routine for *_rank functions.
*/
--
2.50.1 (Apple Git-155)
From 4a572e7fbe0207ddd26fbbde02e1f8d9bfd68ddc Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Wed, 17 Jun 2026 08:16:57 +0900
Subject: [PATCH nocfbot 2/4] Remove unnecessary includes from the row pattern
recognition patch
Drop includes that are unused or covered by existing forward typedefs:
condition_variable.h, hsearch.h, queryenvironment.h from execnodes.h;
<limits.h> from rpr.c; windowapi.h from execRPR.h. Switch rpr.h from
parsenodes.h to primnodes.h, where RPRNavExpr is actually defined.
---
src/backend/optimizer/plan/rpr.c | 2 --
src/include/executor/execRPR.h | 1 -
src/include/nodes/execnodes.h | 3 ---
src/include/optimizer/rpr.h | 7 ++++---
4 files changed, 4 insertions(+), 9 deletions(-)
diff --git a/src/backend/optimizer/plan/rpr.c b/src/backend/optimizer/plan/rpr.c
index 175777a8ffc..50bca59451f 100644
--- a/src/backend/optimizer/plan/rpr.c
+++ b/src/backend/optimizer/plan/rpr.c
@@ -37,8 +37,6 @@
#include "postgres.h"
-#include <limits.h>
-
#include "catalog/pg_proc.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
diff --git a/src/include/executor/execRPR.h b/src/include/executor/execRPR.h
index 7b2b0febb76..fb7dc63a4c6 100644
--- a/src/include/executor/execRPR.h
+++ b/src/include/executor/execRPR.h
@@ -15,7 +15,6 @@
#define EXECRPR_H
#include "nodes/execnodes.h"
-#include "windowapi.h"
/* NFA context management */
extern RPRNFAContext *ExecRPRStartContext(WindowAggState *winstate,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 010fc262b25..b587c3dc15b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -38,9 +38,6 @@
#include "nodes/plannodes.h"
#include "partitioning/partdefs.h"
#include "storage/buf.h"
-#include "storage/condition_variable.h"
-#include "utils/hsearch.h"
-#include "utils/queryenvironment.h"
#include "utils/reltrigger.h"
#include "utils/typcache.h"
diff --git a/src/include/optimizer/rpr.h b/src/include/optimizer/rpr.h
index 802d2f1dd69..b4f87d8caa4 100644
--- a/src/include/optimizer/rpr.h
+++ b/src/include/optimizer/rpr.h
@@ -10,11 +10,12 @@
*
*-------------------------------------------------------------------------
*/
-#ifndef OPTIMIZER_RPR_H
-#define OPTIMIZER_RPR_H
+#ifndef RPR_H
+#define RPR_H
#include "nodes/parsenodes.h"
#include "nodes/plannodes.h"
+#include "nodes/primnodes.h"
/* Limits and special values */
/*
@@ -96,4 +97,4 @@ typedef struct NavTraversal
extern bool nav_traversal_walker(Node *node, void *ctx);
-#endif /* OPTIMIZER_RPR_H */
+#endif /* RPR_H */
--
2.50.1 (Apple Git-155)
From 0570f2e023e78ac3a229b6d9077d026b8b16be17 Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Mon, 15 Jun 2026 15:29:59 +0900
Subject: [PATCH nocfbot 3/4] Recognize row pattern navigation operations by
name in DEFINE
PREV, NEXT, FIRST, and LAST were placeholder functions in pg_proc that
polluted the ordinary function namespace and could be silently misbound to
same-named user functions. Recognize them by name inside a DEFINE clause
and drop the placeholders; an unqualified call is always navigation, while
a schema-qualified call still reaches an ordinary function. Also reject a
navigation operation inside a navigation offset, which must be a run-time
constant and previously could crash the planner.
Recognition happens in two steps in ParseFuncOrColumn: note the matched
name up front, skip the catalog lookup by treating it as FUNCDETAIL_NORMAL,
let the common decoration and wrong-kind-of-routine checks run, and only
then route to ParseRPRNavCall to build the RPRNavExpr. ParseRPRNavCall
therefore does not duplicate the aggregate/window decoration checks
(agg_star, DISTINCT, WITHIN GROUP, ORDER BY, FILTER, OVER); the common path
performs them with identical messages.
Document this in func-window.sgml.
---
doc/src/sgml/func/func-window.sgml | 6 +
src/backend/parser/parse_func.c | 297 ++++++++++-----
src/backend/parser/parse_rpr.c | 27 +-
src/backend/utils/adt/ruleutils.c | 55 ++-
src/backend/utils/adt/windowfuncs.c | 118 ------
src/include/catalog/pg_proc.dat | 24 --
src/test/regress/expected/rpr.out | 32 --
src/test/regress/expected/rpr_base.out | 492 ++++++++++++++++++++++++-
src/test/regress/sql/rpr.sql | 15 -
src/test/regress/sql/rpr_base.sql | 251 +++++++++++++
10 files changed, 1014 insertions(+), 303 deletions(-)
diff --git a/doc/src/sgml/func/func-window.sgml
b/doc/src/sgml/func/func-window.sgml
index ab469b56fd7..1079b6abb6e 100644
--- a/doc/src/sgml/func/func-window.sgml
+++ b/doc/src/sgml/func/func-window.sgml
@@ -282,6 +282,10 @@ IGNORE NULLS
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.
+ The names <function>PREV</function>, <function>NEXT</function>,
+ <function>FIRST</function>, and <function>LAST</function> are
+ recognized as navigation functions only in an unqualified call; a
+ schema-qualified call is resolved as an ordinary function instead.
</para>
<table id="functions-rpr-navigation-table">
@@ -397,6 +401,8 @@ IGNORE NULLS
permitted. Same-category nesting (e.g.,
<function>PREV</function> inside <function>PREV</function>) is also
prohibited.
+ The <parameter>offset</parameter> argument must be a run-time constant:
+ it cannot reference columns or contain a navigation operation.
</para>
<note>
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index fe643641f98..b7c0e312ffe 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -31,7 +31,6 @@
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
-#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -49,6 +48,9 @@ static void unify_hypothetical_args(ParseState *pstate,
List
*fargs, int numAggregatedArgs,
Oid
*actual_arg_types, Oid *declared_arg_types);
static Oid FuncNameAsType(List *funcname);
+static Node *ParseRPRNavCall(ParseState *pstate, List *funcname,
+ List *fargs, List
*argnames, FuncCall *fn,
+ int location);
static Node *ParseComplexProjection(ParseState *pstate, const char *funcname,
Node
*first_arg, int location);
static Oid LookupFuncNameInternal(ObjectType objtype, List *funcname,
@@ -122,6 +124,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List
*fargs,
int fgc_flags;
char aggkind = 0;
ParseCallbackState pcbstate;
+ bool could_be_rpr_nav = false;
/*
* If there's an aggregate filter, transform it using
transformWhereClause
@@ -218,6 +221,28 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List
*fargs,
Assert(first_arg != NULL);
}
+ /*
+ * Inside an RPR DEFINE clause, an unqualified call to one of the row
+ * pattern navigation names PREV/NEXT/FIRST/LAST denotes the navigation
+ * operation, not an ordinary function. Just note that here; the
catalog
+ * lookup is skipped and the RPRNavExpr is built at the end, after the
+ * common decoration checks have run (see the could_be_rpr_nav handling
+ * below). A schema-qualified call is the explicit way to reach an
+ * ordinary function of one of these names.
+ */
+ if (!is_column && !proc_call &&
+ pstate->p_expr_kind == EXPR_KIND_RPR_DEFINE &&
+ list_length(funcname) == 1)
+ {
+ const char *name = strVal(linitial(funcname));
+
+ if (strcmp(name, "prev") == 0 ||
+ strcmp(name, "next") == 0 ||
+ strcmp(name, "first") == 0 ||
+ strcmp(name, "last") == 0)
+ could_be_rpr_nav = true;
+ }
+
/*
* Decide whether it's legitimate to consider the construct to be a
column
* projection. For that, there has to be a single argument of complex
@@ -266,17 +291,32 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname,
List *fargs,
* with default arguments.
*/
- setup_parser_errposition_callback(&pcbstate, pstate, location);
+ if (!could_be_rpr_nav)
+ {
+ setup_parser_errposition_callback(&pcbstate, pstate, location);
- fdresult = func_get_detail(funcname, fargs, argnames, nargs,
- actual_arg_types,
- !func_variadic,
true, proc_call,
- &fgc_flags,
- &funcid, &rettype,
&retset,
- &nvargs, &vatype,
- &declared_arg_types,
&argdefaults);
+ fdresult = func_get_detail(funcname, fargs, argnames, nargs,
+
actual_arg_types,
+
!func_variadic, true, proc_call,
+ &fgc_flags,
+ &funcid,
&rettype, &retset,
+ &nvargs,
&vatype,
+
&declared_arg_types, &argdefaults);
- cancel_parser_errposition_callback(&pcbstate);
+ cancel_parser_errposition_callback(&pcbstate);
+ }
+ else
+ {
+ /*
+ * A recognized navigation name skips catalog lookup entirely.
Treat
+ * it as an ordinary function so the common
wrong-kind-of-routine and
+ * decoration checks below run with the existing messages, then
route
+ * to ParseRPRNavCall to build the RPRNavExpr.
+ */
+ Assert(!proc_call);
+
+ fdresult = FUNCDETAIL_NORMAL;
+ }
/*
* Check for various wrong-kind-of-routine cases.
@@ -658,6 +698,15 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List
*fargs,
parser_errposition(pstate, location)));
}
+ /*
+ * A recognized navigation name has now passed the common decoration and
+ * wrong-kind checks above; build the RPRNavExpr. No fallback to
function
+ * resolution ever happens here.
+ */
+ if (could_be_rpr_nav)
+ return ParseRPRNavCall(pstate, funcname, fargs, argnames, fn,
+ location);
+
/*
* If there are default arguments, we have to include their types in
* actual_arg_types for the purpose of checking generic type
consistency.
@@ -764,88 +813,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List
*fargs,
if (retset)
check_srf_call_placement(pstate, last_srf, location);
- /*
- * RPR navigation functions (PREV/NEXT/FIRST/LAST) are only meaningful
- * inside a WINDOW DEFINE clause.
- *
- * Outside DEFINE, these polymorphic placeholders can shadow column
access
- * via functional notation (e.g., last(f) meaning f.last). For the 1-arg
- * form, try column projection first; if that succeeds, use it instead.
- * Otherwise, report a clear parser error.
- */
- if (fdresult == FUNCDETAIL_NORMAL &&
- pstate->p_expr_kind != EXPR_KIND_RPR_DEFINE &&
- (funcid == F_PREV_ANYELEMENT || funcid == F_NEXT_ANYELEMENT ||
- funcid == F_PREV_ANYELEMENT_INT8 || funcid ==
F_NEXT_ANYELEMENT_INT8 ||
- funcid == F_FIRST_ANYELEMENT || funcid == F_LAST_ANYELEMENT ||
- funcid == F_FIRST_ANYELEMENT_INT8 || funcid ==
F_LAST_ANYELEMENT_INT8))
- {
- /* 1-arg form: try column projection before erroring out */
- if (nargs == 1 && !agg_star && !agg_distinct && over == NULL &&
- list_length(funcname) == 1)
- {
- Node *projection;
-
- projection = ParseComplexProjection(pstate,
-
strVal(linitial(funcname)),
-
linitial(fargs),
-
location);
- if (projection)
- return projection;
- }
-
- /* Not a column projection -- report error */
- ereport(ERROR,
- errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("cannot use %s outside a DEFINE clause",
- NameListToString(funcname)),
- parser_errposition(pstate, location));
- }
-
/* build the appropriate output structure */
- if (fdresult == FUNCDETAIL_NORMAL &&
- pstate->p_expr_kind == EXPR_KIND_RPR_DEFINE &&
- (funcid == F_PREV_ANYELEMENT || funcid == F_NEXT_ANYELEMENT ||
- funcid == F_PREV_ANYELEMENT_INT8 || funcid ==
F_NEXT_ANYELEMENT_INT8 ||
- funcid == F_FIRST_ANYELEMENT || funcid == F_LAST_ANYELEMENT ||
- funcid == F_FIRST_ANYELEMENT_INT8 || funcid ==
F_LAST_ANYELEMENT_INT8))
- {
- /*
- * RPR navigation functions (PREV/NEXT/FIRST/LAST) are compiled
into
- * EEOP_RPR_NAV_SET / EEOP_RPR_NAV_RESTORE opcodes instead of a
normal
- * function call. Represent them as RPRNavExpr nodes so that
later
- * stages can identify them without relying on funcid
comparisons.
- */
- RPRNavKind kind;
- bool has_offset;
- RPRNavExpr *navexpr;
-
- if (funcid == F_PREV_ANYELEMENT || funcid ==
F_PREV_ANYELEMENT_INT8)
- kind = RPR_NAV_PREV;
- else if (funcid == F_NEXT_ANYELEMENT || funcid ==
F_NEXT_ANYELEMENT_INT8)
- kind = RPR_NAV_NEXT;
- else if (funcid == F_FIRST_ANYELEMENT || funcid ==
F_FIRST_ANYELEMENT_INT8)
- kind = RPR_NAV_FIRST;
- else
- kind = RPR_NAV_LAST;
-
- has_offset = (funcid == F_PREV_ANYELEMENT_INT8 ||
- funcid == F_NEXT_ANYELEMENT_INT8 ||
- funcid == F_FIRST_ANYELEMENT_INT8 ||
- funcid == F_LAST_ANYELEMENT_INT8);
-
- navexpr = makeNode(RPRNavExpr);
-
- navexpr->kind = kind;
- navexpr->arg = (Expr *) linitial(fargs);
- navexpr->offset_arg = has_offset ? (Expr *) lsecond(fargs) :
NULL;
- navexpr->resulttype = rettype;
- /* resultcollid will be set by parse_collate.c */
- navexpr->location = location;
-
- retval = (Node *) navexpr;
- }
- else if (fdresult == FUNCDETAIL_NORMAL || fdresult ==
FUNCDETAIL_PROCEDURE)
+ if (fdresult == FUNCDETAIL_NORMAL || fdresult == FUNCDETAIL_PROCEDURE)
{
FuncExpr *funcexpr = makeNode(FuncExpr);
@@ -2116,6 +2085,152 @@ FuncNameAsType(List *funcname)
return result;
}
+/*
+ * ParseRPRNavCall
+ * Recognize a row pattern navigation operation in a DEFINE clause.
+ *
+ * Inside an EXPR_KIND_RPR_DEFINE clause an unqualified call to one of the
+ * names PREV/NEXT/FIRST/LAST denotes the corresponding row pattern navigation
+ * operation (ISO/IEC 19075-5 Subclause 5.6), not an ordinary function call.
+ * The name is matched here, before any catalog lookup, with no fallback to
+ * function resolution: once it matches, decoration and argument-count
+ * violations are dedicated errors rather than letting an ordinary function of
+ * the same name take over. A schema-qualified call (the caller restricts us
+ * to unqualified names) is the documented way to reach such a function
+ * instead.
+ *
+ * The caller routes here only after the name has matched one of the four
+ * navigation names and the common decoration/wrong-kind checks in
+ * ParseFuncOrColumn have run, so this always returns an RPRNavExpr.
+ */
+static Node *
+ParseRPRNavCall(ParseState *pstate, List *funcname, List *fargs,
+ List *argnames, FuncCall *fn, int location)
+{
+ const char *name = strVal(linitial(funcname));
+ RPRNavKind kind;
+ const char *navname;
+ int nargs = list_length(fargs);
+ Node *arg;
+ RPRNavExpr *navexpr;
+
+ /* match the parser-downcased identifier; otherwise not a navigation
name */
+ if (strcmp(name, "prev") == 0)
+ {
+ kind = RPR_NAV_PREV;
+ navname = "PREV";
+ }
+ else if (strcmp(name, "next") == 0)
+ {
+ kind = RPR_NAV_NEXT;
+ navname = "NEXT";
+ }
+ else if (strcmp(name, "first") == 0)
+ {
+ kind = RPR_NAV_FIRST;
+ navname = "FIRST";
+ }
+ else if (strcmp(name, "last") == 0)
+ {
+ kind = RPR_NAV_LAST;
+ navname = "LAST";
+ }
+ else
+ {
+ /* the caller only routes here after matching one of the four
names */
+ pg_unreachable();
+ return NULL;
+ }
+
+ /*
+ * Once the name matches we never fall back to function resolution, so
any
+ * decoration that does not make sense for a navigation operation is a
+ * hard error. The aggregate/window decorations (agg_star, DISTINCT,
+ * WITHIN GROUP, ORDER BY, FILTER, OVER, RESPECT/IGNORE NULLS) are
already
+ * rejected by the common path in ParseFuncOrColumn, which treated the
+ * recognized name as an ordinary function; what remains are the
+ * decorations that path accepts for a plain function but a navigation
+ * operation must still reject.
+ */
+ if (fn->func_variadic)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot use VARIADIC with row pattern
navigation function %s",
+ navname),
+ parser_errposition(pstate, location)));
+ if (argnames != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot use named arguments with row
pattern navigation function %s",
+ navname),
+ parser_errposition(pstate, location)));
+ /* takes a value expression and an optional offset */
+ if (nargs == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("too few arguments for row pattern
navigation function %s",
+ navname),
+ errdetail("%s takes a value expression and an
optional offset argument.",
+ navname),
+ parser_errposition(pstate, location)));
+ if (nargs > 2)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("too many arguments for row pattern
navigation function %s",
+ navname),
+ errdetail("%s takes a value expression and an
optional offset argument.",
+ navname),
+ parser_errposition(pstate, location)));
+
+ /*
+ * Resolve a still-unknown first argument to text, the same way the
+ * anycompatible family does. A navigation operation is not a
polymorphic
+ * function, so the old "could not determine polymorphic type" error
does
+ * not apply; an unknown literal cannot contain a column reference, so
the
+ * walker still rejects it later.
+ */
+ arg = linitial(fargs);
+ if (exprType(arg) == UNKNOWNOID)
+ arg = coerce_to_common_type(pstate, arg, TEXTOID, navname);
+
+ navexpr = makeNode(RPRNavExpr);
+ navexpr->kind = kind;
+ navexpr->arg = (Expr *) arg;
+
+ /* an explicit offset is coerced to int8, which the executor reads */
+ if (nargs == 2)
+ {
+ Node *offset = lsecond(fargs);
+ Oid offtype = exprType(offset);
+
+ if (offtype != INT8OID)
+ {
+ Node *newoffset;
+
+ newoffset = coerce_to_target_type(pstate, offset,
offtype,
+
INT8OID, -1, COERCION_IMPLICIT,
+
COERCE_IMPLICIT_CAST, -1);
+ if (newoffset == NULL)
+ ereport(ERROR,
+
(errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("offset argument of %s
must be type %s, not type %s",
+ navname,
"bigint", format_type_be(offtype)),
+ parser_errposition(pstate,
exprLocation(offset))));
+ offset = newoffset;
+ }
+ navexpr->offset_arg = (Expr *) offset;
+ }
+ else
+ navexpr->offset_arg = NULL;
+
+ /* compound_offset_arg stays NULL; define_walker flattening fills it in
*/
+ navexpr->resulttype = exprType(arg);
+ /* resultcollid will be set by parse_collate.c */
+ navexpr->location = location;
+
+ return (Node *) navexpr;
+}
+
/*
* ParseComplexProjection -
* handles function calls with a single argument that is of complex type.
diff --git a/src/backend/parser/parse_rpr.c b/src/backend/parser/parse_rpr.c
index 3eaea2be750..c0cc9431451 100644
--- a/src/backend/parser/parse_rpr.c
+++ b/src/backend/parser/parse_rpr.c
@@ -466,12 +466,13 @@ transformDefineClause(ParseState *pstate, WindowClause
*wc, WindowDef *windef,
* Single-pass DEFINE clause validator.
*
* One walker function (define_walker) visits every node in a DEFINE
- * expression exactly once and enforces every rule:
- * - For each outer RPRNavExpr (per ISO/IEC 19075-5 5.6.4 nesting rules):
- * * arg must contain at least one column reference
- * * PREV/NEXT wrapping FIRST/LAST flattens to a compound kind
- * * Other nestings are rejected (FIRST(PREV()), PREV(PREV()), ...)
- * * offset_arg / compound_offset_arg must not contain column refs
+ * expression exactly once and enforces, for each outer RPRNavExpr (per
+ * ISO/IEC 19075-5 5.6.4 nesting rules):
+ * - arg must contain at least one column reference
+ * - PREV/NEXT wrapping FIRST/LAST flattens to a compound kind
+ * - Other nestings are rejected (FIRST(PREV()), PREV(PREV()), ...)
+ * - offset_arg / compound_offset_arg must not contain column refs
+ * or nested navigation operations
*
* Volatile callees (and sequence operations) are rejected later in the
* planner via validate_rpr_define_volatility(); see optimizer/plan/rpr.c.
@@ -482,7 +483,7 @@ transformDefineClause(ParseState *pstate, WindowClause *wc,
WindowDef *windef,
* walks nav.arg in PHASE_NAV_ARG to collect nesting/column-ref state,
* applies compound flatten or raises a nesting error, then walks the
* (post-flatten) offset(s) in PHASE_NAV_OFFSET to enforce the
- * constant-offset rule. No subtree is walked twice.
+ * constant-offset and no-nested-nav rules. No subtree is walked twice.
*/
/*
@@ -498,6 +499,7 @@ transformDefineClause(ParseState *pstate, WindowClause *wc,
WindowDef *windef,
* PREV(PREV()), FIRST(FIRST()), three-or-more
deep)
* [2] for each nav offset (PHASE_NAV_OFFSET):
* - must be a run-time constant (no column references)
+ * - must not contain a row pattern navigation operation
*
* Var sightings feed the column-ref rule for the enclosing nav scope;
* RPRNavExpr sightings inside PHASE_NAV_ARG feed the nesting decision.
@@ -538,11 +540,14 @@ define_walker(Node *node, void *context)
if (ctx->phase == DEFINE_PHASE_NAV_OFFSET)
{
/*
- * Navs inside offset_arg are unusual but not directly
banned; the
- * constant-offset rule will catch any Var or volatile
they
- * contain.
+ * A navigation offset must be a run-time constant, so
it cannot
+ * contain a navigation operation.
*/
- return expression_tree_walker(node, define_walker, ctx);
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("row pattern navigation offset
cannot contain a row pattern navigation operation"),
+ errdetail("A navigation offset must be
a run-time constant."),
+ parser_errposition(ctx->pstate,
nav->location));
}
/*
diff --git a/src/backend/utils/adt/ruleutils.c
b/src/backend/utils/adt/ruleutils.c
index 4eb7e35bee4..2b7fd7367f3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -127,6 +127,7 @@ typedef struct
bool varprefix; /* true to print prefixes on
Vars */
bool colNamesVisible; /* do we care about output
column names? */
bool inGroupBy; /* deparsing GROUP BY clause? */
+ bool inRPRDefine; /* deparsing an RPR DEFINE clause? */
bool varInOrderBy; /* deparsing simple Var in ORDER BY? */
Bitmapset *appendparents; /* if not null, map child Vars of these
relids
* back to the
parent rel */
@@ -545,7 +546,7 @@ static char *generate_qualified_relation_name(Oid relid);
static char *generate_function_name(Oid funcid, int nargs,
List
*argnames, Oid *argtypes,
bool
has_variadic, bool *use_variadic_p,
- bool
inGroupBy);
+ bool
inGroupBy, bool inRPRDefine);
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static void add_cast_to(StringInfo buf, Oid typid);
static char *generate_qualified_type_name(Oid typid);
@@ -1131,6 +1132,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
context.indentLevel = PRETTYINDENT_STD;
context.colNamesVisible = true;
context.inGroupBy = false;
+ context.inRPRDefine = false;
context.varInOrderBy = false;
context.appendparents = NULL;
@@ -1142,7 +1144,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
appendStringInfo(&buf, "EXECUTE FUNCTION %s(",
generate_function_name(trigrec->tgfoid, 0,
NIL, NULL,
-
false, NULL, false));
+
false, NULL, false, false));
if (trigrec->tgnargs > 0)
{
@@ -3401,7 +3403,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendStringInfo(&buf, " SUPPORT %s",
generate_function_name(proc->prosupport, 1,
NIL, argtypes,
-
false, NULL, false));
+
false, NULL, false, false));
}
if (oldlen != buf.len)
@@ -4054,6 +4056,7 @@ deparse_expression_pretty(Node *expr, List *dpcontext,
context.indentLevel = startIndent;
context.colNamesVisible = true;
context.inGroupBy = false;
+ context.inRPRDefine = false;
context.varInOrderBy = false;
context.appendparents = NULL;
@@ -5849,6 +5852,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc
rulettc,
context.indentLevel = PRETTYINDENT_STD;
context.colNamesVisible = true;
context.inGroupBy = false;
+ context.inRPRDefine = false;
context.varInOrderBy = false;
context.appendparents = NULL;
@@ -6041,6 +6045,7 @@ get_query_def(Query *query, StringInfo buf, List
*parentnamespace,
context.indentLevel = startIndent;
context.colNamesVisible = colNamesVisible;
context.inGroupBy = false;
+ context.inRPRDefine = false;
context.varInOrderBy = false;
context.appendparents = NULL;
@@ -7220,15 +7225,25 @@ get_rule_define(List *defineClause, deparse_context
*context)
{
StringInfo buf = context->buf;
const char *sep;
+ bool save_inrprdefine = context->inRPRDefine;
sep = " ";
+ /*
+ * Within the DEFINE clause an unqualified prev/next/first/last is a
+ * navigation operation, so a user function of one of those names must
be
+ * schema-qualified to survive a reparse; see generate_function_name().
+ */
+ context->inRPRDefine = true;
+
foreach_node(TargetEntry, te, defineClause)
{
appendStringInfo(buf, "%s%s AS ", sep,
quote_identifier(te->resname));
get_rule_expr((Node *) te->expr, context, false);
sep = ",\n ";
}
+
+ context->inRPRDefine = save_inrprdefine;
}
/*
@@ -7459,6 +7474,7 @@ get_window_frame_options_for_explain(int frameOptions,
context.indentLevel = 0;
context.colNamesVisible = true;
context.inGroupBy = false;
+ context.inRPRDefine = false;
context.varInOrderBy = false;
context.appendparents = NULL;
@@ -11691,7 +11707,8 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
argnames, argtypes,
expr->funcvariadic,
&use_variadic,
-
context->inGroupBy));
+
context->inGroupBy,
+
context->inRPRDefine));
nargs = 0;
foreach(l, expr->args)
{
@@ -11761,7 +11778,8 @@ get_agg_expr_helper(Aggref *aggref, deparse_context
*context,
funcname = generate_function_name(aggref->aggfnoid, nargs, NIL,
argtypes, aggref->aggvariadic,
&use_variadic,
-
context->inGroupBy);
+
context->inGroupBy,
+
context->inRPRDefine);
/* Print the aggregate name, schema-qualified if needed */
appendStringInfo(buf, "%s(%s", funcname,
@@ -11902,7 +11920,8 @@ get_windowfunc_expr_helper(WindowFunc *wfunc,
deparse_context *context,
if (!funcname)
funcname = generate_function_name(wfunc->winfnoid, nargs,
argnames,
argtypes, false, NULL,
-
context->inGroupBy);
+
context->inGroupBy,
+
context->inRPRDefine);
appendStringInfo(buf, "%s(", funcname);
@@ -13743,7 +13762,7 @@ get_tablesample_def(TableSampleClause *tablesample,
deparse_context *context)
appendStringInfo(buf, " TABLESAMPLE %s (",
generate_function_name(tablesample->tsmhandler, 1,
NIL, argtypes,
-
false, NULL, false));
+
false, NULL, false, false));
nargs = 0;
foreach(l, tablesample->args)
@@ -14157,12 +14176,14 @@ generate_qualified_relation_name(Oid relid)
*
* inGroupBy must be true if we're deparsing a GROUP BY clause.
*
+ * inRPRDefine must be true if we're deparsing an RPR DEFINE clause.
+ *
* The result includes all necessary quoting and schema-prefixing.
*/
static char *
generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
bool has_variadic, bool
*use_variadic_p,
- bool inGroupBy)
+ bool inGroupBy, bool inRPRDefine)
{
char *result;
HeapTuple proctup;
@@ -14196,6 +14217,24 @@ generate_function_name(Oid funcid, int nargs, List
*argnames, Oid *argtypes,
force_qualify = true;
}
+ /*
+ * Inside a row pattern DEFINE clause, the parser binds an unqualified
+ * prev/next/first/last to a navigation operation before any catalog
+ * lookup, so an unqualified call to a user function of one of those
names
+ * would change meaning across a deparse/reparse cycle. Force schema
+ * qualification; the qualified form is the documented escape hatch.
Only
+ * the exact lower-case names are at risk: a mixed-case proname deparses
+ * quoted and cannot match the parser's downcased comparison.
+ */
+ if (inRPRDefine)
+ {
+ if (strcmp(proname, "prev") == 0 ||
+ strcmp(proname, "next") == 0 ||
+ strcmp(proname, "first") == 0 ||
+ strcmp(proname, "last") == 0)
+ force_qualify = true;
+ }
+
/*
* Determine whether VARIADIC should be printed. We must do this first
* since it affects the lookup rules in func_get_detail().
diff --git a/src/backend/utils/adt/windowfuncs.c
b/src/backend/utils/adt/windowfuncs.c
index d15aa0c75db..78b7f05aba2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -724,121 +724,3 @@ window_nth_value(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(result);
}
-
-/*
- * prev
- * Catalog placeholder for RPR's PREV navigation operator.
- *
- * The parser transforms prev() calls inside DEFINE into RPRNavExpr nodes,
- * so this function is never reached during normal RPR execution. It exists
- * only so that the parser can resolve the function name from pg_proc.
- * Calls outside DEFINE are rejected by parse_func.c (EXPR_KIND_RPR_DEFINE
- * check). The error below is a defensive measure in case that check is
- * bypassed (e.g., direct C-level function invocation).
- */
-Datum
-window_prev(PG_FUNCTION_ARGS)
-{
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot use PREV() outside a DEFINE clause"));
- PG_RETURN_NULL(); /* not reached */
-}
-
-/*
- * next
- * Catalog placeholder for RPR's NEXT navigation operator.
- * See window_prev() for details.
- */
-Datum
-window_next(PG_FUNCTION_ARGS)
-{
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot use NEXT() outside a DEFINE clause"));
- PG_RETURN_NULL(); /* not reached */
-}
-
-/*
- * prev(value, offset)
- * Catalog placeholder for RPR's PREV navigation operator with offset.
- * See window_prev() for details.
- */
-Datum
-window_prev_offset(PG_FUNCTION_ARGS)
-{
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot use PREV() outside a DEFINE clause"));
- PG_RETURN_NULL(); /* not reached */
-}
-
-/*
- * next(value, offset)
- * Catalog placeholder for RPR's NEXT navigation operator with offset.
- * See window_prev() for details.
- */
-Datum
-window_next_offset(PG_FUNCTION_ARGS)
-{
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot use NEXT() outside a DEFINE clause"));
- PG_RETURN_NULL(); /* not reached */
-}
-
-/*
- * first
- * Catalog placeholder for RPR's FIRST navigation operator.
- * See window_prev() for details.
- */
-Datum
-window_first(PG_FUNCTION_ARGS)
-{
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot use FIRST() outside a DEFINE clause"));
- PG_RETURN_NULL(); /* not reached */
-}
-
-/*
- * last
- * Catalog placeholder for RPR's LAST navigation operator.
- * See window_prev() for details.
- */
-Datum
-window_last(PG_FUNCTION_ARGS)
-{
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot use LAST() outside a DEFINE clause"));
- PG_RETURN_NULL(); /* not reached */
-}
-
-/*
- * first(value, offset)
- * Catalog placeholder for RPR's FIRST navigation operator with offset.
- * See window_prev() for details.
- */
-Datum
-window_first_offset(PG_FUNCTION_ARGS)
-{
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot use FIRST() outside a DEFINE clause"));
- PG_RETURN_NULL(); /* not reached */
-}
-
-/*
- * last(value, offset)
- * Catalog placeholder for RPR's LAST navigation operator with offset.
- * See window_prev() for details.
- */
-Datum
-window_last_offset(PG_FUNCTION_ARGS)
-{
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot use LAST() outside a DEFINE clause"));
- PG_RETURN_NULL(); /* not reached */
-}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 63ad33b68e2..384ba908d35 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10967,30 +10967,6 @@
{ oid => '3114', descr => 'fetch the Nth row value',
proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
-{ oid => '8126', descr => 'fetch the preceding row value',
- proname => 'prev', provolatile => 's', prorettype => 'anyelement',
- proargtypes => 'anyelement', prosrc => 'window_prev' },
-{ oid => '8128', descr => 'fetch the Nth preceding row value',
- proname => 'prev', provolatile => 's', proisstrict => 'f', prorettype =>
'anyelement',
- proargtypes => 'anyelement int8', prosrc => 'window_prev_offset' },
-{ oid => '8127', descr => 'fetch the following row value',
- proname => 'next', provolatile => 's', prorettype => 'anyelement',
- proargtypes => 'anyelement', prosrc => 'window_next' },
-{ oid => '8129', descr => 'fetch the Nth following row value',
- proname => 'next', provolatile => 's', proisstrict => 'f', prorettype =>
'anyelement',
- proargtypes => 'anyelement int8', prosrc => 'window_next_offset' },
-{ oid => '8130', descr => 'fetch the first row value within match',
- proname => 'first', provolatile => 's', prorettype => 'anyelement',
- proargtypes => 'anyelement', prosrc => 'window_first' },
-{ oid => '8132', descr => 'fetch the Nth row value within match',
- proname => 'first', provolatile => 's', proisstrict => 'f', prorettype =>
'anyelement',
- proargtypes => 'anyelement int8', prosrc => 'window_first_offset' },
-{ oid => '8131', descr => 'fetch the last row value within match',
- proname => 'last', provolatile => 's', prorettype => 'anyelement',
- proargtypes => 'anyelement', prosrc => 'window_last' },
-{ oid => '8133', descr => 'fetch the Nth-from-last row value within match',
- proname => 'last', provolatile => 's', proisstrict => 'f', prorettype =>
'anyelement',
- proargtypes => 'anyelement int8', prosrc => 'window_last_offset' },
# functions for range types
{ oid => '3832', descr => 'I/O',
diff --git a/src/test/regress/expected/rpr.out
b/src/test/regress/expected/rpr.out
index c02c9d75a9a..dc5140fecc9 100644
--- a/src/test/regress/expected/rpr.out
+++ b/src/test/regress/expected/rpr.out
@@ -1021,16 +1021,6 @@ WINDOW w AS (
--
-- Error cases: PREV/NEXT usage restrictions
--
--- PREV outside DEFINE clause
-SELECT prev(price) FROM stock;
-ERROR: cannot use prev outside a DEFINE clause
-LINE 1: SELECT prev(price) FROM stock;
- ^
--- NEXT outside DEFINE clause
-SELECT next(price) FROM stock;
-ERROR: cannot use next outside a DEFINE clause
-LINE 1: SELECT next(price) FROM stock;
- ^
-- Nested PREV
SELECT price FROM stock
WINDOW w AS (
@@ -1598,15 +1588,6 @@ WINDOW w AS (
company2 | 07-10-2023 | 1300 | | | 0
(20 rows)
--- 2-arg PREV/NEXT outside DEFINE clause
-SELECT prev(price, 2) FROM stock;
-ERROR: cannot use prev outside a DEFINE clause
-LINE 1: SELECT prev(price, 2) FROM stock;
- ^
-SELECT next(price, 2) FROM stock;
-ERROR: cannot use next outside a DEFINE clause
-LINE 1: SELECT next(price, 2) FROM stock;
- ^
-- 2-arg PREV/NEXT: negative offset
SELECT company, tdate, price, first_value(price) OVER w
FROM stock
@@ -2134,19 +2115,6 @@ SELECT id, val, count(*) OVER w FROM rpr_nav WINDOW w AS
(
DEFINE A AS LAST(val, -1) IS NULL
);
ERROR: row pattern navigation offset must not be negative
--- FIRST/LAST outside DEFINE clause (error cases)
-SELECT first(val) FROM rpr_nav;
-ERROR: cannot use first outside a DEFINE clause
-LINE 1: SELECT first(val) FROM rpr_nav;
- ^
-SELECT last(val) FROM rpr_nav;
-ERROR: cannot use last outside a DEFINE clause
-LINE 1: SELECT last(val) FROM rpr_nav;
- ^
-SELECT first(val, 1) FROM rpr_nav;
-ERROR: cannot use first outside a DEFINE clause
-LINE 1: SELECT first(val, 1) FROM rpr_nav;
- ^
-- Functional notation: should access column, not RPR navigation
CREATE TEMP TABLE rpr_names (prev int, next int, first text, last text);
INSERT INTO rpr_names VALUES (1, 2, 'Joe', 'Blow');
diff --git a/src/test/regress/expected/rpr_base.out
b/src/test/regress/expected/rpr_base.out
index 41541898f5a..973bc55a146 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -1709,9 +1709,10 @@ WINDOW w AS (
B AS val > PREV(val)
)
ORDER BY id;
-ERROR: cannot use prev outside a DEFINE clause
+ERROR: function prev(integer) does not exist
LINE 1: SELECT PREV(id), id, val, COUNT(*) OVER w as cnt
^
+DETAIL: There is no function of that name.
-- NEXT function cannot be used other than in DEFINE
SELECT NEXT(id), id, val, COUNT(*) OVER w as cnt
FROM rpr_nav
@@ -1724,9 +1725,10 @@ WINDOW w AS (
B AS val > PREV(val)
)
ORDER BY id;
-ERROR: cannot use next outside a DEFINE clause
+ERROR: function next(integer) does not exist
LINE 1: SELECT NEXT(id), id, val, COUNT(*) OVER w as cnt
^
+DETAIL: There is no function of that name.
-- FIRST function - reference match_start row
SELECT id, val, COUNT(*) OVER w as cnt
FROM rpr_nav
@@ -1792,15 +1794,376 @@ ORDER BY id;
-- FIRST function cannot be used other than in DEFINE
SELECT FIRST(id), id, val FROM rpr_nav;
-ERROR: cannot use first outside a DEFINE clause
+ERROR: function first(integer) does not exist
LINE 1: SELECT FIRST(id), id, val FROM rpr_nav;
^
+DETAIL: There is no function of that name.
-- LAST function cannot be used other than in DEFINE
SELECT LAST(id), id, val FROM rpr_nav;
-ERROR: cannot use last outside a DEFINE clause
+ERROR: function last(integer) does not exist
LINE 1: SELECT LAST(id), id, val FROM rpr_nav;
^
+DETAIL: There is no function of that name.
DROP TABLE rpr_nav;
+-- Name-space: prev/next/first/last are navigation functions, not ordinary
functions
+CREATE SCHEMA rpr_navns;
+SET search_path TO rpr_navns, public;
+CREATE TABLE nt (g text, id int, val int);
+INSERT INTO nt VALUES ('x', 1, 100), ('x', 2, 200), ('x', 3, 150),
+ ('x', 4, 140), ('x', 5, 150);
+-- Outside DEFINE these are ordinary identifiers and resolve to nothing
+SELECT prev(val) FROM nt;
+ERROR: function prev(integer) does not exist
+LINE 1: SELECT prev(val) FROM nt;
+ ^
+DETAIL: There is no function of that name.
+SELECT next(val) FROM nt;
+ERROR: function next(integer) does not exist
+LINE 1: SELECT next(val) FROM nt;
+ ^
+DETAIL: There is no function of that name.
+SELECT prev(val, 2) FROM nt;
+ERROR: function prev(integer, integer) does not exist
+LINE 1: SELECT prev(val, 2) FROM nt;
+ ^
+DETAIL: There is no function of that name.
+SELECT next(val, 2) FROM nt;
+ERROR: function next(integer, integer) does not exist
+LINE 1: SELECT next(val, 2) FROM nt;
+ ^
+DETAIL: There is no function of that name.
+SELECT first(val) FROM nt;
+ERROR: function first(integer) does not exist
+LINE 1: SELECT first(val) FROM nt;
+ ^
+DETAIL: There is no function of that name.
+SELECT last(val) FROM nt;
+ERROR: function last(integer) does not exist
+LINE 1: SELECT last(val) FROM nt;
+ ^
+DETAIL: There is no function of that name.
+SELECT first(val, 1) FROM nt;
+ERROR: function first(integer, integer) does not exist
+LINE 1: SELECT first(val, 1) FROM nt;
+ ^
+DETAIL: There is no function of that name.
+-- A schema-qualified call is also a plain (failing) function lookup
+SELECT pg_catalog.prev(val) FROM nt;
+ERROR: function pg_catalog.prev(integer) does not exist
+LINE 1: SELECT pg_catalog.prev(val) FROM nt;
+ ^
+-- Outside DEFINE, a user-defined function of that name is callable
+CREATE FUNCTION next(numeric) RETURNS numeric AS 'SELECT -999::numeric'
+ LANGUAGE sql IMMUTABLE;
+SELECT next(10);
+ next
+------
+ -999
+(1 row)
+
+-- Inside DEFINE, unqualified PREV is nav whether or not a user prev() exists
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (START UP+)
+ DEFINE START AS TRUE, UP AS val > PREV(val))
+ ORDER BY id;
+ id | val | cnt | last_id
+----+-----+-----+---------
+ 1 | 100 | 2 | 2
+ 2 | 200 | 0 |
+ 3 | 150 | 0 |
+ 4 | 140 | 2 | 5
+ 5 | 150 | 0 |
+(5 rows)
+
+-- A qualified call invokes the function, so its volatility still matters
+-- VOLATILE: unqualified is nav; qualified is rejected as a volatile function
+CREATE FUNCTION prev(integer) RETURNS integer AS 'SELECT -999'
+ LANGUAGE sql VOLATILE;
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (START UP+)
+ DEFINE START AS TRUE, UP AS val > PREV(val))
+ ORDER BY id;
+ id | val | cnt | last_id
+----+-----+-----+---------
+ 1 | 100 | 2 | 2
+ 2 | 200 | 0 |
+ 3 | 150 | 0 |
+ 4 | 140 | 2 | 5
+ 5 | 150 | 0 |
+(5 rows)
+
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+)
+ DEFINE A AS rpr_navns.prev(val) = -999)
+ ORDER BY id;
+ERROR: volatile functions are not allowed in DEFINE clause
+LINE 6: DEFINE A AS rpr_navns.prev(val) = -999)
+ ^
+DROP FUNCTION prev(integer);
+-- IMMUTABLE: unqualified is nav; qualified is the escape hatch and succeeds
+CREATE FUNCTION prev(integer) RETURNS integer AS 'SELECT -999'
+ LANGUAGE sql IMMUTABLE;
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (START UP+)
+ DEFINE START AS TRUE, UP AS val > PREV(val))
+ ORDER BY id;
+ id | val | cnt | last_id
+----+-----+-----+---------
+ 1 | 100 | 2 | 2
+ 2 | 200 | 0 |
+ 3 | 150 | 0 |
+ 4 | 140 | 2 | 5
+ 5 | 150 | 0 |
+(5 rows)
+
+-- (val).prev is attribute notation, so it calls the ordinary function
prev(val)
+-- (the IMMUTABLE user prev here), the same as the schema-qualified call below
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+)
+ DEFINE A AS (val).prev = -999)
+ ORDER BY id;
+ id | val | cnt | last_id
+----+-----+-----+---------
+ 1 | 100 | 5 | 5
+ 2 | 200 | 0 |
+ 3 | 150 | 0 |
+ 4 | 140 | 0 |
+ 5 | 150 | 0 |
+(5 rows)
+
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+)
+ DEFINE A AS rpr_navns.prev(val) = -999)
+ ORDER BY id;
+ id | val | cnt | last_id
+----+-----+-----+---------
+ 1 | 100 | 5 | 5
+ 2 | 200 | 0 |
+ 3 | 150 | 0 |
+ 4 | 140 | 0 |
+ 5 | 150 | 0 |
+(5 rows)
+
+-- Zero or more than two arguments is an error, with no function fallback
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV() IS NULL);
+ERROR: too few arguments for row pattern navigation function PREV
+LINE 4: PATTERN (A+) DEFINE A AS PREV() IS NULL);
+ ^
+DETAIL: PREV takes a value expression and an optional offset argument.
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val, 1, 2) IS NULL);
+ERROR: too many arguments for row pattern navigation function PREV
+LINE 4: PATTERN (A+) DEFINE A AS PREV(val, 1, 2) IS NULL);
+ ^
+DETAIL: PREV takes a value expression and an optional offset argument.
+-- the error stands even when a user function of that exact arity exists
+CREATE FUNCTION prev(integer, integer, integer) RETURNS integer
+ AS 'SELECT -999' LANGUAGE sql IMMUTABLE;
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val, 1, 2) IS NULL);
+ERROR: too many arguments for row pattern navigation function PREV
+LINE 4: PATTERN (A+) DEFINE A AS PREV(val, 1, 2) IS NULL);
+ ^
+DETAIL: PREV takes a value expression and an optional offset argument.
+DROP FUNCTION prev(integer, integer, integer);
+-- Syntactic decoration is rejected
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(*) IS NULL);
+ERROR: prev(*) specified, but prev is not an aggregate function
+LINE 4: PATTERN (A+) DEFINE A AS PREV(*) IS NULL);
+ ^
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(DISTINCT val) IS NULL);
+ERROR: DISTINCT specified, but prev is not an aggregate function
+LINE 4: PATTERN (A+) DEFINE A AS PREV(DISTINCT val) IS NULL);
+ ^
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val ORDER BY val) IS NULL);
+ERROR: ORDER BY specified, but prev is not an aggregate function
+LINE 4: PATTERN (A+) DEFINE A AS PREV(val ORDER BY val) IS NULL)...
+ ^
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val) FILTER (WHERE true) IS NULL);
+ERROR: FILTER specified, but prev is not an aggregate function
+LINE 4: PATTERN (A+) DEFINE A AS PREV(val) FILTER (WHERE true) I...
+ ^
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val) WITHIN GROUP (ORDER BY val) IS NULL);
+ERROR: WITHIN GROUP specified, but prev is not an aggregate function
+LINE 4: PATTERN (A+) DEFINE A AS PREV(val) WITHIN GROUP (ORDER B...
+ ^
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val) OVER () IS NULL);
+ERROR: OVER specified, but prev is not a window function nor an aggregate
function
+LINE 4: PATTERN (A+) DEFINE A AS PREV(val) OVER () IS NULL);
+ ^
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(VARIADIC ARRAY[val]) IS NULL);
+ERROR: cannot use VARIADIC with row pattern navigation function PREV
+LINE 4: PATTERN (A+) DEFINE A AS PREV(VARIADIC ARRAY[val]) IS NU...
+ ^
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS prev(x => val) IS NULL);
+ERROR: cannot use named arguments with row pattern navigation function PREV
+LINE 4: PATTERN (A+) DEFINE A AS prev(x => val) IS NULL);
+ ^
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val) IGNORE NULLS IS NULL);
+ERROR: only window functions accept RESPECT/IGNORE NULLS
+LINE 4: PATTERN (A+) DEFINE A AS PREV(val) IGNORE NULLS IS NULL)...
+ ^
+-- Quoting does not escape: "prev" is nav, "PREV" is an ordinary name
+SELECT id, val, count(*) OVER w AS cnt
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (START UP+)
+ DEFINE START AS TRUE, UP AS val > "prev"(val))
+ ORDER BY id;
+ id | val | cnt
+----+-----+-----
+ 1 | 100 | 2
+ 2 | 200 | 0
+ 3 | 150 | 0
+ 4 | 140 | 2
+ 5 | 150 | 0
+(5 rows)
+
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS "PREV"(val) IS NULL);
+ERROR: function PREV(integer) does not exist
+LINE 4: PATTERN (A+) DEFINE A AS "PREV"(val) IS NULL);
+ ^
+DETAIL: There is no function of that name.
+-- A view round-trips: bare PREV stays a navigation function, and a qualified
+-- user prev() stays schema-qualified so it does not reparse as navigation
+CREATE VIEW navns_nav AS
+ SELECT id, count(*) OVER w AS cnt FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (START UP+) DEFINE START AS TRUE, UP AS val > PREV(val));
+CREATE VIEW navns_fn AS
+ SELECT id, count(*) OVER w AS cnt FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS rpr_navns.prev(val) = -999);
+SELECT pg_get_viewdef('navns_nav');
+ pg_get_viewdef
+---------------------------------------------------------------------------------------------
+ SELECT id,
+
+ count(*) OVER w AS cnt
+
+ FROM nt
+
+ WINDOW w AS (PARTITION BY g ORDER BY id ROWS BETWEEN CURRENT ROW AND
UNBOUNDED FOLLOWING +
+ AFTER MATCH SKIP PAST LAST ROW
+
+ INITIAL
+
+ PATTERN (start up+)
+
+ DEFINE
+
+ start AS true,
+
+ up AS (val > PREV(val)) );
+(1 row)
+
+SELECT pg_get_viewdef('navns_fn');
+ pg_get_viewdef
+---------------------------------------------------------------------------------------------
+ SELECT id,
+
+ count(*) OVER w AS cnt
+
+ FROM nt
+
+ WINDOW w AS (PARTITION BY g ORDER BY id ROWS BETWEEN CURRENT ROW AND
UNBOUNDED FOLLOWING +
+ AFTER MATCH SKIP PAST LAST ROW
+
+ INITIAL
+
+ PATTERN (a+)
+
+ DEFINE
+
+ a AS (rpr_navns.prev(val) = '-999'::integer) );
+(1 row)
+
+DROP VIEW navns_nav, navns_fn;
+-- Attribute notation is field selection only, never a function fallback
+CREATE TYPE rpr_navns_pair AS (first int, last int);
+CREATE TABLE ct (id int, p rpr_navns_pair);
+INSERT INTO ct VALUES (1, (10, 20)), (2, (30, 40));
+SELECT (p).last FROM ct ORDER BY id;
+ last
+------
+ 20
+ 40
+(2 rows)
+
+SELECT count(*) OVER w FROM ct
+ WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS (p).last > 0);
+ count
+-------
+ 2
+ 0
+(2 rows)
+
+SELECT count(*) OVER w FROM ct
+ WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS (p).prev > 0);
+ERROR: column "prev" not found in data type rpr_navns_pair
+LINE 4: PATTERN (A+) DEFINE A AS (p).prev > 0);
+ ^
+-- Navigation offset must not contain a navigation operation
+SELECT id, val
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+)
+ DEFINE A AS PREV(val, FIRST(1)) > 0)
+ ORDER BY id;
+ERROR: row pattern navigation offset cannot contain a row pattern navigation
operation
+LINE 6: DEFINE A AS PREV(val, FIRST(1)) > 0)
+ ^
+DETAIL: A navigation offset must be a run-time constant.
+DROP SCHEMA rpr_navns CASCADE;
+RESET search_path;
-- ============================================================
-- SKIP TO / INITIAL Tests
-- ============================================================
@@ -3655,6 +4018,127 @@ ERROR: cannot nest row pattern navigation more than
two levels deep
LINE 6: DEFINE A AS PREV(FIRST(PREV(v))) > 0
^
HINT: Only PREV(FIRST()), PREV(LAST()), NEXT(FIRST()), and NEXT(LAST())
compound forms are allowed.
+-- A navigation offset must be a run-time constant, not a navigation operation
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, FIRST(1)) > 0);
+ERROR: row pattern navigation offset cannot contain a row pattern navigation
operation
+LINE 4: PATTERN (A+) DEFINE A AS PREV(v, FIRST(1)) > 0);
+ ^
+DETAIL: A navigation offset must be a run-time constant.
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, FIRST(1) + 1) > 0);
+ERROR: row pattern navigation offset cannot contain a row pattern navigation
operation
+LINE 4: PATTERN (A+) DEFINE A AS PREV(v, FIRST(1) + 1) > 0);
+ ^
+DETAIL: A navigation offset must be a run-time constant.
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, NEXT(1, 0)) > 0);
+ERROR: row pattern navigation offset cannot contain a row pattern navigation
operation
+LINE 4: PATTERN (A+) DEFINE A AS PREV(v, NEXT(1, 0)) > 0);
+ ^
+DETAIL: A navigation offset must be a run-time constant.
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(FIRST(v), LAST(1)) > 0);
+ERROR: row pattern navigation offset cannot contain a row pattern navigation
operation
+LINE 4: PATTERN (A+) DEFINE A AS PREV(FIRST(v), LAST(1)) > 0);
+ ^
+DETAIL: A navigation offset must be a run-time constant.
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, FIRST(v)) > 0);
+ERROR: row pattern navigation offset cannot contain a row pattern navigation
operation
+LINE 4: PATTERN (A+) DEFINE A AS PREV(v, FIRST(v)) > 0);
+ ^
+DETAIL: A navigation offset must be a run-time constant.
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS NEXT(v, PREV(v, 1)) > 0);
+ERROR: row pattern navigation offset cannot contain a row pattern navigation
operation
+LINE 4: PATTERN (A+) DEFINE A AS NEXT(v, PREV(v, 1)) > 0);
+ ^
+DETAIL: A navigation offset must be a run-time constant.
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(FIRST(v, LAST(1)), 2) > 0);
+ERROR: cannot nest row pattern navigation more than two levels deep
+LINE 4: PATTERN (A+) DEFINE A AS PREV(FIRST(v, LAST(1)), 2) > 0)...
+ ^
+HINT: Only PREV(FIRST()), PREV(LAST()), NEXT(FIRST()), and NEXT(LAST())
compound forms are allowed.
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, FIRST(1::bigint)) > 0);
+ERROR: row pattern navigation offset cannot contain a row pattern navigation
operation
+LINE 4: PATTERN (A+) DEFINE A AS PREV(v, FIRST(1::bigint)) > 0);
+ ^
+DETAIL: A navigation offset must be a run-time constant.
+-- An unknown literal argument resolves to text; it must still reference a
column
+SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV('foo') = 'bar');
+ERROR: argument of row pattern navigation operation must include at least one
column reference
+LINE 4: PATTERN (A+) DEFINE A AS PREV('foo') = 'bar');
+ ^
+SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV('foo'));
+ERROR: argument of DEFINE must be type boolean, not type text
+LINE 4: PATTERN (A+) DEFINE A AS PREV('foo'));
+ ^
+SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(NULL) IS NULL);
+ERROR: argument of row pattern navigation operation must include at least one
column reference
+LINE 4: PATTERN (A+) DEFINE A AS PREV(NULL) IS NULL);
+ ^
+PREPARE rpr_navarg AS SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV($1) IS NULL);
+ERROR: argument of row pattern navigation operation must include at least one
column reference
+LINE 4: PATTERN (A+) DEFINE A AS PREV($1) IS NULL);
+ ^
+-- An int2 offset is coerced to int8 like any implicit cast (same as plain 0)
+SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, 0::smallint) = v);
+ count
+-------
+ 5
+ 0
+ 0
+ 0
+ 0
+(5 rows)
+
+SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, 0) = v);
+ count
+-------
+ 5
+ 0
+ 0
+ 0
+ 0
+(5 rows)
+
-- ============================================================
-- Window Deduplication Tests
-- ============================================================
diff --git a/src/test/regress/sql/rpr.sql b/src/test/regress/sql/rpr.sql
index b15b30c85ac..e3e9de789db 100644
--- a/src/test/regress/sql/rpr.sql
+++ b/src/test/regress/sql/rpr.sql
@@ -444,12 +444,6 @@ WINDOW w AS (
-- Error cases: PREV/NEXT usage restrictions
--
--- PREV outside DEFINE clause
-SELECT prev(price) FROM stock;
-
--- NEXT outside DEFINE clause
-SELECT next(price) FROM stock;
-
-- Nested PREV
SELECT price FROM stock
WINDOW w AS (
@@ -831,10 +825,6 @@ WINDOW w AS (
DEFINE A AS PREV(price, 0) = price
);
--- 2-arg PREV/NEXT outside DEFINE clause
-SELECT prev(price, 2) FROM stock;
-SELECT next(price, 2) FROM stock;
-
-- 2-arg PREV/NEXT: negative offset
SELECT company, tdate, price, first_value(price) OVER w
FROM stock
@@ -1124,11 +1114,6 @@ SELECT id, val, count(*) OVER w FROM rpr_nav WINDOW w AS
(
DEFINE A AS LAST(val, -1) IS NULL
);
--- FIRST/LAST outside DEFINE clause (error cases)
-SELECT first(val) FROM rpr_nav;
-SELECT last(val) FROM rpr_nav;
-SELECT first(val, 1) FROM rpr_nav;
-
-- Functional notation: should access column, not RPR navigation
CREATE TEMP TABLE rpr_names (prev int, next int, first text, last text);
INSERT INTO rpr_names VALUES (1, 2, 'Joe', 'Blow');
diff --git a/src/test/regress/sql/rpr_base.sql
b/src/test/regress/sql/rpr_base.sql
index fcefd59de4a..e71f0dd3680 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -1309,6 +1309,195 @@ SELECT LAST(id), id, val FROM rpr_nav;
DROP TABLE rpr_nav;
+-- Name-space: prev/next/first/last are navigation functions, not ordinary
functions
+CREATE SCHEMA rpr_navns;
+SET search_path TO rpr_navns, public;
+CREATE TABLE nt (g text, id int, val int);
+INSERT INTO nt VALUES ('x', 1, 100), ('x', 2, 200), ('x', 3, 150),
+ ('x', 4, 140), ('x', 5, 150);
+
+-- Outside DEFINE these are ordinary identifiers and resolve to nothing
+SELECT prev(val) FROM nt;
+SELECT next(val) FROM nt;
+SELECT prev(val, 2) FROM nt;
+SELECT next(val, 2) FROM nt;
+SELECT first(val) FROM nt;
+SELECT last(val) FROM nt;
+SELECT first(val, 1) FROM nt;
+-- A schema-qualified call is also a plain (failing) function lookup
+SELECT pg_catalog.prev(val) FROM nt;
+
+-- Outside DEFINE, a user-defined function of that name is callable
+CREATE FUNCTION next(numeric) RETURNS numeric AS 'SELECT -999::numeric'
+ LANGUAGE sql IMMUTABLE;
+SELECT next(10);
+
+-- Inside DEFINE, unqualified PREV is nav whether or not a user prev() exists
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (START UP+)
+ DEFINE START AS TRUE, UP AS val > PREV(val))
+ ORDER BY id;
+
+-- A qualified call invokes the function, so its volatility still matters
+-- VOLATILE: unqualified is nav; qualified is rejected as a volatile function
+CREATE FUNCTION prev(integer) RETURNS integer AS 'SELECT -999'
+ LANGUAGE sql VOLATILE;
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (START UP+)
+ DEFINE START AS TRUE, UP AS val > PREV(val))
+ ORDER BY id;
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+)
+ DEFINE A AS rpr_navns.prev(val) = -999)
+ ORDER BY id;
+DROP FUNCTION prev(integer);
+-- IMMUTABLE: unqualified is nav; qualified is the escape hatch and succeeds
+CREATE FUNCTION prev(integer) RETURNS integer AS 'SELECT -999'
+ LANGUAGE sql IMMUTABLE;
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (START UP+)
+ DEFINE START AS TRUE, UP AS val > PREV(val))
+ ORDER BY id;
+-- (val).prev is attribute notation, so it calls the ordinary function
prev(val)
+-- (the IMMUTABLE user prev here), the same as the schema-qualified call below
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+)
+ DEFINE A AS (val).prev = -999)
+ ORDER BY id;
+SELECT id, val, count(*) OVER w AS cnt, last_value(id) OVER w AS last_id
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+)
+ DEFINE A AS rpr_navns.prev(val) = -999)
+ ORDER BY id;
+
+-- Zero or more than two arguments is an error, with no function fallback
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV() IS NULL);
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val, 1, 2) IS NULL);
+-- the error stands even when a user function of that exact arity exists
+CREATE FUNCTION prev(integer, integer, integer) RETURNS integer
+ AS 'SELECT -999' LANGUAGE sql IMMUTABLE;
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val, 1, 2) IS NULL);
+DROP FUNCTION prev(integer, integer, integer);
+
+-- Syntactic decoration is rejected
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(*) IS NULL);
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(DISTINCT val) IS NULL);
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val ORDER BY val) IS NULL);
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val) FILTER (WHERE true) IS NULL);
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val) WITHIN GROUP (ORDER BY val) IS NULL);
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val) OVER () IS NULL);
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(VARIADIC ARRAY[val]) IS NULL);
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS prev(x => val) IS NULL);
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS PREV(val) IGNORE NULLS IS NULL);
+
+-- Quoting does not escape: "prev" is nav, "PREV" is an ordinary name
+SELECT id, val, count(*) OVER w AS cnt
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (START UP+)
+ DEFINE START AS TRUE, UP AS val > "prev"(val))
+ ORDER BY id;
+SELECT count(*) OVER w FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS "PREV"(val) IS NULL);
+
+-- A view round-trips: bare PREV stays a navigation function, and a qualified
+-- user prev() stays schema-qualified so it does not reparse as navigation
+CREATE VIEW navns_nav AS
+ SELECT id, count(*) OVER w AS cnt FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (START UP+) DEFINE START AS TRUE, UP AS val > PREV(val));
+CREATE VIEW navns_fn AS
+ SELECT id, count(*) OVER w AS cnt FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS rpr_navns.prev(val) = -999);
+SELECT pg_get_viewdef('navns_nav');
+SELECT pg_get_viewdef('navns_fn');
+DROP VIEW navns_nav, navns_fn;
+
+-- Attribute notation is field selection only, never a function fallback
+CREATE TYPE rpr_navns_pair AS (first int, last int);
+CREATE TABLE ct (id int, p rpr_navns_pair);
+INSERT INTO ct VALUES (1, (10, 20)), (2, (30, 40));
+SELECT (p).last FROM ct ORDER BY id;
+SELECT count(*) OVER w FROM ct
+ WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS (p).last > 0);
+SELECT count(*) OVER w FROM ct
+ WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+) DEFINE A AS (p).prev > 0);
+
+-- Navigation offset must not contain a navigation operation
+SELECT id, val
+ FROM nt
+ WINDOW w AS (PARTITION BY g ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING INITIAL
+ PATTERN (A+)
+ DEFINE A AS PREV(val, FIRST(1)) > 0)
+ ORDER BY id;
+
+DROP SCHEMA rpr_navns CASCADE;
+RESET search_path;
+
-- ============================================================
-- SKIP TO / INITIAL Tests
-- ============================================================
@@ -2420,6 +2609,68 @@ WINDOW w AS (
DEFINE A AS PREV(FIRST(PREV(v))) > 0
);
+-- A navigation offset must be a run-time constant, not a navigation operation
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, FIRST(1)) > 0);
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, FIRST(1) + 1) > 0);
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, NEXT(1, 0)) > 0);
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(FIRST(v), LAST(1)) > 0);
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, FIRST(v)) > 0);
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS NEXT(v, PREV(v, 1)) > 0);
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(FIRST(v, LAST(1)), 2) > 0);
+SELECT count(*) OVER w
+FROM generate_series(1,10) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, FIRST(1::bigint)) > 0);
+
+-- An unknown literal argument resolves to text; it must still reference a
column
+SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV('foo') = 'bar');
+SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV('foo'));
+SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(NULL) IS NULL);
+PREPARE rpr_navarg AS SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV($1) IS NULL);
+
+-- An int2 offset is coerced to int8 like any implicit cast (same as plain 0)
+SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, 0::smallint) = v);
+SELECT count(*) OVER w
+FROM generate_series(1,5) s(v)
+WINDOW w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+) DEFINE A AS PREV(v, 0) = v);
+
-- ============================================================
-- Window Deduplication Tests
-- ============================================================
--
2.50.1 (Apple Git-155)
From 1b8aac573b2d6d22882ee8892ccb7ae5cc215b5f Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Mon, 15 Jun 2026 16:54:13 +0900
Subject: [PATCH nocfbot 4/4] Use a dedicated ExprContext for RPR DEFINE clause
evaluation
DEFINE clauses were evaluated in the per-output-tuple context. Because
EEOP_RPR_NAV_RESTORE copies pass-by-reference navigation results into that
context, resetting it per row freed window function results before
ExecProject (a use-after-free), while not resetting it leaked across the
partition. tmpcontext cannot be reused either, as ExecQualAndReset() resets
it mid-expression when NEXT re-enters spool_tuples. Use a third ExprContext,
reset once per row and separate from both.
---
src/backend/executor/execRPR.c | 5 ++++-
src/backend/executor/nodeWindowAgg.c | 25 ++++++++++++++++++++++---
src/include/nodes/execnodes.h | 1 +
3 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/src/backend/executor/execRPR.c b/src/backend/executor/execRPR.c
index b326a58bbf5..de78b06d277 100644
--- a/src/backend/executor/execRPR.c
+++ b/src/backend/executor/execRPR.c
@@ -1592,10 +1592,13 @@ static void
nfa_reevaluate_dependent_vars(WindowAggState *winstate, RPRNFAContext *ctx,
int64 currentPos)
{
- ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+ ExprContext *econtext = winstate->rprContext;
int64 saved_match_start = winstate->nav_match_start;
int64 saved_pos = winstate->currentpos;
+ /* Release the previous evaluation's DEFINE expression memory */
+ ResetExprContext(econtext);
+
/* Temporarily set nav_match_start and currentpos for FIRST/LAST */
winstate->nav_match_start = ctx->matchStartRow;
winstate->currentpos = currentPos;
diff --git a/src/backend/executor/nodeWindowAgg.c
b/src/backend/executor/nodeWindowAgg.c
index 5b2385f1d8d..90f33bdee40 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2742,12 +2742,28 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int
eflags)
/*
* Create expression contexts. We need two, one for per-input-tuple
- * processing and one for per-output-tuple processing. We cheat a
little
- * by using ExecAssignExprContext() to build both.
+ * processing and one for per-output-tuple processing, plus an optional
+ * third for row pattern recognition DEFINE evaluation (built just below
+ * when a DEFINE clause is present). We cheat a little by using
+ * ExecAssignExprContext() to build them all. Each call overwrites
+ * ps_ExprContext, so the last call must establish the output context.
*/
ExecAssignExprContext(estate, &winstate->ss.ps);
tmpcontext = winstate->ss.ps.ps_ExprContext;
winstate->tmpcontext = tmpcontext;
+
+ /*
+ * Row pattern recognition evaluates DEFINE clauses in a third context,
+ * reset before each DEFINE evaluation pass. It must be distinct from
+ * tmpcontext and ps_ExprContext so its reset frees neither input nor
+ * output tuple memory.
+ */
+ if (node->defineClause != NIL)
+ {
+ ExecAssignExprContext(estate, &winstate->ss.ps);
+ winstate->rprContext = winstate->ss.ps.ps_ExprContext;
+ }
+
ExecAssignExprContext(estate, &winstate->ss.ps);
/* Create long-lived context for storage of partition-local memory etc
*/
@@ -4572,12 +4588,15 @@ static bool
nfa_evaluate_row(WindowObject winobj, int64 pos, bool *varMatched)
{
WindowAggState *winstate = winobj->winstate;
- ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+ ExprContext *econtext = winstate->rprContext;
int numDefineVars =
list_length(winstate->defineVariableList);
int varIdx = 0;
TupleTableSlot *slot;
int64 saved_pos;
+ /* Release the previous row's DEFINE evaluation memory */
+ ResetExprContext(econtext);
+
/* Fetch current row into temp_slot_1 */
slot = winstate->temp_slot_1;
if (!window_gettupleslot(winobj, pos, slot))
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b587c3dc15b..b1c3bd63b55 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2700,6 +2700,7 @@ typedef struct WindowAggState
MemoryContext aggcontext; /* shared context for aggregate working
data */
MemoryContext curaggcontext; /* current aggregate's working data */
ExprContext *tmpcontext; /* short-term evaluation context */
+ ExprContext *rprContext; /* DEFINE clause evaluation context */
bool all_first; /* true if the scan is starting
*/
bool partition_spooled; /* true if all tuples in
current partition
--
2.50.1 (Apple Git-155)