Re: Row pattern recognition
Henson Choi <assam258@gmail.com>
From: Henson Choi <assam258@gmail.com>
To: jian he <jian.universality@gmail.com>, Tatsuo Ishii <ishii@postgresql.org>
Cc: zsolt.parragi@percona.com, sjjang112233@gmail.com,
vik@postgresfriends.org, er@xs4all.nl, jacob.champion@enterprisedb.com, david.g.johnston@gmail.com, peter@eisentraut.org, li.evan.chao@gmail.com, pgsql-hackers@postgresql.org
Date: 2026-06-19T00:43:10Z
Lists: pgsql-hackers
Hi Jian,
> ParseFuncOrColumn cleanly handles (f).prev by translating it to prev(f)
as a
> regular function call. However, if a dedicated window navigation function
> exists, this translation creates ambiguity -- it becomes unclear whether
> prev(f) is window navigation or a normal function call.
Agreed, and that is the reason I would rather not have a dedicated
navigation function at all. With navigation handled purely as syntax,
(f).prev never has to compete with it: attribute notation just falls
through to an ordinary function (prev(f)), the same inside and outside a
DEFINE clause. That is Tatsuo's and my preferred option (a), so I will
settle on it.
> Cases with additional dots (e.g., public.prev(arg)) should also be treated
> as normal function calls, IMHO.
Yes. A schema-qualified name is the explicit escape hatch to an ordinary
function; navigation is recognized only for an unqualified, single-element
name.
> As a result, only prev(arg) and prev(arg, offset) are recognized as
special
> window navigation syntax, despite being visually identical to a function
> call.
Right -- that is exactly the surface I want to land on: the dotless
prev(...)/next(...)/first(...)/last(...) forms are navigation, everything
else is an ordinary function.
> Summary:
> Dedicated window navigation functions should be removed entirely. Window
> navigation should be limited to a single syntactic form (no dots) -- one
> that *looks* like a function call but is parsed as syntax.
That is the direction I will take the tree. So the three of us have
converged.
> This is not unprecedented; there are many existing cases where something
> appears to be a function call but is actually a syntax form, for example:
>
> SELECT json_object('{}');
> json_object
> -------------
> {}
> (1 row)
>
> SELECT public.json_object('{}');
> ERROR: function public.json_object(unknown) does not exist
> LINE 1: SELECT public.json_object('{}');
> ^
>
> So I think in the DEFINE context, it makes sense for some form that looks
> like a function call to actually be syntax.
The json_object parallel is exact at the user-visible level, but I want to
flag that the implementation has to differ underneath -- and that is
actually why navigation is not done the json_object way.
json_object can live in the grammar because two constraints do not apply to
it:
- No context restriction. JSON_OBJECT means the same thing wherever a
value expression is allowed, so a single keyword production in the
shared a_expr grammar is enough.
- The name is safe to reserve. Making json_object a keyword costs almost
nobody an identifier.
Row pattern navigation has both constraints:
- It must mean navigation *only* inside DEFINE; everywhere else
prev/next/first/last are ordinary names. bison is LALR(1) and
context-free, so a production cannot be conditioned on "are we inside
DEFINE"; a_expr is shared by SELECT lists, WHERE, etc. And a row
pattern definition is "ColId AS a_expr", so navigation can appear
anywhere a value expression can. Special-casing it in the grammar
would mean duplicating the whole a_expr tree into a second DEFINE-only
expression grammar (or resorting to lexer feedback), which is not worth
it.
- The names are common. prev/next/first/last (especially next, first,
last) are everyday column and function names; reserving them globally
would break existing queries.
So the plan is the opposite of a grammar keyword: parse navigation as an
ordinary FuncCall and reinterpret it in parse analysis, gated on
p_expr_kind == EXPR_KIND_RPR_DEFINE and an unqualified, single-element
name. That keeps one expression grammar, leaves the names free everywhere
else, and confines the special meaning to exactly the DEFINE context -- the
same "looks like a function call, parsed as syntax" behavior you described,
reached by a lighter path that does not grow the keyword list.
Thanks,
Henson
Commits
Same data as JSON:
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Adjust cross-version upgrade tests for seg_out() fix
- 3e3d7875e956 19 (unreleased) cited
-
Rationalize error comments in partition split/merge tests
- ecb2508aaf9b 19 (unreleased) cited
-
Add fast path for foreign key constraint checks
- 2da86c1ef9b5 19 (unreleased) cited
-
Fix assorted pretty-trivial memory leaks in the backend.
- e78d1d6d47dc 19 (unreleased) cited
-
Add temporal FOREIGN KEY contraints
- 89f908a6d0ac 18.0 cited
-
Add trailing commas to enum definitions
- 611806cd726f 17.0 cited
-
Remove obsolete executor cleanup code
- d060e921ea5a 17.0 cited