BUG #19506: LOAD '$libdir/...' inside extension scripts ignores dynamic_library_path with extension_control_path
The following bug has been logged on the website:
Bug reference: 19506
Logged by: Gabriele Bartolini
Email address: gabriele.bartolini@gmail.com
PostgreSQL version: 18.4
Operating system: Linux (reproduced under CloudNativePG/Kubernetes)
Description:
When an extension is installed in a location reached via
`extension_control_path` / `dynamic_library_path` (rather than the
compiled-in package library directory), a LOAD '$libdir/foo' hardcoded
inside an extension's SQL script fails to find the library. PostGIS does
this in its upgrade scripts, so a PostGIS upgrade fails:
```
app=# SELECT postgis_extensions_upgrade();
NOTICE: Updating extension postgis 3.6.1
ERROR: could not access file "$libdir/postgis-3": No such file or directory
CONTEXT: SQL statement "LOAD '$libdir/postgis-3'"
extension script file "postgis--ANY--3.6.3.sql", near line 1530
```
This is a side effect of the fix for bug #18920 (commit f777d773878). Commit
4f7f7b03758 (`extension_control_path`) made the feature work by stripping
the '$libdir/' prefix so that dynamic_library_path is consulted. #18920 then
restricted that stripping to the function-load path so that a user-issued
`LOAD` keeps the literal '$libdir/' prefix. As a result, a `LOAD` inside an
extension script now also keeps the literal prefix, so
`dynamic_library_path` is never consulted, and the library cannot be found.
A `LOAD` running inside an extension script should behave like the
extension's function loads (strip '$libdir/'), while a LOAD issued directly
by a user should keep it (the #18920 behaviour). The two can be
distinguished by `creating_extension`.
Reproduced with the CloudNativePG operator on Kubernetes, but it applies to
any setup using `extension_control_path` / `dynamic_library_path`.
Hi,
On Thu, 4 Jun 2026 at 21:23, PG Bug reporting form <noreply@postgresql.org>
wrote:
The following bug has been logged on the website:
Bug reference: 19506
Logged by: Gabriele Bartolini
Email address: gabriele.bartolini@gmail.com
PostgreSQL version: 18.4
Operating system: Linux (reproduced under CloudNativePG/Kubernetes)
Description:When an extension is installed in a location reached via
`extension_control_path` / `dynamic_library_path` (rather than the
compiled-in package library directory), a LOAD '$libdir/foo' hardcoded
inside an extension's SQL script fails to find the library. PostGIS does
this in its upgrade scripts, so a PostGIS upgrade fails:```
app=# SELECT postgis_extensions_upgrade();
NOTICE: Updating extension postgis 3.6.1
ERROR: could not access file "$libdir/postgis-3": No such file or
directory
CONTEXT: SQL statement "LOAD '$libdir/postgis-3'"
extension script file "postgis--ANY--3.6.3.sql", near line 1530
```This is a side effect of the fix for bug #18920 (commit f777d773878).
Commit
4f7f7b03758 (`extension_control_path`) made the feature work by stripping
the '$libdir/' prefix so that dynamic_library_path is consulted. #18920
then
restricted that stripping to the function-load path so that a user-issued
`LOAD` keeps the literal '$libdir/' prefix. As a result, a `LOAD` inside an
extension script now also keeps the literal prefix, so
`dynamic_library_path` is never consulted, and the library cannot be found.A `LOAD` running inside an extension script should behave like the
extension's function loads (strip '$libdir/'), while a LOAD issued directly
by a user should keep it (the #18920 behaviour). The two can be
distinguished by `creating_extension`.Reproduced with the CloudNativePG operator on Kubernetes, but it applies to
any setup using `extension_control_path` / `dynamic_library_path`.
Thanks for the report and the very clear diagnosis, I could
reproduce the issue and your analysis matches what I see.
The attached patch implements exactly what you suggested: a
LOAD running while creating_extension is true strips the simple
"$libdir/" prefix (so dynamic_library_path is consulted, like
the extension's own function loads do), while a user-issued
LOAD keeps the literal prefix and therefore preserves the
#18920 behaviour.
To avoid duplicating the existing prefix-stripping logic in
load_external_function(), I factored it out into a small
static helper and reused it from load_file(). Nested paths
(e.g. "$libdir/foo/bar") are still left untouched and continue
to be expanded by expand_dynamic_library_name() as before.
Verified locally that the PostGIS-style reproducer now succeeds
when the extension is installed via extension_control_path /
dynamic_library_path, that a plain user-issued
LOAD '$libdir/foo' still behaves as on HEAD, and that the
existing extension regression suites still pass.
Regards,
Ayush
Attachments:
v1-0001-dfmgr-let-extension-script-LOAD-use-dynamic_lib.patchapplication/octet-stream; name=v1-0001-dfmgr-let-extension-script-LOAD-use-dynamic_lib.patchDownload+24-15
Hi Ayush,
I was about to draft an email to hackers about this. I had already done a
patch: https://github.com/gbartolini/postgres/pull/1
Thanks for your reply. I will verify yours later.
Cheers,
Gabriele
On Fri, 5 Jun 2026 at 14:27, Ayush Tiwari <ayushtiwari.slg01@gmail.com>
wrote:
Hi,
On Thu, 4 Jun 2026 at 21:23, PG Bug reporting form <noreply@postgresql.org>
wrote:The following bug has been logged on the website:
Bug reference: 19506
Logged by: Gabriele Bartolini
Email address: gabriele.bartolini@gmail.com
PostgreSQL version: 18.4
Operating system: Linux (reproduced under CloudNativePG/Kubernetes)
Description:When an extension is installed in a location reached via
`extension_control_path` / `dynamic_library_path` (rather than the
compiled-in package library directory), a LOAD '$libdir/foo' hardcoded
inside an extension's SQL script fails to find the library. PostGIS does
this in its upgrade scripts, so a PostGIS upgrade fails:```
app=# SELECT postgis_extensions_upgrade();
NOTICE: Updating extension postgis 3.6.1
ERROR: could not access file "$libdir/postgis-3": No such file or
directory
CONTEXT: SQL statement "LOAD '$libdir/postgis-3'"
extension script file "postgis--ANY--3.6.3.sql", near line 1530
```This is a side effect of the fix for bug #18920 (commit f777d773878).
Commit
4f7f7b03758 (`extension_control_path`) made the feature work by stripping
the '$libdir/' prefix so that dynamic_library_path is consulted. #18920
then
restricted that stripping to the function-load path so that a user-issued
`LOAD` keeps the literal '$libdir/' prefix. As a result, a `LOAD` inside
an
extension script now also keeps the literal prefix, so
`dynamic_library_path` is never consulted, and the library cannot be
found.A `LOAD` running inside an extension script should behave like the
extension's function loads (strip '$libdir/'), while a LOAD issued
directly
by a user should keep it (the #18920 behaviour). The two can be
distinguished by `creating_extension`.Reproduced with the CloudNativePG operator on Kubernetes, but it applies
to
any setup using `extension_control_path` / `dynamic_library_path`.Thanks for the report and the very clear diagnosis, I could
reproduce the issue and your analysis matches what I see.The attached patch implements exactly what you suggested: a
LOAD running while creating_extension is true strips the simple
"$libdir/" prefix (so dynamic_library_path is consulted, like
the extension's own function loads do), while a user-issued
LOAD keeps the literal prefix and therefore preserves the
#18920 behaviour.To avoid duplicating the existing prefix-stripping logic in
load_external_function(), I factored it out into a small
static helper and reused it from load_file(). Nested paths
(e.g. "$libdir/foo/bar") are still left untouched and continue
to be expanded by expand_dynamic_library_name() as before.Verified locally that the PostGIS-style reproducer now succeeds
when the extension is installed via extension_control_path /
dynamic_library_path, that a plain user-issued
LOAD '$libdir/foo' still behaves as on HEAD, and that the
existing extension regression suites still pass.Regards,
Ayush
--
Gabriele Bartolini
VP, Chief Architect, Kubernetes
enterprisedb.com / Melbourne, Australia
Hi,
On Fri, 5 Jun 2026 at 10:04, Gabriele Bartolini <
gabriele.bartolini@enterprisedb.com> wrote:
Hi Ayush,
I was about to draft an email to hackers about this. I had already done a
patch: https://github.com/gbartolini/postgres/pull/1Thanks for your reply. I will verify yours later.
Cheers,
Gabriele
Your patch looks good to me, we can go ahead with that.
I dont think this needs a dedicated tap test file though,
you can use the existing 001_extension_control_path.pl
Happy to review, once you post it.
Regards,
Ayush
On 03/06/26 19:21, PG Bug reporting form wrote:
The following bug has been logged on the website:
Bug reference: 19506
Logged by: Gabriele Bartolini
Email address: gabriele.bartolini@gmail.com
PostgreSQL version: 18.4
Operating system: Linux (reproduced under CloudNativePG/Kubernetes)
Description:When an extension is installed in a location reached via
`extension_control_path` / `dynamic_library_path` (rather than the
compiled-in package library directory), a LOAD '$libdir/foo' hardcoded
inside an extension's SQL script fails to find the library. PostGIS does
this in its upgrade scripts, so a PostGIS upgrade fails:```
app=# SELECT postgis_extensions_upgrade();
NOTICE: Updating extension postgis 3.6.1
ERROR: could not access file "$libdir/postgis-3": No such file or directory
CONTEXT: SQL statement "LOAD '$libdir/postgis-3'"
extension script file "postgis--ANY--3.6.3.sql", near line 1530
```This is a side effect of the fix for bug #18920 (commit f777d773878). Commit
4f7f7b03758 (`extension_control_path`) made the feature work by stripping
the '$libdir/' prefix so that dynamic_library_path is consulted. #18920 then
restricted that stripping to the function-load path so that a user-issued
`LOAD` keeps the literal '$libdir/' prefix. As a result, a `LOAD` inside an
extension script now also keeps the literal prefix, so
`dynamic_library_path` is never consulted, and the library cannot be found.
The only reason that it was decided that we should strip the $libdir
was that almost all popular extensions use $libdir prefix on
module_pathname on .control files, so the extension_control_path would
not work and waiting for extensions to change this will make the
extension_control_path almost useless (I hope that we can remove this
in the future once 18 is the minimum supported version).
I'm not sure if we also want this for the LOAD command, I'm wondering
if the postgis could remove the $libdir prefix from the LOAD command
instead of Postgres striping this, what do you think? IIRC a simple
LOAD 'postgis-3' on versions before 18 will still works.
I think that extensions should start to remove the $libdir prefix
since extension_control_path is going to the second release cycle, but
I agree that it seems more complicated than it actually is.
--
Matheus Alcantara
EDB: https://www.enterprisedb.com
Hi everyone,
I realised I hadn't sent this email with the patch. Please find the
attached patch.
Thanks,
Gabriele
On Fri, 5 Jun 2026 at 22:12, Matheus Alcantara <matheusssilv97@gmail.com>
wrote:
On 03/06/26 19:21, PG Bug reporting form wrote:
The following bug has been logged on the website:
Bug reference: 19506
Logged by: Gabriele Bartolini
Email address: gabriele.bartolini@gmail.com
PostgreSQL version: 18.4
Operating system: Linux (reproduced under CloudNativePG/Kubernetes)
Description:When an extension is installed in a location reached via
`extension_control_path` / `dynamic_library_path` (rather than the
compiled-in package library directory), a LOAD '$libdir/foo' hardcoded
inside an extension's SQL script fails to find the library. PostGIS does
this in its upgrade scripts, so a PostGIS upgrade fails:```
app=# SELECT postgis_extensions_upgrade();
NOTICE: Updating extension postgis 3.6.1
ERROR: could not access file "$libdir/postgis-3": No such file ordirectory
CONTEXT: SQL statement "LOAD '$libdir/postgis-3'"
extension script file "postgis--ANY--3.6.3.sql", near line 1530
```This is a side effect of the fix for bug #18920 (commit f777d773878).
Commit
4f7f7b03758 (`extension_control_path`) made the feature work by stripping
the '$libdir/' prefix so that dynamic_library_path is consulted. #18920then
restricted that stripping to the function-load path so that a user-issued
`LOAD` keeps the literal '$libdir/' prefix. As a result, a `LOAD` insidean
extension script now also keeps the literal prefix, so
`dynamic_library_path` is never consulted, and the library cannot befound.
The only reason that it was decided that we should strip the $libdir
was that almost all popular extensions use $libdir prefix on
module_pathname on .control files, so the extension_control_path would
not work and waiting for extensions to change this will make the
extension_control_path almost useless (I hope that we can remove this
in the future once 18 is the minimum supported version).I'm not sure if we also want this for the LOAD command, I'm wondering
if the postgis could remove the $libdir prefix from the LOAD command
instead of Postgres striping this, what do you think? IIRC a simple
LOAD 'postgis-3' on versions before 18 will still works.I think that extensions should start to remove the $libdir prefix
since extension_control_path is going to the second release cycle, but
I agree that it seems more complicated than it actually is.--
Matheus Alcantara
EDB: https://www.enterprisedb.com
--
Gabriele Bartolini
VP, Chief Architect, Kubernetes
enterprisedb.com / Melbourne, Australia
Attachments:
0001-Strip-libdir-from-LOAD-inside-extension-scripts.patchapplication/octet-stream; name=0001-Strip-libdir-from-LOAD-inside-extension-scripts.patchDownload+159-13
Hi Matheus, do you think we can apply this patch to the next Postgres minor
release?
On Wed, 24 Jun 2026 at 19:47, Gabriele Bartolini <
gabriele.bartolini@enterprisedb.com> wrote:
Hi everyone,
I realised I hadn't sent this email with the patch. Please find the
attached patch.Thanks,
GabrieleOn Fri, 5 Jun 2026 at 22:12, Matheus Alcantara <matheusssilv97@gmail.com>
wrote:On 03/06/26 19:21, PG Bug reporting form wrote:
The following bug has been logged on the website:
Bug reference: 19506
Logged by: Gabriele Bartolini
Email address: gabriele.bartolini@gmail.com
PostgreSQL version: 18.4
Operating system: Linux (reproduced under CloudNativePG/Kubernetes)
Description:When an extension is installed in a location reached via
`extension_control_path` / `dynamic_library_path` (rather than the
compiled-in package library directory), a LOAD '$libdir/foo' hardcoded
inside an extension's SQL script fails to find the library. PostGIS does
this in its upgrade scripts, so a PostGIS upgrade fails:```
app=# SELECT postgis_extensions_upgrade();
NOTICE: Updating extension postgis 3.6.1
ERROR: could not access file "$libdir/postgis-3": No such file ordirectory
CONTEXT: SQL statement "LOAD '$libdir/postgis-3'"
extension script file "postgis--ANY--3.6.3.sql", near line 1530
```This is a side effect of the fix for bug #18920 (commit f777d773878).
Commit
4f7f7b03758 (`extension_control_path`) made the feature work by
stripping
the '$libdir/' prefix so that dynamic_library_path is consulted. #18920
then
restricted that stripping to the function-load path so that a
user-issued
`LOAD` keeps the literal '$libdir/' prefix. As a result, a `LOAD`
inside an
extension script now also keeps the literal prefix, so
`dynamic_library_path` is never consulted, and the library cannot befound.
The only reason that it was decided that we should strip the $libdir
was that almost all popular extensions use $libdir prefix on
module_pathname on .control files, so the extension_control_path would
not work and waiting for extensions to change this will make the
extension_control_path almost useless (I hope that we can remove this
in the future once 18 is the minimum supported version).I'm not sure if we also want this for the LOAD command, I'm wondering
if the postgis could remove the $libdir prefix from the LOAD command
instead of Postgres striping this, what do you think? IIRC a simple
LOAD 'postgis-3' on versions before 18 will still works.I think that extensions should start to remove the $libdir prefix
since extension_control_path is going to the second release cycle, but
I agree that it seems more complicated than it actually is.--
Matheus Alcantara
EDB: https://www.enterprisedb.com--
Gabriele Bartolini
VP, Chief Architect, Kubernetes
enterprisedb.com / Melbourne, Australia
--
Gabriele Bartolini
VP, Chief Architect, Kubernetes
enterprisedb.com / Melbourne, Australia
On Mon Jul 6, 2026 at 10:37 PM -03, Gabriele Bartolini wrote:
Hi Matheus, do you think we can apply this patch to the next Postgres minor
release?
Hi Gabriele, did you saw my reply on [1]/messages/by-id/9ef850b9-d127-44db-9a6c-4db64436b056@gmail.com?
Although we have this strip logic when reading module_pathname I'm not
sure if we should replicate this for the LOAD command.
I think that this should be fixed on postgis update scripts instead of
Postgres since the $libdir on module_pathname and LOAD commands is not
necessary anymore after 18 release.
Also I think that we need more opinions on this from other hackers.
[1]: /messages/by-id/9ef850b9-d127-44db-9a6c-4db64436b056@gmail.com
--
Matheus Alcantara
EDB: https://www.enterprisedb.com