Re: RFC: Additional Directory for Extensions

From: "David E(dot) Wheeler" <david(at)justatheory(dot)com>
To: Peter Eisentraut <peter(at)eisentraut(dot)org>
Cc: PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org>, Gabriele Bartolini <gabriele(dot)bartolini(at)enterprisedb(dot)com>, Craig Ringer <craig(dot)ringer(at)enterprisedb(dot)com>, Robert Haas <robertmhaas(at)gmail(dot)com>, Alvaro Herrera <alvherre(at)alvh(dot)no-ip(dot)org>
Subject: Re: RFC: Additional Directory for Extensions
Date: 2025-01-14 20:01:30
Message-ID: 137FF1C5-F586-4C48-8A8B-5314CAD0FDF9@justatheory.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hello Peter & Co.

On Dec 5, 2024, at 06:07, Peter Eisentraut <peter(at)eisentraut(dot)org> wrote:

> I've made a bit of progress on this patch, filled in some documentation and resolved some TODO markers. Also:

Finally getting around to reviewing this patch. Should it be considered part of the previous patch[1] for purposes of commitfest tracking?

I’ve also created a GitHub PR[2] for anyone who’d prefer to look it over that way.

>> Some open problems or discussion points:
>> - You can install extensions into alternative directories using PGXS like
>> make install datadir=/else/where/share pkglibdir=/else/where/lib
>> This works. I was hoping it would work to use
>> make install prefix=/else/where
>> but that doesn't because of some details in Makefile.global. I think we can tweak that a little bit to make that work too.
>
> This works now.

I tried `prefix` with semver[3] and it did not work:

``` console
❯ make PG_CONFIG=~/dev/misc/postgres/pgsql-devel/bin/pg_config prefix=/Users/david/pgsql-test
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Werror=unguarded-availability-new -Wendif-labels -Wmissing-format-attribute -Wcast-function-type -Wformat-security -Wmissing-variable-declarations -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-unused-command-line-argument -Wno-compound-token-split-by-macro -Wno-cast-function-type-strict -I/opt/homebrew/opt/readline/include -I/opt/homebrew/opt/openssl/include -I/opt/homebrew/opt/libxml2/include -I/opt/homebrew/opt/icu4c/include -fvisibility=hidden -I. -I./ -I/Users/david/pgsql-test/include/server -I/Users/david/pgsql-test/include/internal -I/opt/homebrew/Cellar/icu4c(at)76/76.1_1/include -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX15.1.sdk -I/opt/homebrew/opt/readline/include -I/opt/homebrew/opt/openssl/include -I/opt/homebrew/opt/libxml2/include -I/opt/homebrew/opt/icu4c/include -c -o src/semver.o src/semver.c
src/semver.c:13:10: fatal error: 'postgres.h' file not found
13 | #include "postgres.h"
| ^~~~~~~~~~~~
1 error generated.
make: *** [src/semver.o] Error 1
```

It works fine without `prefix` to install into the default directories as before. It also installs fine with `datadir` and `pkglibdir`:

