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 1wBPCu-0011qE-1b for pgsql-hackers@arkaria.postgresql.org; Sat, 11 Apr 2026 03:40:29 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wBPCr-00FQlS-1P for pgsql-hackers@arkaria.postgresql.org; Sat, 11 Apr 2026 03:40:26 +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 1wBPCr-00FQlK-0E for pgsql-hackers@lists.postgresql.org; Sat, 11 Apr 2026 03:40:26 +0000 Received: from mail-dl1-x122d.google.com ([2607:f8b0:4864:20::122d]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wBPCp-00000000Sa3-2yMJ for pgsql-hackers@lists.postgresql.org; Sat, 11 Apr 2026 03:40:25 +0000 Received: by mail-dl1-x122d.google.com with SMTP id a92af1059eb24-12bfa7fe691so2913271c88.0 for ; Fri, 10 Apr 2026 20:40:23 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1775878821; cv=none; d=google.com; s=arc-20240605; b=Cgfjo0891Vl4plof+V+8DxWHKtgfe/370QuJ30EG964MUqqc7NPhNGjNTz/OZBclO+ tzKV4z1nU/sinG5x9XxQ3MC7udC4fWEk6k4qgZ6OsF5JK6+QkES7LTizfRAM6DE5Y9E6 NtDB09RAg7LL7euB1TmRc+5hDFcdnExHuJCzBYOUSgBnzy71fo7qSck1n3oBTlU+vhJ0 sejk9aZ1rQ8fPZM3kGril62BjG01mqn2UwmQ9vuWEDGF67gYKQwXvEUcOm33wkaoregg A5f41TuQ91A25QfV2AsA+ndok0n6aVvL+e5uMeJxdvQQhMG8gCyb9RSnav5Gbhyge5/b x0sg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=to:subject:message-id:date:from:mime-version:dkim-signature; bh=jqxrumU9h+rYnDQIhqyAjFr/WO3/qUUlF/ctZAb1/z0=; fh=eL7DjuqwxWiuYdO11QNufGyZy4K2CU6M+Z7o6g5j7lc=; b=fs2m3NCSsCMc0LoBWKZNH7kUPUWI1lftehKIwXNz+jg9Fdpe+NcPVJP5VC1wvvBAHk Tmejfh9zrk9OdVC55bubZwwB6/6DqQQis7KDLM2BGs9yib1MV7NPnz/FokkldqfHy/p7 o4DTGTALDPrMiVVUvhtsyb4LdBJ7olm3B6FAEXCmyYwkh/IVXEZtseWfDFb26nKuAoVa cf01bM8BZpc1Q5uC3Rd2M2oJCOdnQILkrg0N1iUih3HOSWjgmqEI2hrUEGU52m0Q+5fT 9QyxKjE8omxyzDgunUVGYHij2A87+MS6D16/RBpJvfKJ1fYwHUUTuRi9zbG48k3AmNoE Cj/g==; darn=lists.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=1775878821; x=1776483621; darn=lists.postgresql.org; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=jqxrumU9h+rYnDQIhqyAjFr/WO3/qUUlF/ctZAb1/z0=; b=NfvO42Yz4PWdysHYJZv/fPKe7/RRbRd0Qtv0EacdS7BUxXlmquz6DSqFb+koNWsjmg eOAkAY/HdDL2IFyxFCO0DJF5lsMwJ3GaIrwei5AhZPPyFYZZdubI9P5khU4+LyqKT4TO qNjK6WN7FG4yOY7vJVg5SXhGGMUF3DxWG5zxGANmcybiWSe1zDlzo+3H6a1I1ZLfJ+6K SqL8xdiitT6m8Lpt/ZSbY7gYE5O9q7g4bHJRnvoJoQt93FIvQTOeDyCeXsXlWstrV62s 7cr26Kkn7WUak+hLDjQbR+RcYD0yBCpgvm4qJdVdI6c5W7ptur2WzpCap2im/N1JlLCL iJKA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775878821; x=1776483621; h=to:subject:message-id:date:from:mime-version:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=jqxrumU9h+rYnDQIhqyAjFr/WO3/qUUlF/ctZAb1/z0=; b=crhTDVUFc2V4oOA1Fp7oi10nryB29CTOgps6FFvskFGi3xJtcfhg6XQEVfTGuqdNbW TyoBI4Y81V/2zxFyD7foMm+rT5cnTyGgvMUuxsjcyscxhoCu9lz/OgLgydKe/6xxAyhu P6pO8hUMj8gOL4P/E3hsSwJ6cNRxNIr7vUc+FrSnP9JBkUugACDpdTH9IXBfczYRTp+A dhX0+OKYrVZr7zkoqHkAp+wSYnCyFGR/ur0SoGEqaPquFE5JOFtGIV1rcmReo4HfSIW+ aDgr3dh0sNbmvnonOJMR4zxAqMFNEfpzf8d6EZFMB628g1am+ovjguYCLACAZsZ2vCIL OABg== X-Gm-Message-State: AOJu0Yw+ihPY3nxV0v2uQW/I3/DuZqB0D7kdQvpJzUmjLEAL/nPgr8qR CmkGZ0TC2xWITa10a2BI7OIrUTf6Ww+BJW88zU6YmvK2SkQL18NESKYE1OuXrWs6FnWD86ZCfJ/ cpHbGDAzO48RtSdCDViGZolnJDw7bMQtXaU1M X-Gm-Gg: AeBDieui2dbLavaIDIqNrUEhgRvATAXzFxaTSxxveZy0IAFrBMmsRoRsTYYzXxkxHEu 9Lg9alJ9qU0Vsfaoektfdg5F0BaWZtEcsJ31KHYIo83TPCyHYqIkDFsx46DqJDgUUkchKQLyD99 ZmYyxLFBaDelDerV/QUB34iZLzLSyMT9Ysum6r67ww4hhaExNZSqD9L4dkqKKO5JOQOB52HeYdY 7zZ4WzcnTkmBTcM5IucN8bqNrGAssZ+OGt72FR90qLTISgl6YViL95Nq2R1n36g/Jb5xZ6Xqx+8 ctclQgTB X-Received: by 2002:a05:7022:6981:b0:128:d362:31e8 with SMTP id a92af1059eb24-12c34edb90emr3897589c88.20.1775878821188; Fri, 10 Apr 2026 20:40:21 -0700 (PDT) MIME-Version: 1.0 From: Isaac Morland Date: Fri, 10 Apr 2026 23:40:10 -0400 X-Gm-Features: AQROBzA8GeZUe_oZavi2L-y-V3HHG58aDJUCxR890u8Ajknaw_-EY0zPWp9vICE Message-ID: Subject: Possible mismatch between behaviour and documentation in CREATE FUNCTION To: PostgreSQL Developers Content-Type: multipart/alternative; boundary="0000000000004d18fa064f27023b" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --0000000000004d18fa064f27023b Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable The documentation says that only BEGIN ATOMIC parses the function body at definition time. Whereas AS parses the function body at execution time: https://www.postgresql.org/docs/current/sql-createfunction.html (and look at the explanation of sql_body) For PLPGSQL this works the way I would expect: postgres=3D# create or replace function t (f int) returns void language plpgsql as $$ begin select abs (f); end; $$; CREATE FUNCTION postgres=3D# create or replace function t (f int) returns void language plpgsql as $$ begin select absasdf (f); end; $$; CREATE FUNCTION But for SQL it does not - it's pretty clear some pretty detailed checking of the function body is happening: postgres=3D# create or replace function t (f int) returns void language sql as $$ select abs (f); $$; CREATE FUNCTION postgres=3D# create or replace function t (f int) returns void language sql as $$ select absasdf (f); $$; ERROR: function absasdf(integer) does not exist LINE 1: ... t (f int) returns void language sql as $$ select absasdf (f... ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. The only difference between the last two statements is whether or not the specified function actually exists. The other thing I don't understand is how this interacts with the behaviour of search_path. My understanding was that the function behaviour could change depending on the search_path at call time; but if that's true it's nonsensical to parse (or at least to look up objects used by) the function at definition time, because the meaning of the body depends on the search_path and could be valid at execution time even if not at definition time. If we talk about overall syntax, not just things like function name lookup, then my confusion extends to PLPGSQL as well: postgres=3D# create or replace function t (f int) returns void language plpgsql as $$ begin select; end; $$; CREATE FUNCTION postgres=3D# create or replace function t (f int) returns void language plpgsql as $$ begin selec; end; $$; ERROR: syntax error at or near "selec" LINE 1: ...(f int) returns void language plpgsql as $$ begin selec; end... ^ In other words, while PLPGSQL doesn't look up function names to check that they exist, it won't accept a syntactically invalid function body. I feel that I must somehow be confusing myself, because the documentation describes how I thought the system worked but I don't see how that can be reconciled with the observed behaviour. Do we need a documentation update? This is all on a reasonably recent version: postgres=3D# select version (); version =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80 PostgreSQL 18.1 (Homebrew) on x86_64-apple-darwin23.6.0, compiled by Apple clang version 16.0.0 (clang-1600.0.26.6), 64-bit (1 row) Thanks for any insight anybody can provide. --0000000000004d18fa064f27023b Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
The documentation says that only BEGIN ATOMIC parses the f= unction body at definition time. Whereas AS parses the function body at exe= cution time:


(and look at the exp= lanation of sql_body)

For PLPGSQL this works the w= ay I would expect:

postgres=3D# create or replace = function t (f int) returns void language plpgsql as $$ begin select abs (f)= ; end; $$;
CREATE FUNCTION
postgres=3D# create or replace function t = (f int) returns void language plpgsql as $$ begin select absasdf (f); end; = $$;
CREATE FUNCTION

But for SQL it does not= - it's pretty clear some pretty detailed checking of the function body= is happening:

postgres=3D# create or replace func= tion t (f int) returns void language sql as $$ select abs (f); $$;
CREAT= E FUNCTION
postgres=3D# create or replace function t (f int) returns voi= d language sql as $$ select absasdf (f); $$;
ERROR: =C2=A0function absas= df(integer) does not exist
LINE 1: ... t (f int) returns void language s= ql as $$ select absasdf (f...
=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=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0^
HINT: =C2=A0No function matches the given n= ame and argument types. You might need to add explicit type casts.

The only difference between the last two statements is= whether or not the specified function actually exists.

The other thing I don't understand is how this interacts with the= behaviour of search_path. My understanding was that the function behaviour= could change depending on the search_path at call time; but if that's = true it's nonsensical to parse=C2=A0(or at least to look up objects used by) the function at de= finition time, because the meaning of the body depends on the search_path a= nd could be valid at execution time even if not at definition time.

If we talk about overall syntax, not just things like fun= ction name lookup, then my confusion extends to PLPGSQL as well:
=
postgres=3D# create or replace function t (f int) returns vo= id language plpgsql as $$ begin select; end; $$;
CREATE FUNCTION
post= gres=3D# create or replace function t (f int) returns void language plpgsql= as $$ begin selec; end; $$;
ERROR: =C2=A0syntax error at or near "= selec"
LINE 1: ...(f int) returns void language plpgsql as $$ begin= selec; end...
=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=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0^

In other words, while PLPGSQL d= oesn't look up function names to check that they exist, it won't ac= cept a syntactically invalid function body.

I feel= that I must somehow be confusing myself, because the documentation describ= es how I thought the system worked but I don't see how that can be reco= nciled with the observed behaviour. Do we need a documentation update?

This is all on a reasonably recent version:
=
postgres=3D# select version ();
=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=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0version =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=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0
=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80
=C2=A0PostgreSQL 18.1 (Homebrew) on x8= 6_64-apple-darwin23.6.0, compiled by Apple clang version 16.0.0 (clang-1600= .0.26.6), 64-bit
(1 row)

Thanks for any ins= ight anybody can provide.
--0000000000004d18fa064f27023b--