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 1wDkxm-003KZO-0I for pgsql-bugs@arkaria.postgresql.org; Fri, 17 Apr 2026 15:18:34 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wDkxk-00AXuo-0S for pgsql-bugs@arkaria.postgresql.org; Fri, 17 Apr 2026 15:18:32 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wDkxj-00AXuU-2U for pgsql-bugs@lists.postgresql.org; Fri, 17 Apr 2026 15:18:31 +0000 Received: from mail-yx1-xb12e.google.com ([2607:f8b0:4864:20::b12e]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wDkxh-00000001UZ5-2C8l for pgsql-bugs@postgresql.org; Fri, 17 Apr 2026 15:18:30 +0000 Received: by mail-yx1-xb12e.google.com with SMTP id 956f58d0204a3-651c8371ed5so577691d50.2 for ; Fri, 17 Apr 2026 08:18:29 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1776439109; cv=none; d=google.com; s=arc-20240605; b=V1+GAS6cDV98bVY2pnJNkgwq/e1u5eLsMyCzS24yEb2anBDQrv5aAU0w1bUF5KYpGm LTcuJHCD8SPg6EoFzo6Rw8mln9FVakyLKpa3ySezE4ryWw9xMt8Lb2z0pLtR/lM79MGy FVcJHlXbG9nSBCSMZzMHrJWp/Wc5lK96TnHlEK1I5oNMSZHhjeAyIM2xV1V25Q2GeiWX IFNWtGhRI7xix1f2LMBDMZaay1r3Eu3j4ObnJdyeyPcFBwru4RvhgUw2aQsEdEnNVe6L mFVixCVoLG5TJgZpCWLHUbu5rPuAvB/8aBZHvCjQiV5PQyASe8tetzokjASk62soJQQf myCQ== 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:in-reply-to:references :mime-version:dkim-signature; bh=b4SpMmuPITEA8xn3XzIu65VnZwWRFNbc57jn5Lrpv1M=; fh=NnUgeBkmh+TQprfTvuw6gN5uet0oqxw1F4T41DomM3g=; b=e2314SkkQ7JkxBHRtKy+zcy9939k4WTR+30TWqPYnkrxN5xGRvvKoELlfPzEBSKKtI Gg+/mqmsFAZQqbVnr3hN7Bu9fROz7bUMsbyRGVVnSVXwFu7b2tG38kcMzumZ5gFQzumv D/iYxOeA3grRLU3rMr1g28+OsYiuS4M6Ftdjd49K4MjIUXMRAkl8hwnZ5qHUCt51sD5Q 6987tMP9R5iZfXpOJ0FoPAH61238/9k6zubgeySgXGoS5SGfm4K2WK7lKz+4B0XMvB0S gxBLcFiLiU0x3RcaDGT9pc9bh6tQcV2weaYteoAvZWIgLDHa+AkMMZuPNEbIlUwQoJyy sM/A==; 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=1776439109; x=1777043909; darn=postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=b4SpMmuPITEA8xn3XzIu65VnZwWRFNbc57jn5Lrpv1M=; b=o9ZDsQgMqCP2Y7FTFJJIpfS1AwBTK7px3SWBbGpLHRROxgjgMH8Xoa4jkukrT3ollt GUWC+xiS0KOLWsGxLiGhbnaJZOxqm+kLbbkOXgGP4Gp7OwfBCAPNno4mqVCWOyeJCvIP C1BnaKf3gJm9XGBBK6/prZkam3IRlom947KVzZmI5n4dY5mZ9utMGVKFxqI5Rlpi9G7O ZPeAGzKO3tc3/GJ/GPCvB5rgsEA6UTZxMNV+W9vOUXq+lENuT7QrZvuoUSkqTIPulKer S/wcCYkMWG+8nLlULAUP0KTSDBQ9g8++PBRxR4Z3CeLN2rpokaz2FMXq1C9n2WYt1jbn fjPQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776439109; x=1777043909; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=b4SpMmuPITEA8xn3XzIu65VnZwWRFNbc57jn5Lrpv1M=; b=KBJ8EyOsgIchTAQNVWr0sOSTzyBcLzhxefw69DBItSzWtDFKJXR7FHUKcb8L8RKAWf aoCEf3uuV+XxW3LNgctu1I58OENC+JBP6T0fH+Dkw3Vjt5qFVTB78/UDgN9YhBzAs2AU YYaaJK7VS/hEXu/ZQEZnKBbHBOv3u13Ntvj3FdKJPLhFAYjyoO4psZYKtJ1X4LfF5OCW Q8SfJeWcV7i+7YdnaxRGNwZF8dnl/nX7JKfYSwdLDCQYkwpNPCl83Q85rQUUy9f5Ra7I 1NajyB+nuIl1tWkUYVaExOQrhj2onP+mJyNmeSIK8Em9XMfdurJnHXJNACAgUOlCD4sl N+MA== X-Gm-Message-State: AOJu0YzBdwQgrFI2isTn5YcDyzNjXyxiEPX2DRHm7N7ELbzBHos9zPyO Re7uBbi9g3Y0uOsihE4q97Nn+jqm85BHRHWbiNq6st16KJHBYIkmzWtfEfByA+mWF/kwXuLvpPJ zhyuvteERWiPOAXg1WscDY34WMpu1aRc= X-Gm-Gg: AeBDiesld2vzlL0w3UK9Zervkf2JRZ+UEls0NzmH2j0D1YR09Sm/vXh/SAKr24sZ+v0 yJ5oIockd/0rA/UpkmZq69p+EUXMfkRhi99pzIxDy8ouWckFYSqIHGqYzLmZ+RSCoW+Z6wpJW67 OtQIFr2JVyhx5BWXG6zljl9p0ZJRQg0XuABpNX/cZEj7aytGlU2NTns8TCBGRMJfVng6QGjAZeS osCPYFSthiD3ZO/iLswz1mcQwAD66zqP2iIbZ/OXkQaaJ9es40p9pOGoLxkMl1z8vHtkwUf7AK5 B0L9bG1PMoc/cSWnyKHpdIdwSF0z X-Received: by 2002:a05:690e:d43:b0:651:c41c:80d1 with SMTP id 956f58d0204a3-653108380dbmr2555457d50.15.1776439109057; Fri, 17 Apr 2026 08:18:29 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: "David G. Johnston" Date: Fri, 17 Apr 2026 08:17:52 -0700 X-Gm-Features: AQROBzCkDtTpT7SEPhe7QAO3Qe5o8SO4-KXnmOGebOWtdTqVwCFV57LRDg3MKlo Message-ID: Subject: Re: BUG: PL/pgSQL FOREACH misparses variable named "slice" with SLICE clause To: Leendert Gravendeel Cc: pgsql-bugs@postgresql.org Content-Type: multipart/alternative; boundary="0000000000000f8108064fa97616" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --0000000000000f8108064fa97616 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Fri, Apr 17, 2026 at 7:33=E2=80=AFAM Leendert Gravendeel wrote: > I believe I have found a parser issue in PL/pgSQL involving the > FOREACH ... SLICE syntax. > Thanks for the report! > > CREATE FUNCTION test_slice_conflict() RETURNS text > LANGUAGE plpgsql AS $$ > DECLARE > slice integer[]; > arr integer[] :=3D ARRAY[[1,2],[3,4]]; > BEGIN > FOREACH slice SLICE 1 IN ARRAY arr LOOP > END LOOP; > RETURN 'ok'; > END; > $$; > > Observed behavior: > The function fails to compile due to incorrect parsing of `slice` > as the SLICE keyword. > > Expected behavior: > `slice` should be treated as a normal identifier (loop variable), > and the function should compile and run successfully. > > Confirmed on master. Chat provided much more context for how/why this happened and why this seems like a good fix; prior art is my main argument though. If a PL/pgSQL variable is named "slice", using it in a FOREACH ... SLICE loop produces a spurious syntax error: DO $$ DECLARE slice integer[]; arr integer[] :=3D ARRAY[[1,2],[3,4]]; BEGIN FOREACH slice SLICE 1 IN ARRAY arr LOOP END LOOP; END; $$; ERROR: syntax error at or near "SLICE" The one-token lookahead in the for_variable grammar action runs under normal identifier lookup, so when "slice" is in scope the following SLICE keyword is consumed as a T_DATUM reference rather than K_SLICE, and foreach_slice fails. The fix is to suppress variable lookup for that lookahead, using the same save/restore pattern already used in pl_gram.y::read_cursor_args(): /* Read the argument name, ignoring any matching variable */ save_IdentifierLookup =3D plpgsql_IdentifierLookup; plpgsql_IdentifierLookup =3D IDENTIFIER_LOOKUP_DECLARE; yylex(yylvalp, yyllocp, yyscanner); argname =3D yylvalp->str; plpgsql_IdentifierLookup =3D save_IdentifierLookup; Therefore we need: diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index 5009e59a78f..681fd3d5cff 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -1635,11 +1635,15 @@ for_variable : T_DATUM else { int tok; + IdentifierLookup save_IdentifierLookup; $$.scalar =3D $1.datum; $$.row =3D NULL; /* check for comma-separated list */ + save_IdentifierLookup =3D plpgsql_IdentifierLookup; + plpgsql_IdentifierLookup =3D IDENTIFIER_LOOKUP_DECLARE; tok =3D yylex(&yylval, &yylloc, yyscanner); + plpgsql_IdentifierLookup =3D save_IdentifierLookup; plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner); if (tok =3D=3D ',') $$.row =3D (PLpgSQL_datum *) David J. --0000000000000f8108064fa97616 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
On Fri, Apr 17, 2026 at 7:33=E2=80=AFAM Leendert Gravendee= l <leenderthenk@gmail.com&= gt; wrote:
I believe I have found= a parser issue in PL/pgSQL involving the
FOREACH ... SLICE syntax.

Thanks for the = report!



=C2=A0 CREATE FUNCTION test_slice_conflict() RETURNS text
=C2=A0 LANGUAGE plpgsql AS $$
=C2=A0 DECLARE
=C2=A0 =C2=A0 slice integer[];
=C2=A0 =C2=A0 arr integer[] :=3D ARRAY[[1,2],[3,4]];
=C2=A0 BEGIN
=C2=A0 =C2=A0 FOREACH slice SLICE 1 IN ARRAY arr LOOP
=C2=A0 =C2=A0 END LOOP;
=C2=A0 =C2=A0 RETURN 'ok';
=C2=A0 END;
=C2=A0 $$;

Observed behavior:
The function fails to compile due to incorrect parsing of `slice`
as the SLICE keyword.

Expected behavior:
`slice` should be treated as a normal identifier (loop variable),
and the function should compile and run successfully.

<= div>
Confirmed on master.

Chat provided mu= ch more context for how/why this happened=C2=A0and why this seems like a go= od fix; prior art is my main argument though.

If a PL= /pgSQL variable is named "slice", using it in a FOREACH ... SLICE=
loop produces a spurious syntax error:

=C2=A0 DO $$ DECLARE
= =C2=A0 =C2=A0 =C2=A0 slice integer[];
=C2=A0 =C2=A0 =C2=A0 arr =C2=A0 in= teger[] :=3D ARRAY[[1,2],[3,4]];
=C2=A0 BEGIN
=C2=A0 =C2=A0 =C2=A0 FO= REACH slice SLICE 1 IN ARRAY arr LOOP
=C2=A0 =C2=A0 =C2=A0 END LOOP;
= =C2=A0 END; $$;
=C2=A0 ERROR: =C2=A0syntax error at or near "SLICE&= quot;

The one-token lookahead in the for_variable grammar action run= s under
normal identifier lookup, so when "slice" is in scope = the following SLICE
keyword is consumed as a T_DATUM reference rather th= an K_SLICE, and
foreach_slice fails.

The fix is to suppress varia= ble lookup for that lookahead, using the same
save/restore pattern alrea= dy used in pl_gram.y::read_cursor_args():

=C2=A0 /* Read the argumen= t name, ignoring any matching variable */
=C2=A0 save_IdentifierLookup = =3D plpgsql_IdentifierLookup;
=C2=A0 plpgsql_IdentifierLookup =3D IDENTI= FIER_LOOKUP_DECLARE;
=C2=A0 yylex(yylvalp, yyllocp, yyscanner);
=C2= =A0 argname =3D yylvalp->str;
=C2=A0 plpgsql_IdentifierLookup =3D sav= e_IdentifierLookup;

Therefore we need:

diff --git a/src/pl/pl= pgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 5009e59a78f..68= 1fd3d5cff 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpg= sql/src/pl_gram.y
@@ -1635,11 +1635,15 @@ for_variable : T_DATUM
=C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0el= se
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0{
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0int tok;
+ IdentifierLookup save_Ident= ifierLookup;
=C2=A0
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0$$.scalar =3D $1.datum;
=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0$$.row =3D NULL;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0/* check for comma-separated l= ist */
+ save_IdentifierLookup =3D plpgsql_IdentifierLookup;
+ = plpgsql_IdentifierLookup =3D IDENTIFIER_LOOKUP_DECLARE;
=C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0tok =3D yylex(&yylval, &yylloc, yyscanner);
+ plpgsql= _IdentifierLookup =3D save_IdentifierLookup;
=C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0plpgsql_push= _back_token(tok, &yylval, &yylloc, yyscanner);
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if= (tok =3D=3D ',')
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0$$.row =3D (PLpg= SQL_datum *)

David J.

--0000000000000f8108064fa97616--