``` console
❯ make PG_CONFIG=~/dev/misc/postgres/pgsql-devel/bin/pg_config datadir=/Users/david/pgsql-test/share pkglibdir=/Users/david/pgsql-test/lib install
/opt/homebrew/bin/gmkdir -p '/Users/david/pgsql-test/share/extension'
/opt/homebrew/bin/gmkdir -p '/Users/david/pgsql-test/share/semver'
/opt/homebrew/bin/gmkdir -p '/Users/david/pgsql-test/lib'
/opt/homebrew/bin/gmkdir -p '/Users/david/dev/misc/postgres/pgsql-devel/share/doc/semver'
/opt/homebrew/bin/ginstall -c -m 644 .//semver.control '/Users/david/pgsql-test/share/extension/'
/opt/homebrew/bin/ginstall -c -m 644 .//sql/semver--0.10.0--0.11.0.sql .//sql/semver--0.11.0--0.12.0.sql .//sql/semver--0.12.0--0.13.0.sql .//sql/semver--0.13.0--0.15.0.sql .//sql/semver--0.15.0--0.16.0.sql .//sql/semver--0.16.0--0.17.0.sql .//sql/semver--0.17.0--0.20.0.sql .//sql/semver--0.2.1--0.2.4.sql .//sql/semver--0.2.4--0.3.0.sql .//sql/semver--0.20.0--0.21.0.sql .//sql/semver--0.21.0--0.22.0.sql .//sql/semver--0.22.0--0.30.0.sql .//sql/semver--0.3.0--0.4.0.sql .//sql/semver--0.30.0--0.31.0.sql .//sql/semver--0.31.0--0.31.1.sql .//sql/semver--0.31.1--0.31.2.sql .//sql/semver--0.31.2--0.32.0.sql .//sql/semver--0.5.0--0.10.0.sql .//sql/semver--unpackaged--0.2.1.sql '/Users/david/pgsql-test/share/semver/'
/opt/homebrew/bin/ginstall -c -m 755 src/semver.dylib '/Users/david/pgsql-test/lib/'
/opt/homebrew/bin/gmkdir -p '/Users/david/pgsql-test/lib/bitcode/src/semver'
/opt/homebrew/bin/gmkdir -p '/Users/david/pgsql-test/lib/bitcode'/src/semver/src/
/opt/homebrew/bin/ginstall -c -m 644 src/semver.bc '/Users/david/pgsql-test/lib/bitcode'/src/semver/src/
cd '/Users/david/pgsql-test/lib/bitcode' && /opt/homebrew/Cellar/llvm/19.1.6/bin/llvm-lto -thinlto -thinlto-action=thinlink -o src/semver.index.bc src/semver/src/semver.bc
/opt/homebrew/bin/ginstall -c -m 644 .//doc/semver.mmd '/Users/david/dev/misc/postgres/pgsql-devel/share/doc/semver/'
```

But then it won’t load:

```psql
postgres=# create extension semver;
ERROR: extension "semver" has no installation script nor update path for version “0.40.0"
```

Since `semver` uses the directory parameter[4], I decided to try another C extension that doesn’t use it, envvar[5], which worked:

``` psql
postgres=# create extension envvar;
CREATE EXTENSION
```

So I suspect the issue is that, when looking for SQL files, the patch needs to use the directory parameter[4] when it’s set --- and it can be an absolute path! Honestly I think there’s a case to be made for eliminating that parameter.

The `prefix` param works with a pure SQL extension like pair[6]:

```console
❯ make PG_CONFIG=~/dev/misc/postgres/pgsql-devel/bin/pg_config prefix=/Users/david/pgsql-test
cp sql/pair.sql sql/pair--0.1.2.sql

❯ make PG_CONFIG=~/dev/misc/postgres/pgsql-devel/bin/pg_config prefix=/Users/david/pgsql-test install
/opt/homebrew/bin/gmkdir -p '/Users/david/pgsql-test/share/extension'
/opt/homebrew/bin/gmkdir -p '/Users/david/pgsql-test/share/extension'
/opt/homebrew/bin/gmkdir -p '/Users/david/pgsql-test/share/doc//extension'
/opt/homebrew/bin/ginstall -c -m 644 .//pair.control '/Users/david/pgsql-test/share/extension/'
/opt/homebrew/bin/ginstall -c -m 644 .//sql/pair--0.1.2.sql .//sql/pair--unpackaged--0.1.2.sql '/Users/david/pgsql-test/share/extension/'
/opt/homebrew/bin/ginstall -c -m 644 .//doc/pair.md '/Users/david/pgsql-test/share/doc//extension/‘
```

I thought it would put the files into /Users/david/pgsql-test/extension, not /Users/david/pgsql-test/share/extension? I guess that makes sense; but then perhaps the search path should be for prefixes, in which case I’d use a config like:

``` ini
extension_control_path = '/Users/david/pgsql-test:$system'
dynamic_library_path = '/Users/david/pgsql-test/lib:$libdir'
```

