Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1waNKW-001kvk-2C for pgsql-hackers@arkaria.postgresql.org; Fri, 19 Jun 2026 00:43:32 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1waNKT-00EC8G-2X for pgsql-hackers@arkaria.postgresql.org; Fri, 19 Jun 2026 00:43:29 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1waNKT-00EC87-1T for pgsql-hackers@lists.postgresql.org; Fri, 19 Jun 2026 00:43:29 +0000 Received: from mail-ed1-x52a.google.com ([2a00:1450:4864:20::52a]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1waNKR-00000001Bio-0Jaw for pgsql-hackers@postgresql.org; Fri, 19 Jun 2026 00:43:28 +0000 Received: by mail-ed1-x52a.google.com with SMTP id 4fb4d7f45d1cf-68e5f7c1131so2499536a12.2 for ; Thu, 18 Jun 2026 17:43:26 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1781829805; cv=none; d=google.com; s=arc-20240605; b=OexWkEvmj3XZISmNlWP0IQdvMcKBTSCGj71dKkVSyxjO7XwOki+YjaKFVOko5/iHzB N+mx2x3T/fbNnLUClwlQged8VoJy4EWsZNAVheomOuYn+mZnwwzYN6YmvwUttE0iIfe+ EbbaR39zkINBKC0Wiy8Xne9qzYv9P4J4fpyH9/lnTmK0X3suZCkxOdNov/3pIzg0/ZCN MTPyn31Bu1x/Pu7nXljdiseT3gAIwupvpOJ0LoA+G4AX0btHNZndqmi08cvqEzTgyGeW MpAkQbDSLiegEgBo7REiS2WXOinW1DjJ9wCH7kS7h0qVRllrfLLYkLApW5uf28GFRxqm +qpA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=cc:to:subject:message-id:date:from:reply-to:in-reply-to:references :mime-version:dkim-signature; bh=89E9UsNxdfCsq0hDNYIDBn+aDm9XgZWZCOuUbn1WkRc=; fh=zAf/b7oQa7kuWae0837SNKnwxZVm5idAJIuk4tH4OXA=; b=Nj3joMf4dN/F/pcQtY54q9NGhSEh0yfJN4UAKlDZohrkqmJblB3msB89ZhzG3AgPYh hPp4yVxiGjlR6+avuyuMos6hgeXZhm+6wREVq5ORIkQj7sxgjU2Qu68FqantL7nTsPKX nL6vqO71l1Th1PnbHc6ZJdxG/vYGex6M/PDgZSy3Xa+Cb18L4NZgCPFXTzVx/qhS8jtA 6cGUZOKqU1nZWvZmp4DnMPmXoysES4ItriANbJ3pER3Wab0spL3mUmGKuPFjjmkoFAQk MMzh1l4X1FeDCDogisXCaN1//VAshr7L32kT1aN80Kz5AmNJt031P/26SpaT5ZtFMByQ 7w+g==; darn=postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781829805; x=1782434605; darn=postgresql.org; h=cc:to:subject:message-id:date:from:reply-to:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=89E9UsNxdfCsq0hDNYIDBn+aDm9XgZWZCOuUbn1WkRc=; b=j83kN11ul2ynBkkBf0gvrmQYoN4shtQGtQXJ5Y9f31+KCkpAsc9sZcCLuq6fC4fjk9 SZfHkQ+6CK9bsoRw21vIjItcFiwmTtm0P484GShg1u5cPWxhA9mWJeNYeqE4YfAAm3cJ rNwnggUZopyNiRKinVnEceaqjIXSLk7SRQtDc1f7dGPDhTNwVp+T6xwgU/v99s4j4r8R IP9cHGXFEUUQXbbuBGyTYOV/eyXK7Jx3yMOyMXZvD6huHG80HU+YfjNHb4Fj4lwZTeEi 91DHAZXePblAPQl/gK5NOYuY7JCjoPWkiCZHGKHthMbxZm1Wk32aDvFEkC4QKQ23yboI 66NQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781829805; x=1782434605; h=cc:to:subject:message-id:date:from:reply-to:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=89E9UsNxdfCsq0hDNYIDBn+aDm9XgZWZCOuUbn1WkRc=; b=HunaI3EB/th/ww+XT8Hg2FVdPMcleeN5tD0m3YNqWJs4av1+oynHVMd7TUsm7Z4lF+ 0q+CXjF5mMsL0LtJiVoVIakaw8l/l7CYgfgR0tpmIchc3KnimL9JeJlVSm8t2t7G7ZlD efZB8HR3zfI80KBID9jfxeMqloz4eWdrfiPuLjsgavYVH5o/bDOKB4Qchmd+W2I0VUo3 1jIs8ZlaXpG4cz0gGRbysX9UoTJ2zVCDnUKlQWPK2RJprwGuCzm/B10Wms+RQDg/FLkG 3Kfr/hwWI4PKLcMDrzOJPiqA5Der9E6uq6/38Ds3FaUgh7ANKskA7TPp2Xsmf9yuYF+C UldA== X-Forwarded-Encrypted: i=1; AFNElJ82x5g4MaH4ZI/OxvcLMhAP5o/pL8oF5yLxPET+EFPlpR+kCbiZJnbddE3GBQG4IfoU8/XqjbLJ3+lXKn8H@postgresql.org X-Gm-Message-State: AOJu0YwqWPnjxp1+6opCSnp6n1xIuJpoePm3E/AAN8phV7ezcmPqGTTA 42IbIqyPnv+VX2Zyk1POfYUBwUCo4YnGz1BMp5SJe9hNjUlLFiY4qOsFQbTEpPIcd1vq/6a/caa FMiQ2xdo0+gj3NBU+xMZOcky1mOFfEZs= X-Gm-Gg: AfdE7clh0LTjUwv5d6XTVEyJpg2KqKoYV0Gl0NlL8s8w8Job8l3AZB4HWZt+Bi6jubj 7CRlrJD8rNu3ohuR1Th+IwqRp/l1R2I5KLRRGVdc/fP1FfClBYuPCiCL1bfb1P20VotcbzDPCvk h2UhfUq/9smQdJEEX/W5I26mO10WbpXDo7LeaJXbXZSybGNy/wzbJYEAj+0JjpO2nDDm2jQCpRG cQfm5b8uzQQ4HexBIVMwYy0kO9+QH49QuR07jzQvkM3qEICqrp0d+RzA8ff27uAHwwfis0B6Ar0 yGaehEBxxQ++WzthHNVVHK5l5GxlSyn/Cn/KOQozki7fecPqzCTkOMbSyAJe8rLUm/4YIOS3k35 yV8u13iqRFg== X-Received: by 2002:a17:907:971e:b0:bdb:e19d:525c with SMTP id a640c23a62f3a-c097c7bb58bmr62862066b.33.1781829804440; Thu, 18 Jun 2026 17:43:24 -0700 (PDT) MIME-Version: 1.0 References: <20260618.185007.1430819167281911424.ishii@postgresql.org> In-Reply-To: Reply-To: assam258@gmail.com From: Henson Choi Date: Fri, 19 Jun 2026 09:43:10 +0900 X-Gm-Features: AVVi8CcZCgHiAevI11nQ5YJli6gNq2-vWZ5pF9LlYwtTmIW8G9R9-jLDi2UMoN8 Message-ID: Subject: Re: Row pattern recognition To: jian he , Tatsuo Ishii 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 Content-Type: multipart/alternative; boundary="0000000000008b3e6a0654909452" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --0000000000008b3e6a0654909452 Content-Type: text/plain; charset="UTF-8" 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 --0000000000008b3e6a0654909452 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi Jian,

> ParseFuncOrColumn cleanly handles (f)= .prev by translating it to prev(f) as a
> regular function call. Howe= ver, if a dedicated window navigation function
> exists, this transla= tion creates ambiguity -- it becomes unclear whether
> prev(f) is win= dow navigation or a normal function call.

Agreed, and that is the re= ason I would rather not have a dedicated
navigation function at all.=C2= =A0 With navigation handled purely as syntax,
(f).prev never has to comp= ete with it: attribute notation just falls
through to an ordinary functi= on (prev(f)), the same inside and outside a
DEFINE clause.=C2=A0 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 t= reated
> as normal function calls, IMHO.

Yes.=C2=A0 A schema-q= ualified name is the explicit escape hatch to an ordinary
function; navi= gation 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 w= ant to land on: the dotless
prev(...)/next(...)/first(...)/last(...) for= ms are navigation, everything
else is an ordinary function.

> = Summary:
> Dedicated window navigation functions should be removed en= tirely.=C2=A0 Window
> navigation should be limited to a single synta= ctic form (no dots) -- one
> that *looks* like a function call but is= parsed as syntax.

That is the direction I will take the tree.=C2=A0= So the three of us have
converged.

> This is not unprecedente= d; there are many existing cases where something
> appears to be a fu= nction call but is actually a syntax form, for example:
>
> SEL= ECT json_object('{}');
> =C2=A0json_object
> ----------= ---
> =C2=A0{}
> (1 row)
>
> SELECT public.json_obj= ect('{}');
> ERROR: =C2=A0function public.json_object(unknown= ) does not exist
> LINE 1: SELECT public.json_object('{}');> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0^
>
= > So I think in the DEFINE context, it makes sense for some form that lo= oks
> like a function call to actually be syntax.

The json_obj= ect 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:

=C2=A0 -= No context restriction.=C2=A0 JSON_OBJECT means the same thing wherever a<= br>=C2=A0 =C2=A0 value expression is allowed, so a single keyword productio= n in the
=C2=A0 =C2=A0 shared a_expr grammar is enough.

=C2=A0 - = The name is safe to reserve.=C2=A0 Making json_object a keyword costs almos= t
=C2=A0 =C2=A0 nobody an identifier.

Row pattern navigation has = both constraints:

=C2=A0 - It must mean navigation *only* inside DEF= INE; everywhere else
=C2=A0 =C2=A0 prev/next/first/last are ordinary nam= es. =C2=A0bison is LALR(1) and
=C2=A0 =C2=A0 context-free, so a producti= on cannot be conditioned on "are we inside
=C2=A0 =C2=A0 DEFINE&quo= t;; a_expr is shared by SELECT lists, WHERE, etc.=C2=A0 And a row
=C2=A0= =C2=A0 pattern definition is "ColId AS a_expr", so navigation ca= n appear
=C2=A0 =C2=A0 anywhere a value expression can.=C2=A0 Special-ca= sing it in the grammar
=C2=A0 =C2=A0 would mean duplicating the whole a_= expr tree into a second DEFINE-only
=C2=A0 =C2=A0 expression grammar (or= resorting to lexer feedback), which is not worth
=C2=A0 =C2=A0 it.
<= br>=C2=A0 - The names are common. =C2=A0prev/next/first/last (especially ne= xt, first,
=C2=A0 =C2=A0 last) are everyday column and function names; r= eserving them globally
=C2=A0 =C2=A0 would break existing queries.
So the plan is the opposite of a grammar keyword: parse navigation as an<= br>ordinary FuncCall and reinterpret it in parse analysis, gated on
p_ex= pr_kind =3D=3D EXPR_KIND_RPR_DEFINE and an unqualified, single-element
n= ame.=C2=A0 That keeps one expression grammar, leaves the names free everywh= ere
else, and confines the special meaning to exactly the DEFINE context= -- the
same "looks like a function call, parsed as syntax" be= havior you described,
reached by a lighter path that does not grow the k= eyword list.

Thanks,
Henson
--0000000000008b3e6a0654909452--