And the search path would append `share/extension` to each path. But then it varies from the behavior of `dynamic_library_path`. :-(

Not at all sure where the doc files should go unless, again, prefix is truly used as a prefix for all the things, which frankly seems reasonable.

I’m wondering whether there should be formal documentation of prefix, datadir, pkglibdir, etc. I haven’t noticed them in the PGXS docs[7].

>> This disables the use of dynamic_library_path, so this whole idea of installing an extension elsewhere won't work that way. The obvious solution is that extensions change this to just 'foo'. But this will require a lot updating work for many extensions, or a lot of patching by packagers.
>
>
> I have solved this by just stripping off "$libdir/" from the front of the filename. This works for now. We can think about other ways to tweak this, perhaps, but I don't see any drawback to this in practice.

I think this makes perfect sense. I presume it’s stripped out *before* replacing the MODULE_PATHNAME string in SQL files, yes?

# Patch Review

Compiles fine. All tests pass. Some comments on the docs:

> <primary><varname>extension_control_path</varname> configuration parameter</primary>

Although the current path explicitly searches for control files, I’d like to suggest a more generic name, since the point is to find extensions; control files are just the (current) method for identifying them. I suggest `extension_path`. Although given the above, maybe it should be all about prefixes.

> The value for <varname>extension_control_path</varname> must be a
> list of absolute directory paths separated by colons (or semi-colons
> on Windows). If a list element starts
> with the special string <literal>$system</literal>, the

I like this idea, though I quibble with the term `$system`, which seems like it would identify SYSCONFDIR, not SHAREDIR/extension. How about `$extdir` or, if using prefixes, `$coreprefix` or something.

> Note that the path elements should typically end in
> <literal>extension</literal> if the normal installation layouts are
> followed. (The value for <literal>$system</literal> already includes
> the <literal>extension</literal> suffix.)

In fact, if we’re using `prefix` as currently implemented, it should end in `share/extension`, no?

> Note that if you set this parameter to be able to load extensions from
> nonstandard locations, you will most likely also need to set <xref
> linkend="guc-dynamic-library-path"/> to a correspondent location, for

s/correspondent/corresponding/

> example,
> <programlisting>
> extension_control_path = '/usr/local/share/postgresql/extension:$system'
> dynamic_library_path = '/usr/local/lib/postgresql:$libdir'
> </programlisting>

This makes sense in the context of this patch, but I sure would like to see these features decoupled as much as possible.

<digression type="brief">

Hence my proposal that extensions each have their own directory. For example, via Slack Gabriele sent Peter and me a POC for loading individual extensions as mounted volumes in an OCI container. The Kubernetes config looks like this:

``` yaml
postgresql:
shared_preload_libraries:
- pg_squeeze
parameters:
extension_control_path: '/extensions/pgvector/18/share:/extensions/pgsqueeze/18/share:$system'
dynamic_library_path: '/extensions/pgvector/18/lib:/extensions/pgsqueeze/18/lib:$libdir'

extensions:
- name: pgvector
image:
reference: ghcr.io/cloudnative-pg/pgvector-testing:0.8.0
- name: pgsqueeze
image:
reference: ghcr.io/cloudnative-pg/pgsqueeze-testing:1.7
```

Which works! But it also means that new directories need to be appended to `extension_control_path` and, often `dynamic_library_path` for every extension added. It would be much nicer to just have 2-3 paths that contain extensions identified by a directory name. Then, to add a new extension, one just installs it to the appropriate prefix. (This is also yet another reason to eliminate the directory parameter[4].)

This is only additional wish I had for this feature, but I also believe we can iterate in that direction based on your patch.

</digression>

> the installation's <literal>SHAREDIR</literal> directory. By default,
> the script files are looked for in the same directory where the
> control file was found.

This could use a pointer to the impact of the directory parameter[4]. Bit of a wildcard for DBAs who want to use this feature, TBH.

Best,

David

[1]: https://commitfest.postgresql.org/51/4913/
[2]: https://github.com/theory/postgres/pull/9/files
[3]: https://github.com/theory/pg-semver
[4]: https://www.postgresql.org/docs/current/extend-extensions.html#EXTEND-EXTENSIONS-FILES-DIRECTORY
[5]: https://github.com/theory/pg-envvar
[6]: https://github.com/theory/kv-pair
[7]: https://www.postgresql.org/docs/current/extend-pgxs.html

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Masahiko Sawada 2025-01-14 20:02:36 Re: Skip collecting decoded changes of already-aborted transactions
Previous Message Tom Lane 2025-01-14 19:52:29 Re: pgbench error: (setshell) of script 0; execution of meta-command failed