Add Postgres module info

Started by Andrei Lepikhovabout 1 year ago52 messages
#1Andrei Lepikhov
lepihov@gmail.com
1 attachment(s)

Hi,

I would like to propose the module_info structure, which aims to let
extension maintainers sew some data into the binary file. Being included
in the module code, this information remains unchanged and is available
for reading by a backend.
As I see, this question was already debated during the introduction of
PG_MODULE_MAGIC [1]/messages/by-id/20060507211705.GB3808@svana.org, and developers didn't add such data because the
practical case wasn't obvious. Right now, when multiple extensions are
maintained and used in installations constantly, we have enough
information to continue this discussion.
The idea was initially born with the support of the extension, which had
a stable UI and frequently changing library code. To answer customer
requests, it was necessary to know the specific version of the code to
reproduce the situation in a test environment. That required introducing
a file naming rule and a specific exported variable into the module
code. However, this is not a sufficient guarantee of version
determination and complicates the technical process when supporting many
extensions obtained from different sources.
Another example is a library without an installation script at all - see
auto_explain. Without a 'CREATE EXTENSION' call, we don't have an option
to find out the library's version.
It would be much easier if the Postgres catalogue contained a function,
for example, module_info(module_name), which would allow you to
determine the file's full path and name containing the desired module in
the psql console and its version.
On the other hand, the Omnigres project (author Yurii Rashkovski) also
came up with the idea of ​​​​module versioning, although it does this
externally out of the Postgres core. When designing this code, I also
adopted ideas from this repository.
So, let me propose a patch that introduces this tiny feature: the
maintainer can add the PG_MODULE_INFO macro to the library code, and
Postgres reads it on the module's load.

There is a question of how much information makes sense to add to the
module. For now, each time I prepare extensions to release, I have to
add the extension name (to avoid issues with file naming/renaming) and
the version. Format of the version storage? Do we need a separate minor
version number? It is a subject to debate.

[1]: /messages/by-id/20060507211705.GB3808@svana.org
/messages/by-id/20060507211705.GB3808@svana.org

--
regards, Andrei Lepikhov

Attachments:

v0-0001-Introduce-MODULE_INFO-macro.patchtext/plain; charset=UTF-8; name=v0-0001-Introduce-MODULE_INFO-macro.patchDownload
From 56d4a0dadd32a601d52ed59b38935a36a175f635 Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepihov@gmail.com>
Date: Tue, 19 Nov 2024 18:45:36 +0700
Subject: [PATCH v0] Introduce MODULE_INFO macro.

This optional macro allows dynamically loaded shared libraries (modules)
a standard way to incorporate version and name data. The introduced catalogue
routine module_info can be used to find this module by name and check
the version. It makes users independent from file naming conventions.

With a growing number of Postgres core hooks and the introduction of named DSM
segments, the number of modules that don't need to be loaded on startup may
grow fast. Moreover, in many cases related to query tree transformation or
extra path recommendation, such modules might not need database objects except
GUCs - see auto_explain as an example. That means they don't need to execute
the 'CREATE EXTENSION' statement at all and don't have a record in
the pg_extension table. Such a trick provides much flexibility, including
an online upgrade and may become widespread.

In addition, it is also convenient in support to be sure that the installation
(or at least the backend) includes a specific version of the module. Even if
a module has an installation script, it is not rare that it provides
an implementation for a range of UI routine versions. It makes sense to ensure
which specific version of the code is used.

Discussions [1,2] already mentioned module-info stuff, but at that time,
extensibility techniques and extension popularity were low, and it wasn't
necessary to provide that data.

[1] https://www.postgresql.org/message-id/flat/20060507211705.GB3808%40svana.org
[2] https://www.postgresql.org/message-id/flat/20051106162658.34c31d57%40thunder.logicalchaos.org
---
 contrib/auto_explain/auto_explain.c           |  2 +
 contrib/auto_explain/t/001_auto_explain.pl    |  9 +++
 contrib/pg_prewarm/t/001_basic.pl             | 10 ++-
 .../pg_stat_statements/pg_stat_statements.c   |  1 +
 src/backend/utils/fmgr/dfmgr.c                | 73 ++++++++++++++++++-
 src/include/catalog/pg_proc.dat               |  6 ++
 src/include/fmgr.h                            | 18 +++++
 7 files changed, 117 insertions(+), 2 deletions(-)

diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index 623a674f99..f4110eb1aa 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -22,6 +22,8 @@
 
 PG_MODULE_MAGIC;
 
+PG_MODULE_INFO("auto_explain", 1000000);
+
 /* GUC variables */
 static int	auto_explain_log_min_duration = -1; /* msec or -1 */
 static int	auto_explain_log_parameter_max_length = -1; /* bytes or -1 */
diff --git a/contrib/auto_explain/t/001_auto_explain.pl b/contrib/auto_explain/t/001_auto_explain.pl
index 0e5b34afa9..d60c2cbbf1 100644
--- a/contrib/auto_explain/t/001_auto_explain.pl
+++ b/contrib/auto_explain/t/001_auto_explain.pl
@@ -53,6 +53,15 @@ like(
 	qr/Seq Scan on pg_class/,
 	"sequential scan logged, text mode");
 
+$log_contents = $node->safe_psql(
+	"postgres", q{SELECT substring(libname, 'auto_explain'),version
+				  FROM module_info('auto_explain');});
+
+like(
+	$log_contents,
+	qr/auto_explain|1000000/,
+	"Check module version");
+
 # Prepared query.
 $log_contents = query_log($node,
 	q{PREPARE get_proc(name) AS SELECT * FROM pg_proc WHERE proname = $1; EXECUTE get_proc('int4pl');}
diff --git a/contrib/pg_prewarm/t/001_basic.pl b/contrib/pg_prewarm/t/001_basic.pl
index 825d3448ee..141b8b7d7d 100644
--- a/contrib/pg_prewarm/t/001_basic.pl
+++ b/contrib/pg_prewarm/t/001_basic.pl
@@ -25,8 +25,16 @@ $node->safe_psql("postgres",
 	  . "CREATE TABLE test(c1 int);\n"
 	  . "INSERT INTO test SELECT generate_series(1, 100);");
 
-# test read mode
+# test empty module info
 my $result =
+  $node->safe_psql("postgres", "SELECT * FROM module_info('fake_module');");
+like($result, qr/|/, 'Return null if module does not exist');
+$result =
+  $node->safe_psql("postgres", "SELECT * FROM module_info('pg_prewarm');");
+like($result, qr/|/, 'Return null if module does not provide an info');
+
+# test read mode
+$result =
   $node->safe_psql("postgres", "SELECT pg_prewarm('test', 'read');");
 like($result, qr/^[1-9][0-9]*$/, 'read mode succeeded');
 
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 49c657b3e0..0afd23211a 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -72,6 +72,7 @@
 #include "utils/timestamp.h"
 
 PG_MODULE_MAGIC;
+PG_MODULE_INFO("pg_stat_statements", 010000);
 
 /* Location of permanent stats file (valid when database is shut down) */
 #define PGSS_DUMP_FILE	PGSTAT_STAT_PERMANENT_DIRECTORY "/pg_stat_statements.stat"
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index c7aa789b51..5931c30502 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -31,10 +31,12 @@
 #endif							/* !WIN32 */
 
 #include "fmgr.h"
+#include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
+#include "utils/builtins.h"
 #include "utils/hsearch.h"
 
 
@@ -61,7 +63,9 @@ typedef struct df_files
 	ino_t		inode;			/* Inode number of file */
 #endif
 	void	   *handle;			/* a handle for pg_dl* functions */
-	char		filename[FLEXIBLE_ARRAY_MEMBER];	/* Full pathname of file */
+	char		name[NAMEDATALEN];
+	int			version;
+	char		filename[256];	/* Full pathname of file */
 } DynamicFileList;
 
 static DynamicFileList *file_list = NULL;
@@ -185,6 +189,7 @@ internal_load_library(const char *libname)
 {
 	DynamicFileList *file_scanner;
 	PGModuleMagicFunction magic_func;
+	PGModuleInfoFunction minfo_func;
 	char	   *load_error;
 	struct stat stat_buf;
 	PG_init_t	PG_init;
@@ -281,6 +286,26 @@ internal_load_library(const char *libname)
 					 errhint("Extension libraries are required to use the PG_MODULE_MAGIC macro.")));
 		}
 
+		/*
+		 * Module info is an optional block. The PG_MODULE_MAGIC machinery
+		 * already checked compatibility of the module and the core codes. So,
+		 * we doesn't need to be too careful here.
+		 */
+		minfo_func = (PGModuleInfoFunction)
+			dlsym(file_scanner->handle, PG_MODULEINFO_FUNCTION_NAME_STRING);
+		if (minfo_func)
+		{
+			const pg_minfo_struct *minfo = (*minfo_func) ();
+
+			file_scanner->version = minfo->ver;
+			strcpy(file_scanner->name, minfo->name);
+		}
+		else
+		{
+			/* That means the library doesn't provide module info so far. */
+			file_scanner->version = -1;
+		}
+
 		/*
 		 * If the library has a _PG_init() function, call it.
 		 */
@@ -685,3 +710,49 @@ RestoreLibraryState(char *start_address)
 		start_address += strlen(start_address) + 1;
 	}
 }
+
+Datum
+module_info(PG_FUNCTION_ARGS)
+{
+	FuncCallContext	   *funcctx;
+	MemoryContext		oldcontext;
+	char			   *module_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	DynamicFileList	   *file_scanner;
+	TupleDesc			tupdesc;
+	List			   *modlst = NIL;
+	Datum				result;
+	Datum				values[2];
+	bool				isnull[2] = {0,0};
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		funcctx = SRF_FIRSTCALL_INIT();
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+
+		for (file_scanner = file_list; file_scanner != NULL;
+			file_scanner = file_scanner->next)
+		{
+			if (strcmp(module_name, file_scanner->name) == 0)
+				modlst = lappend(modlst, file_scanner);
+		}
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	funcctx = SRF_PERCALL_SETUP();
+
+	if (modlst != NIL)
+	{
+		file_scanner = (DynamicFileList *) llast(modlst);
+		values[0] = CStringGetTextDatum(file_scanner->filename);
+		values[1] = Int32GetDatum(file_scanner->version);
+		result = HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
+		modlst = list_delete_last(modlst);
+		SRF_RETURN_NEXT(funcctx, result);
+	}
+	else
+		SRF_RETURN_DONE(funcctx);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cbbe8acd38..9f37c4fc1e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -263,6 +263,12 @@
 { oid => '89', descr => 'PostgreSQL version string',
   proname => 'version', provolatile => 's', prorettype => 'text',
   proargtypes => '', prosrc => 'pgsql_version' },
+{ oid => '111', descr => 'Module Info',
+  proname => 'module_info', provolatile => 's', prorettype => 'record',
+  proretset => 't', proargtypes => 'text', proallargtypes => '{text,text,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{module_name,libname,version}',
+  prosrc => 'module_info' },
 
 { oid => '86', descr => 'I/O',
   proname => 'pg_ddl_command_in', prorettype => 'pg_ddl_command',
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 1e3795de4a..21b186a17e 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -483,6 +483,12 @@ typedef struct
 	FMGR_ABI_EXTRA, \
 }
 
+typedef struct pg_minfo_struct
+{
+	char	name[NAMEDATALEN];
+	int32	ver;
+} pg_minfo_struct;
+
 StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_magic_struct *) 0)->abi_extra),
 				 "FMGR_ABI_EXTRA too long");
 
@@ -492,8 +498,12 @@ StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_magic_struct *) 0)->abi_e
  */
 typedef const Pg_magic_struct *(*PGModuleMagicFunction) (void);
 
+typedef const pg_minfo_struct *(*PGModuleInfoFunction) (void);
+
 #define PG_MAGIC_FUNCTION_NAME Pg_magic_func
 #define PG_MAGIC_FUNCTION_NAME_STRING "Pg_magic_func"
+#define PG_MODULEINFO_FUNCTION_NAME pg_module_info
+#define PG_MODULEINFO_FUNCTION_NAME_STRING "pg_module_info"
 
 #define PG_MODULE_MAGIC \
 extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
@@ -505,6 +515,14 @@ PG_MAGIC_FUNCTION_NAME(void) \
 } \
 extern int no_such_variable
 
+#define PG_MODULE_INFO(modulename, version) \
+extern PGDLLEXPORT const pg_minfo_struct *PG_MODULEINFO_FUNCTION_NAME(void); \
+const pg_minfo_struct * \
+PG_MODULEINFO_FUNCTION_NAME(void) \
+{ \
+	static const pg_minfo_struct module_info = {modulename, version}; \
+	return &module_info; \
+} \
 
 /*-------------------------------------------------------------------------
  *		Support routines and macros for callers of fmgr-compatible functions
-- 
2.47.1

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrei Lepikhov (#1)
Re: Add Postgres module info

Andrei Lepikhov <lepihov@gmail.com> writes:

I would like to propose the module_info structure, which aims to let
extension maintainers sew some data into the binary file. Being included
in the module code, this information remains unchanged and is available
for reading by a backend.

I don't have much of an opinion one way or the other about the
usefulness of these additional info fields. But I would like to
object to the way you've gone about it, namely to copy-and-paste
the magic-block mechanism. That doesn't scale: the next time
somebody else wants some more fields, will we have three such
structs?

The approach we foresaw using was that we could simply add more
fields to Pg_magic_struct (obviously, only in a major version).
That's happened at least once already - abi_extra was not there
to begin with.

There are a couple of ways that we could deal with the API
seen by module authors:

1. The PG_MODULE_MAGIC macro keeps the same API and leaves the
additional field(s) empty. Authors who want to fill the
extra field(s) use a new macro, say PG_MODULE_MAGIC_V2.

2. PG_MODULE_MAGIC gains some arguments, forcing everybody
to change their code. While this would be annoying, it'd be
within our compatibility rules for a major version update.
I wouldn't do it though unless there were a compelling reason
why everybody should fill these fields.

3. Maybe we could do something with making PG_MODULE_MAGIC
variadic, but I've not thought hard about what that could
look like. In any case it'd only be a cosmetic improvement
over the above ways.

4. The extra fields are filled indirectly by macros that
extension authors can optionally provide (a variant on the
FMGR_ABI_EXTRA mechanism). This would be code-order-sensitive
so I'm not sure it's really a great idea.

5. Something I didn't think of?

With any of these except #4, authors who want their source code to
support multiple PG major versions would be forced into using #if
tests on CATALOG_VERSION_NO to decide what to write. That's a
bit annoying but hardly unusual.

regards, tom lane

#3Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#2)
Re: Add Postgres module info

Hi,

On 2024-12-11 13:21:03 -0500, Tom Lane wrote:

Andrei Lepikhov <lepihov@gmail.com> writes:

I would like to propose the module_info structure, which aims to let
extension maintainers sew some data into the binary file. Being included
in the module code, this information remains unchanged and is available
for reading by a backend.

I don't have much of an opinion one way or the other about the
usefulness of these additional info fields.

FWIW, Id like to have some more information in there, without commenting on
the specifics.

But I would like to object to the way you've gone about it, namely to
copy-and-paste the magic-block mechanism. That doesn't scale: the next time
somebody else wants some more fields, will we have three such structs?

I agree with that.

The approach we foresaw using was that we could simply add more
fields to Pg_magic_struct (obviously, only in a major version).
That's happened at least once already - abi_extra was not there
to begin with.

There are a couple of ways that we could deal with the API
seen by module authors:

1. The PG_MODULE_MAGIC macro keeps the same API and leaves the
additional field(s) empty. Authors who want to fill the
extra field(s) use a new macro, say PG_MODULE_MAGIC_V2.

2. PG_MODULE_MAGIC gains some arguments, forcing everybody
to change their code. While this would be annoying, it'd be
within our compatibility rules for a major version update.
I wouldn't do it though unless there were a compelling reason
why everybody should fill these fields.

I'd like to avoid needing to do this again if / when we invent the next set of
optional arguments. So just having a different macro with a hardcoded set of
arguments or changing PG_MODULE_MAGIC to have a hardcoded set of arguments
doesn't seem great.

To be future proof, I think it'd be good to declare the arguments as
designated initializers. E.g. like

PG_MODULE_MAGIC_EXT(
.version = 10000,
.threadsafe = 1
);

where the macro would turn the arguments into a struct initializer inside
Pg_magic_struct.

That way we can add/remove arguments and only extensions that use
removed arguments need to change.

3. Maybe we could do something with making PG_MODULE_MAGIC
variadic, but I've not thought hard about what that could
look like. In any case it'd only be a cosmetic improvement
over the above ways.

Yea, it'd be nice to avoid needing an _EXT or _V2. But I can't immediately
think of a way that allows a macro with no arguments and and an argument.

4. The extra fields are filled indirectly by macros that
extension authors can optionally provide (a variant on the
FMGR_ABI_EXTRA mechanism). This would be code-order-sensitive
so I'm not sure it's really a great idea.

Agreed.

With any of these except #4, authors who want their source code to
support multiple PG major versions would be forced into using #if
tests on CATALOG_VERSION_NO to decide what to write. That's a
bit annoying but hardly unusual.

#2 would be bit more annoying than #1, I'd say, because it'd affect every
single extension, even ones not interested in any of this.

Greetings,

Andres Freund

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#3)
Re: Add Postgres module info

Andres Freund <andres@anarazel.de> writes:

On 2024-12-11 13:21:03 -0500, Tom Lane wrote:

There are a couple of ways that we could deal with the API
seen by module authors:

To be future proof, I think it'd be good to declare the arguments as
designated initializers. E.g. like

PG_MODULE_MAGIC_EXT(
.version = 10000,
.threadsafe = 1
);

Yeah, I'd come to pretty much the same conclusion after sending
my email. That looks like it should work and be convenient
to extend further.

The other possibly-non-obvious bit is that we should probably
invent a sub-structure holding the ABI-related fields, so as to
minimize the amount of rewriting needed in dfmgr.c.

regards, tom lane

#5Euler Taveira
euler@eulerto.com
In reply to: Andres Freund (#3)
Re: Add Postgres module info

On Wed, Dec 11, 2024, at 4:26 PM, Andres Freund wrote:

On 2024-12-11 13:21:03 -0500, Tom Lane wrote:

Andrei Lepikhov <lepihov@gmail.com> writes:

I would like to propose the module_info structure, which aims to let
extension maintainers sew some data into the binary file. Being included
in the module code, this information remains unchanged and is available
for reading by a backend.

I don't have much of an opinion one way or the other about the
usefulness of these additional info fields.

FWIW, Id like to have some more information in there, without commenting on
the specifics.

+1 for the general idea. I received some reports like [1]https://github.com/eulerto/wal2json/issues/181 related to wal2json
that people wants to obtain the output plugin version. Since it is not installed
via CREATE EXTENSION, it is not possible to detect what version is installed,
hence, some tools cannot have some logic to probe the module version. In a
managed environment, it is hard to figure out the exact version for
non-CREATE-EXTENSION modules, unless it is explicitly informed by the vendor.

[1]: https://github.com/eulerto/wal2json/issues/181

--
Euler Taveira
EDB https://www.enterprisedb.com/

#6Andrei Lepikhov
lepihov@gmail.com
In reply to: Tom Lane (#2)
Re: Add Postgres module info

On 12/12/2024 01:21, Tom Lane wrote:

Andrei Lepikhov <lepihov@gmail.com> writes:

I would like to propose the module_info structure, which aims to let
extension maintainers sew some data into the binary file. Being included
in the module code, this information remains unchanged and is available
for reading by a backend.

I don't have much of an opinion one way or the other about the
usefulness of these additional info fields. But I would like to
object to the way you've gone about it, namely to copy-and-paste
the magic-block mechanism. That doesn't scale: the next time
somebody else wants some more fields, will we have three such
structs?

It makes sense. But I want to clarify that I avoided changing
PG_MODULE_MAGIC because the newly introduced structure has a totally
different purpose and usage logic: the struct is designed to check
compatibility, but module info isn't connected to the core version at
all: a single version of the code may be built for multiple PG versions.
At the same time, various versions of the same library may be usable
with the same core.

From the coding point of view, I agree that your approach is more
laconic and reasonable. I will rewrite the code using this approach.

--
regards, Andrei Lepikhov

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Euler Taveira (#5)
Re: Add Postgres module info

"Euler Taveira" <euler@eulerto.com> writes:

+1 for the general idea. I received some reports like [1] related to wal2json
that people wants to obtain the output plugin version. Since it is not installed
via CREATE EXTENSION, it is not possible to detect what version is installed,
hence, some tools cannot have some logic to probe the module version. In a
managed environment, it is hard to figure out the exact version for
non-CREATE-EXTENSION modules, unless it is explicitly informed by the vendor.

What would you foresee as the SQL API for inspecting a module that's
not tied to an extension?

regards, tom lane

#8Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrei Lepikhov (#6)
Re: Add Postgres module info

Andrei Lepikhov <lepihov@gmail.com> writes:

It makes sense. But I want to clarify that I avoided changing
PG_MODULE_MAGIC because the newly introduced structure has a totally
different purpose and usage logic: the struct is designed to check
compatibility, but module info isn't connected to the core version at
all: a single version of the code may be built for multiple PG versions.
At the same time, various versions of the same library may be usable
with the same core.

Surely. But I don't see a need for two separately-looked-up
physical structures. Seems to me it's sufficient to put the
ABI-checking fields into a sub-struct within the magic block.

regards, tom lane

#9Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#7)
Re: Add Postgres module info

On Wed, Dec 11, 2024 at 08:34:28PM -0500, Tom Lane wrote:

"Euler Taveira" <euler@eulerto.com> writes:

+1 for the general idea. I received some reports like [1] related to wal2json
that people wants to obtain the output plugin version. Since it is not installed
via CREATE EXTENSION, it is not possible to detect what version is installed,
hence, some tools cannot have some logic to probe the module version. In a
managed environment, it is hard to figure out the exact version for
non-CREATE-EXTENSION modules, unless it is explicitly informed by the vendor.

What would you foresee as the SQL API for inspecting a module that's
not tied to an extension?

Rather than a function that can be called with a specific module name
in input, invent a new system SRF function that would report back for
a process all the libraries that have been loaded in it? Presumably,
the extra tracking can be done in dfmgr.c with more fields added to
DynamicFileList to track the information involved.

Being able to print the information of DynamicFileList can be argued
as useful on its own as long as its execution can be granted, with
superuser right by default.
--
Michael

#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#9)
Re: Add Postgres module info

Michael Paquier <michael@paquier.xyz> writes:

On Wed, Dec 11, 2024 at 08:34:28PM -0500, Tom Lane wrote:

What would you foresee as the SQL API for inspecting a module that's
not tied to an extension?

Rather than a function that can be called with a specific module name
in input, invent a new system SRF function that would report back for
a process all the libraries that have been loaded in it?

Yeah, that could work.

Presumably,
the extra tracking can be done in dfmgr.c with more fields added to
DynamicFileList to track the information involved.

I wouldn't add any overhead to the normal case for this. Couldn't
we walk the list and re-fetch each module's magic block inside
this new function?

regards, tom lane

#11Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#10)
Re: Add Postgres module info

On Wed, Dec 11, 2024 at 10:39:38PM -0500, Tom Lane wrote:

Michael Paquier <michael@paquier.xyz> writes:

Presumably,
the extra tracking can be done in dfmgr.c with more fields added to
DynamicFileList to track the information involved.

I wouldn't add any overhead to the normal case for this. Couldn't
we walk the list and re-fetch each module's magic block inside
this new function?

Depends on how much we should try to cache to make that less expensive
on repeated calls because we cannot unload libraries, but sure, I
don't see why we could not that for each SQL function call to simplify
the logic and the structures in place.
--
Michael

#12Andrei Lepikhov
lepihov@gmail.com
In reply to: Michael Paquier (#11)
Re: Add Postgres module info

On 12/12/24 10:44, Michael Paquier wrote:

On Wed, Dec 11, 2024 at 10:39:38PM -0500, Tom Lane wrote:

Michael Paquier <michael@paquier.xyz> writes:

Presumably,
the extra tracking can be done in dfmgr.c with more fields added to
DynamicFileList to track the information involved.

I wouldn't add any overhead to the normal case for this. Couldn't
we walk the list and re-fetch each module's magic block inside
this new function?

Depends on how much we should try to cache to make that less expensive
on repeated calls because we cannot unload libraries, but sure, I
don't see why we could not that for each SQL function call to simplify
the logic and the structures in place.

I want to say that 'cannot unload libraries' is a negative outcome of
the architecture. It would be better to invent something like
PG_unregister, allowing libraries to at least return a hook routine call
back to the system.
So, maybe it makes sense to design this feature with re-fetching
libraries, supposing it is already implemented somehow and elements of
the DynamicFileList may be removed.

--
regards, Andrei Lepikhov

#13Andrei Lepikhov
lepihov@gmail.com
In reply to: Tom Lane (#8)
1 attachment(s)
Re: Add Postgres module info

On 12/12/24 08:36, Tom Lane wrote:

Andrei Lepikhov <lepihov@gmail.com> writes:

It makes sense. But I want to clarify that I avoided changing
PG_MODULE_MAGIC because the newly introduced structure has a totally
different purpose and usage logic: the struct is designed to check
compatibility, but module info isn't connected to the core version at
all: a single version of the code may be built for multiple PG versions.
At the same time, various versions of the same library may be usable
with the same core.

Surely. But I don't see a need for two separately-looked-up
physical structures. Seems to me it's sufficient to put the
ABI-checking fields into a sub-struct within the magic block.

Okay, I've rewritten the patch to understand how it works. It seems to
work pretty well. I added separate fields for minor and major versions.

--
regards, Andrei Lepikhov

Attachments:

v1-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchtext/x-patch; charset=UTF-8; name=v1-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchDownload
From 0bc431ce0ababc9fe5492773205525a6839f1264 Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepihov@gmail.com>
Date: Tue, 19 Nov 2024 18:45:36 +0700
Subject: [PATCH v1] Introduce PG_MODULE_MAGIC_EXT macro.

This macro allows dynamically loaded shared libraries (modules) a standard way
to incorporate version (major and minor) and name data. The introduced
catalogue routine module_info can be used to find this module by name and check
the version. It makes users independent from file naming conventions.

With a growing number of Postgres core hooks and the introduction of named DSM
segments, the number of modules that don't need to be loaded on startup may
grow fast. Moreover, in many cases related to query tree transformation or
extra path recommendation, such modules might not need database objects except
GUCs - see auto_explain as an example. That means they don't need to execute
the 'CREATE EXTENSION' statement at all and don't have a record in
the pg_extension table. Such a trick provides much flexibility, including
an online upgrade and may become widespread.

In addition, it is also convenient in support to be sure that the installation
(or at least the backend) includes a specific version of the module. Even if
a module has an installation script, it is not rare that it provides
an implementation for a range of UI routine versions. It makes sense to ensure
which specific version of the code is used.

Discussions [1,2] already mentioned module-info stuff, but at that time,
extensibility techniques and extension popularity were low, and it wasn't
necessary to provide that data.

[1] https://www.postgresql.org/message-id/flat/20060507211705.GB3808%40svana.org
[2] https://www.postgresql.org/message-id/flat/20051106162658.34c31d57%40thunder.logicalchaos.org
---
 contrib/auto_explain/auto_explain.c           |  2 +-
 contrib/auto_explain/t/001_auto_explain.pl    |  9 +++
 contrib/pg_prewarm/t/001_basic.pl             |  8 ++-
 .../pg_stat_statements/pg_stat_statements.c   |  2 +-
 src/backend/utils/fmgr/dfmgr.c                | 72 ++++++++++++++++++-
 src/include/catalog/pg_proc.dat               |  6 ++
 src/include/fmgr.h                            | 24 ++++++-
 7 files changed, 116 insertions(+), 7 deletions(-)

diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index f2eaa8e494..f482ab125d 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -20,7 +20,7 @@
 #include "executor/instrument.h"
 #include "utils/guc.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT("auto_explain", 1, 0);
 
 /* GUC variables */
 static int	auto_explain_log_min_duration = -1; /* msec or -1 */
diff --git a/contrib/auto_explain/t/001_auto_explain.pl b/contrib/auto_explain/t/001_auto_explain.pl
index 0e5b34afa9..5db7df5478 100644
--- a/contrib/auto_explain/t/001_auto_explain.pl
+++ b/contrib/auto_explain/t/001_auto_explain.pl
@@ -53,6 +53,15 @@ like(
 	qr/Seq Scan on pg_class/,
 	"sequential scan logged, text mode");
 
+$log_contents = $node->safe_psql(
+	"postgres", q{SELECT substring(libname, 'auto_explain'),module_name,major_ver,minor_ver
+				  FROM module_info() WHERE major_ver IS NOT NULL;});
+
+like(
+	$log_contents,
+	qr/auto_explain|auto_explain|1|0/,
+	"Check module version");
+
 # Prepared query.
 $log_contents = query_log($node,
 	q{PREPARE get_proc(name) AS SELECT * FROM pg_proc WHERE proname = $1; EXECUTE get_proc('int4pl');}
diff --git a/contrib/pg_prewarm/t/001_basic.pl b/contrib/pg_prewarm/t/001_basic.pl
index 825d3448ee..f0197e33af 100644
--- a/contrib/pg_prewarm/t/001_basic.pl
+++ b/contrib/pg_prewarm/t/001_basic.pl
@@ -25,8 +25,14 @@ $node->safe_psql("postgres",
 	  . "CREATE TABLE test(c1 int);\n"
 	  . "INSERT INTO test SELECT generate_series(1, 100);");
 
-# test read mode
+# test empty module info
 my $result =
+  $node->safe_psql("postgres", "SELECT substring(libname, 'pg_prewarm'),
+  						module_name,major_ver,minor_ver FROM module_info();");
+like($result, qr/pg_prewarm|||/, 'Return null if module does not provide an info');
+
+# test read mode
+$result =
   $node->safe_psql("postgres", "SELECT pg_prewarm('test', 'read');");
 like($result, qr/^[1-9][0-9]*$/, 'read mode succeeded');
 
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 602cae54ff..24374e9209 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -71,7 +71,7 @@
 #include "utils/memutils.h"
 #include "utils/timestamp.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT("pg_stat_statements", 1, 12);
 
 /* Location of permanent stats file (valid when database is shut down) */
 #define PGSS_DUMP_FILE	PGSTAT_STAT_PERMANENT_DIRECTORY "/pg_stat_statements.stat"
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 8e81ecc749..e11f9d20a6 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -21,10 +21,12 @@
 #endif							/* !WIN32 */
 
 #include "fmgr.h"
+#include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
+#include "utils/builtins.h"
 #include "utils/hsearch.h"
 
 
@@ -51,6 +53,9 @@ typedef struct df_files
 	ino_t		inode;			/* Inode number of file */
 #endif
 	void	   *handle;			/* a handle for pg_dl* functions */
+	char		name[NAMEDATALEN];
+	int32		major_ver;
+	int32		minor_ver;
 	char		filename[FLEXIBLE_ARRAY_MEMBER];	/* Full pathname of file */
 } DynamicFileList;
 
@@ -75,7 +80,7 @@ static char *substitute_libpath_macro(const char *name);
 static char *find_in_dynamic_libpath(const char *basename);
 
 /* Magic structure that module needs to match to be accepted */
-static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
+static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA({0});
 
 
 /*
@@ -246,7 +251,8 @@ internal_load_library(const char *libname)
 			const Pg_magic_struct *magic_data_ptr = (*magic_func) ();
 
 			if (magic_data_ptr->len != magic_data.len ||
-				memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0)
+				memcmp(magic_data_ptr, &magic_data,
+					   offsetof(Pg_magic_struct, module_extra)) != 0)
 			{
 				/* copy data block before unlinking library */
 				Pg_magic_struct module_magic_data = *magic_data_ptr;
@@ -258,6 +264,20 @@ internal_load_library(const char *libname)
 				/* issue suitable complaint */
 				incompatible_module_error(libname, &module_magic_data);
 			}
+
+			if (magic_data_ptr->module_extra.name[0] != '\0')
+			{
+				strcpy(file_scanner->name, magic_data_ptr->module_extra.name);
+				file_scanner->major_ver = magic_data_ptr->module_extra.major_ver;
+				file_scanner->minor_ver = magic_data_ptr->module_extra.minor_ver;
+			}
+			else
+			{
+				/*
+				 * No need additional efforts. Zero-filled data serves as is in
+				 * the 'not initialized' state
+				 */
+			}
 		}
 		else
 		{
@@ -675,3 +695,51 @@ RestoreLibraryState(char *start_address)
 		start_address += strlen(start_address) + 1;
 	}
 }
+
+Datum
+module_info(PG_FUNCTION_ARGS)
+{
+	FuncCallContext	   *funcctx;
+	MemoryContext		oldcontext;
+	DynamicFileList	   *file_scanner = NULL;
+	TupleDesc			tupdesc;
+	Datum				result;
+	Datum				values[4];
+	bool				isnull[4] = {0,0,0,0};
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		funcctx = SRF_FIRSTCALL_INIT();
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+
+		file_scanner = file_list;
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	funcctx = SRF_PERCALL_SETUP();
+
+	if (file_scanner != NULL)
+	{
+		if (file_scanner->name[0] == '\0')
+		{
+			isnull[0] = isnull[1] = isnull[2] = true;
+		}
+		else
+		{
+			values[0] = CStringGetTextDatum(file_scanner->name);
+			values[1] = Int32GetDatum(file_scanner->major_ver);
+			values[2] = Int32GetDatum(file_scanner->minor_ver);
+		}
+
+		values[3] = CStringGetTextDatum(file_scanner->filename);
+		result = HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
+		file_scanner = file_scanner->next;
+		SRF_RETURN_NEXT(funcctx, result);
+	}
+	else
+		SRF_RETURN_DONE(funcctx);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0f22c21723..8638b5842c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -311,6 +311,12 @@
   proname => 'scalargtjoinsel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int2 internal',
   prosrc => 'scalargtjoinsel' },
+{ oid => '111', descr => 'Module Info',
+  proname => 'module_info', provolatile => 's', prorettype => 'record',
+  proretset => 't', proargtypes => '', proallargtypes => '{text,int4,int4,text}',
+  proargmodes => '{o,o,o,o}', prorows => 10,
+  proargnames => '{module_name,major_ver,minor_ver,libname}',
+  prosrc => 'module_info' },
 
 { oid => '336',
   descr => 'restriction selectivity of <= and related operators on scalar datatypes',
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 1e3795de4a..de1ce3401f 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -459,6 +459,13 @@ extern PGDLLEXPORT void _PG_init(void);
  *-------------------------------------------------------------------------
  */
 
+typedef struct pg_module_info
+{
+	char	name[NAMEDATALEN];
+	int32	major_ver;
+	int32	minor_ver;
+} pg_module_info;
+
 /* Definition of the magic block structure */
 typedef struct
 {
@@ -469,10 +476,12 @@ typedef struct
 	int			namedatalen;	/* NAMEDATALEN */
 	int			float8byval;	/* FLOAT8PASSBYVAL */
 	char		abi_extra[32];	/* see pg_config_manual.h */
+	pg_module_info module_extra;
 } Pg_magic_struct;
 
 /* The actual data block contents */
-#define PG_MODULE_MAGIC_DATA \
+/* Fill the module info part with zeros by default. Zero-length module name indicates that it is not initialised */
+#define PG_MODULE_MAGIC_DATA(...) \
 { \
 	sizeof(Pg_magic_struct), \
 	PG_VERSION_NUM / 100, \
@@ -481,6 +490,7 @@ typedef struct
 	NAMEDATALEN, \
 	FLOAT8PASSBYVAL, \
 	FMGR_ABI_EXTRA, \
+	{__VA_ARGS__}, \
 }
 
 StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_magic_struct *) 0)->abi_extra),
@@ -500,11 +510,21 @@ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
 const Pg_magic_struct * \
 PG_MAGIC_FUNCTION_NAME(void) \
 { \
-	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \
+	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA({0}); \
 	return &Pg_magic_data; \
 } \
 extern int no_such_variable
 
+#define PG_MODULE_MAGIC_EXT(...) \
+extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
+const Pg_magic_struct * \
+PG_MAGIC_FUNCTION_NAME(void) \
+{ \
+	static const Pg_magic_struct Pg_magic_data = \
+		PG_MODULE_MAGIC_DATA(__VA_ARGS__); \
+	return &Pg_magic_data; \
+} \
+extern int no_such_variable
 
 /*-------------------------------------------------------------------------
  *		Support routines and macros for callers of fmgr-compatible functions
-- 
2.39.5

#14Yurii Rashkovskii
yrashk@omnigres.com
In reply to: Andrei Lepikhov (#13)
Re: Add Postgres module info

On Thu, Dec 12, 2024 at 3:41 PM Andrei Lepikhov <lepihov@gmail.com> wrote:

On 12/12/24 08:36, Tom Lane wrote:

Andrei Lepikhov <lepihov@gmail.com> writes:

It makes sense. But I want to clarify that I avoided changing
PG_MODULE_MAGIC because the newly introduced structure has a totally
different purpose and usage logic: the struct is designed to check
compatibility, but module info isn't connected to the core version at
all: a single version of the code may be built for multiple PG versions.
At the same time, various versions of the same library may be usable
with the same core.

Surely. But I don't see a need for two separately-looked-up
physical structures. Seems to me it's sufficient to put the
ABI-checking fields into a sub-struct within the magic block.

Okay, I've rewritten the patch to understand how it works. It seems to
work pretty well. I added separate fields for minor and major versions.

I am keenly interested in helping in this area; as you have mentioned, I've
done similar work using an extension.

Some thoughts/questions:

1. Do we need to latch onto the "magic" structure here? Have we considered
an opportunity to create a separate metadata slot that looks something like
`PG_MODULE_INFO(.version = ...)`. My impression of module magic was that it
should rather be populated during the build – to provide build-time
information. MODULE_INFO would be a rather informational section supplied
by the developer.

2. Any reasons to dictate MAJ.MIN format? With semantic versioning abound,
it's rather common to use MAJ.MIN.PATCH. There are also other extensions to
it (like pre-releases, builds, etc.). All of these indicate distinct
versions. The differences between them can be figured out using semver or
other parsers. Pure PL/pgSQL implementations of that exist [1]https://github.com/bigsmoke/pg_text_semver.

3. In my work, I also introduced the concept of stable module identity – a
unique string (for example, UUID) that represents the identity of the
module even if its name is going to change. Admittedly, this is not _the
most common_ type of problem, but I anticipate it becoming more of an issue
with the growth of the extension ecosystem, potential name clashes, and
renamings. With this approach, developers assign this unique string to a
module once at the beginning and never change it. Have you considered this?

[1]: https://github.com/bigsmoke/pg_text_semver

#15Andres Freund
andres@anarazel.de
In reply to: Andrei Lepikhov (#12)
Re: Add Postgres module info

Hi,

On 2024-12-12 11:35:56 +0700, Andrei Lepikhov wrote:

On 12/12/24 10:44, Michael Paquier wrote:

On Wed, Dec 11, 2024 at 10:39:38PM -0500, Tom Lane wrote:

Michael Paquier <michael@paquier.xyz> writes:

Presumably,
the extra tracking can be done in dfmgr.c with more fields added to
DynamicFileList to track the information involved.

I wouldn't add any overhead to the normal case for this. Couldn't
we walk the list and re-fetch each module's magic block inside
this new function?

Depends on how much we should try to cache to make that less expensive
on repeated calls because we cannot unload libraries, but sure, I
don't see why we could not that for each SQL function call to simplify
the logic and the structures in place.

I want to say that 'cannot unload libraries' is a negative outcome of the
architecture. It would be better to invent something like PG_unregister,
allowing libraries to at least return a hook routine call back to the
system.
So, maybe it makes sense to design this feature with re-fetching libraries,
supposing it is already implemented somehow and elements of the
DynamicFileList may be removed.

I am quite certain we'll not support unloading libraries anytime soon. We used
to support it and it caused problems... Changing anything about how exactly
things are tracked in dfmgr.c will be the smallest part of supporting
unloading libraries again.

Greetings,

Andres Freund

#16Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#15)
Re: Add Postgres module info

Andres Freund <andres@anarazel.de> writes:

On 2024-12-12 11:35:56 +0700, Andrei Lepikhov wrote:

I want to say that 'cannot unload libraries' is a negative outcome of the
architecture. It would be better to invent something like PG_unregister,
allowing libraries to at least return a hook routine call back to the
system.

I am quite certain we'll not support unloading libraries anytime soon. We used
to support it and it caused problems... Changing anything about how exactly
things are tracked in dfmgr.c will be the smallest part of supporting
unloading libraries again.

Indeed. However, I don't see what that has to do with the current
discussion anyway. The proposed SRF would iterate through whatever
is in the DynamicFileList. What does it care whether there's a way
to add or remove entries?

regards, tom lane

#17Andrei Lepikhov
lepihov@gmail.com
In reply to: Yurii Rashkovskii (#14)
Re: Add Postgres module info

On 12/12/24 21:02, Yurii Rashkovskii wrote:

On Thu, Dec 12, 2024 at 3:41 PM Andrei Lepikhov <lepihov@gmail.com
<mailto:lepihov@gmail.com>> wrote:

On 12/12/24 08:36, Tom Lane wrote:

Andrei Lepikhov <lepihov@gmail.com <mailto:lepihov@gmail.com>>

writes:

It makes sense. But I want to clarify that I avoided changing
PG_MODULE_MAGIC because the newly introduced structure has a totally
different purpose and usage logic: the struct is designed to check
compatibility, but module info isn't connected to the core

version at

all: a single version of the code may be built for multiple PG

versions.

At the same time, various versions of the same library may be usable
with the same core.

Surely.  But I don't see a need for two separately-looked-up
physical structures.  Seems to me it's sufficient to put the
ABI-checking fields into a sub-struct within the magic block.

Okay, I've rewritten the patch to understand how it works. It seems to
work pretty well. I added separate fields for minor and major versions.

I am keenly interested in helping in this area; as you have mentioned,
I've done similar work using an extension.

Some thoughts/questions:

1. Do we need to latch onto the "magic" structure here? Have we
considered an opportunity to create a separate metadata slot that looks
something like `PG_MODULE_INFO(.version = ...)`. My impression of module
magic was that it should rather be populated during the build – to
provide build-time information. MODULE_INFO would be a rather
informational section supplied by the developer.

It has already been debated above. I may agree with colleagues that
maintainer-provided information should be stored in the magic field to
reduce noise.
At the same time, we use a single code part to load all that data into
the DynamicFileList. That looks pretty well, isn't it?

2. Any reasons to dictate MAJ.MIN format? With semantic versioning
abound, it's rather common to use MAJ.MIN.PATCH. There are also other
extensions to it (like pre-releases, builds, etc.). All of these
indicate distinct versions. The differences between them can be figured
out using semver or other parsers. Pure PL/pgSQL implementations of that
exist [1].

Okay, thanks; that's a good catch. I wonder how to follow these rules
with a static fixed-sized structure. I would like to read about any
suggestions and implementation examples.

3. In my work, I also introduced the concept of stable module identity –
a unique string (for example, UUID) that represents the identity of the
module even if its name is going to change. Admittedly, this is not _the
most common_ type of problem, but I anticipate it becoming more of an
issue with the growth of the extension ecosystem, potential name
clashes, and renamings. With this approach, developers assign this
unique string to a module once at the beginning and never change it.
Have you considered this?

This option just needs some live examples. I think, if necessary, it
could be added later.

--
regards, Andrei Lepikhov

#18Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrei Lepikhov (#17)
Re: Add Postgres module info

Andrei Lepikhov <lepihov@gmail.com> writes:

On 12/12/24 21:02, Yurii Rashkovskii wrote:

2. Any reasons to dictate MAJ.MIN format? With semantic versioning
abound, it's rather common to use MAJ.MIN.PATCH.

Okay, thanks; that's a good catch. I wonder how to follow these rules
with a static fixed-sized structure. I would like to read about any
suggestions and implementation examples.

There's nothing stopping a field of the magic block from being
a "const char *" pointer to a string literal.

regards, tom lane

#19Andrei Lepikhov
lepihov@gmail.com
In reply to: Tom Lane (#18)
1 attachment(s)
Re: Add Postgres module info

On 12/13/24 10:17, Tom Lane wrote:

Andrei Lepikhov <lepihov@gmail.com> writes:

On 12/12/24 21:02, Yurii Rashkovskii wrote:

2. Any reasons to dictate MAJ.MIN format? With semantic versioning
abound, it's rather common to use MAJ.MIN.PATCH.

Okay, thanks; that's a good catch. I wonder how to follow these rules
with a static fixed-sized structure. I would like to read about any
suggestions and implementation examples.

There's nothing stopping a field of the magic block from being
a "const char *" pointer to a string literal.

Ok, See v.2 in attachment.

--
regards, Andrei Lepikhov

Attachments:

v2-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchtext/x-patch; charset=UTF-8; name=v2-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchDownload
From ca3957264a68dc7871b98ada4cdde1ee26aefade Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepihov@gmail.com>
Date: Tue, 19 Nov 2024 18:45:36 +0700
Subject: [PATCH v2] Introduce PG_MODULE_MAGIC_EXT macro.

This macro provides dynamically loaded shared libraries (modules) with standard
way to incorporate version (supposedly, defined according to semantic versioning
specification) and name data. The introduced catalogue routine module_info can
be used to find this module by name and check the version. It makes users
independent from file naming conventions.

With a growing number of Postgres core hooks and the introduction of named DSM
segments, the number of modules that don't need to be loaded on startup may
grow fast. Moreover, in many cases related to query tree transformation or
extra path recommendation, such modules might not need database objects except
GUCs - see auto_explain as an example. That means they don't need to execute
the 'CREATE EXTENSION' statement at all and don't have a record in
the pg_extension table. Such a trick provides much flexibility, including
an online upgrade and may become widespread.

In addition, it is also convenient in support to be sure that the installation
(or at least the backend) includes a specific version of the module. Even if
a module has an installation script, it is not rare that it provides
an implementation for a range of UI routine versions. It makes sense to ensure
which specific version of the code is used.

Discussions [1,2] already mentioned module-info stuff, but at that time,
extensibility techniques and extension popularity were low, and it wasn't
necessary to provide that data.

[1] https://www.postgresql.org/message-id/flat/20060507211705.GB3808%40svana.org
[2] https://www.postgresql.org/message-id/flat/20051106162658.34c31d57%40thunder.logicalchaos.org
---
 contrib/auto_explain/auto_explain.c        |  5 +-
 contrib/auto_explain/t/001_auto_explain.pl |  8 +++
 contrib/pg_prewarm/t/001_basic.pl          |  8 ++-
 src/backend/utils/fmgr/dfmgr.c             | 62 +++++++++++++++++++++-
 src/include/catalog/pg_proc.dat            |  6 +++
 src/include/fmgr.h                         | 28 ++++++++--
 6 files changed, 110 insertions(+), 7 deletions(-)

diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index f2eaa8e494..ab997830f3 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -20,7 +20,10 @@
 #include "executor/instrument.h"
 #include "utils/guc.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+	.name = "auto_explain",
+	.version = "1.0.0"
+);
 
 /* GUC variables */
 static int	auto_explain_log_min_duration = -1; /* msec or -1 */
diff --git a/contrib/auto_explain/t/001_auto_explain.pl b/contrib/auto_explain/t/001_auto_explain.pl
index 0e5b34afa9..81481606da 100644
--- a/contrib/auto_explain/t/001_auto_explain.pl
+++ b/contrib/auto_explain/t/001_auto_explain.pl
@@ -53,6 +53,14 @@ like(
 	qr/Seq Scan on pg_class/,
 	"sequential scan logged, text mode");
 
+$log_contents = $node->safe_psql(
+	"postgres", q{SELECT substring(libname, 'auto_explain'),module_name,version
+				  FROM module_info() WHERE version IS NOT NULL;});
+like(
+	$log_contents,
+	qr/auto_explain\|auto_explain\|1\.0\.0/,
+	"Check module version");
+
 # Prepared query.
 $log_contents = query_log($node,
 	q{PREPARE get_proc(name) AS SELECT * FROM pg_proc WHERE proname = $1; EXECUTE get_proc('int4pl');}
diff --git a/contrib/pg_prewarm/t/001_basic.pl b/contrib/pg_prewarm/t/001_basic.pl
index 825d3448ee..174822befc 100644
--- a/contrib/pg_prewarm/t/001_basic.pl
+++ b/contrib/pg_prewarm/t/001_basic.pl
@@ -25,8 +25,14 @@ $node->safe_psql("postgres",
 	  . "CREATE TABLE test(c1 int);\n"
 	  . "INSERT INTO test SELECT generate_series(1, 100);");
 
-# test read mode
+# test empty module info
 my $result =
+  $node->safe_psql("postgres", "SELECT substring(libname, 'pg_prewarm'),
+  						module_name,version FROM module_info();");
+like($result, qr/pg_prewarm\|\|/, 'Return null if module does not provide an info');
+
+# test read mode
+$result =
   $node->safe_psql("postgres", "SELECT pg_prewarm('test', 'read');");
 like($result, qr/^[1-9][0-9]*$/, 'read mode succeeded');
 
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 8e81ecc749..20cca3e3d5 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -21,10 +21,12 @@
 #endif							/* !WIN32 */
 
 #include "fmgr.h"
+#include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
+#include "utils/builtins.h"
 #include "utils/hsearch.h"
 
 
@@ -51,6 +53,7 @@ typedef struct df_files
 	ino_t		inode;			/* Inode number of file */
 #endif
 	void	   *handle;			/* a handle for pg_dl* functions */
+	const pg_module_info *minfo;
 	char		filename[FLEXIBLE_ARRAY_MEMBER];	/* Full pathname of file */
 } DynamicFileList;
 
@@ -75,7 +78,7 @@ static char *substitute_libpath_macro(const char *name);
 static char *find_in_dynamic_libpath(const char *basename);
 
 /* Magic structure that module needs to match to be accepted */
-static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
+static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA(0);
 
 
 /*
@@ -245,8 +248,14 @@ internal_load_library(const char *libname)
 		{
 			const Pg_magic_struct *magic_data_ptr = (*magic_func) ();
 
+			/*
+			 * Check magic field from loading library to be sure it  compiled
+			 * for the same Postgres code. Skip maintainer fields at the end
+			 * of the struct.
+			 */
 			if (magic_data_ptr->len != magic_data.len ||
-				memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0)
+				memcmp(magic_data_ptr, &magic_data,
+					   offsetof(Pg_magic_struct, module_extra)) != 0)
 			{
 				/* copy data block before unlinking library */
 				Pg_magic_struct module_magic_data = *magic_data_ptr;
@@ -258,6 +267,9 @@ internal_load_library(const char *libname)
 				/* issue suitable complaint */
 				incompatible_module_error(libname, &module_magic_data);
 			}
+
+			/* Save link to the maintainer-provided info */
+			file_scanner->minfo = &magic_data_ptr->module_extra;
 		}
 		else
 		{
@@ -675,3 +687,49 @@ RestoreLibraryState(char *start_address)
 		start_address += strlen(start_address) + 1;
 	}
 }
+
+Datum
+module_info(PG_FUNCTION_ARGS)
+{
+	FuncCallContext	   *funcctx;
+	MemoryContext		oldcontext;
+	DynamicFileList	   *file_scanner = NULL;
+	TupleDesc			tupdesc;
+	Datum				result;
+	Datum				values[3];
+	bool				isnull[3] = {0,0,0};
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		funcctx = SRF_FIRSTCALL_INIT();
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+
+		file_scanner = file_list;
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	funcctx = SRF_PERCALL_SETUP();
+
+	if (file_scanner != NULL)
+	{
+		if (file_scanner->minfo->name == NULL)
+			isnull[0] = true;
+		else
+			values[0] = CStringGetTextDatum(file_scanner->minfo->name);
+		if (file_scanner->minfo->version == NULL)
+			isnull[1] = true;
+		else
+			values[1] = CStringGetTextDatum(file_scanner->minfo->version);
+
+		values[2] = CStringGetTextDatum(file_scanner->filename);
+		result = HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
+		file_scanner = file_scanner->next;
+		SRF_RETURN_NEXT(funcctx, result);
+	}
+	else
+		SRF_RETURN_DONE(funcctx);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0f22c21723..51cb23f827 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -311,6 +311,12 @@
   proname => 'scalargtjoinsel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int2 internal',
   prosrc => 'scalargtjoinsel' },
+{ oid => '111', descr => 'Module Info',
+  proname => 'module_info', provolatile => 's', prorettype => 'record',
+  proretset => 't', proargtypes => '', proallargtypes => '{text,text,text}',
+  proargmodes => '{o,o,o}', prorows => 10,
+  proargnames => '{module_name,version,libname}',
+  prosrc => 'module_info' },
 
 { oid => '336',
   descr => 'restriction selectivity of <= and related operators on scalar datatypes',
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 1e3795de4a..ea21e15332 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -459,6 +459,12 @@ extern PGDLLEXPORT void _PG_init(void);
  *-------------------------------------------------------------------------
  */
 
+typedef struct pg_module_info
+{
+	const char *name;
+	const char *version;
+} pg_module_info;
+
 /* Definition of the magic block structure */
 typedef struct
 {
@@ -469,10 +475,15 @@ typedef struct
 	int			namedatalen;	/* NAMEDATALEN */
 	int			float8byval;	/* FLOAT8PASSBYVAL */
 	char		abi_extra[32];	/* see pg_config_manual.h */
+	pg_module_info module_extra;
 } Pg_magic_struct;
 
-/* The actual data block contents */
-#define PG_MODULE_MAGIC_DATA \
+/* The actual data block contents
+ *
+ * Fill the module info part with zeros by default. Zero-length module name
+ * indicates that it is not initialised.
+ */
+#define PG_MODULE_MAGIC_DATA(...) \
 { \
 	sizeof(Pg_magic_struct), \
 	PG_VERSION_NUM / 100, \
@@ -481,6 +492,7 @@ typedef struct
 	NAMEDATALEN, \
 	FLOAT8PASSBYVAL, \
 	FMGR_ABI_EXTRA, \
+	{__VA_ARGS__}, \
 }
 
 StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_magic_struct *) 0)->abi_extra),
@@ -500,11 +512,21 @@ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
 const Pg_magic_struct * \
 PG_MAGIC_FUNCTION_NAME(void) \
 { \
-	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \
+	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(0); \
 	return &Pg_magic_data; \
 } \
 extern int no_such_variable
 
+#define PG_MODULE_MAGIC_EXT(...) \
+extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
+const Pg_magic_struct * \
+PG_MAGIC_FUNCTION_NAME(void) \
+{ \
+	static const Pg_magic_struct Pg_magic_data = \
+		PG_MODULE_MAGIC_DATA(__VA_ARGS__); \
+	return &Pg_magic_data; \
+} \
+extern int no_such_variable
 
 /*-------------------------------------------------------------------------
  *		Support routines and macros for callers of fmgr-compatible functions
-- 
2.39.5

#20David E. Wheeler
david@justatheory.com
In reply to: Euler Taveira (#5)
Re: Add Postgres module info

On Dec 11, 2024, at 19:49, Euler Taveira <euler@eulerto.com> wrote:

FWIW, Id like to have some more information in there, without commenting on
the specifics.

+1 for the general idea.

Same.

I received some reports like [1] related to wal2json
that people wants to obtain the output plugin version. Since it is not installed
via CREATE EXTENSION, it is not possible to detect what version is installed,
hence, some tools cannot have some logic to probe the module version.

I’m all for additional metadata for native extensions, but I’d also like to draw attention to the “Future” section my proposal[1]https://justatheory.com/2024/11/rfc-extension-packaging-lookup/#future-deprecate-load to require that module-only extensions also include a control file and be loadable via CREATE EXTENSION (and proposed *_preload_extensions GUCs[2]https://justatheory.com/2024/11/rfc-extension-packaging-lookup/#extension-preloading). This would unify how all types of extensions are added to a database, and would include version information as for all other CREATE EXTENSION extensions.

Not a mutually-exclusive proposal, of course; I think it makes sense to have metadata included in the binary itself. Would be useful to compare against what CREATE EXTENSION thinks is the version and raising an error or warning when they diverge.

Best,

David

[1]: https://justatheory.com/2024/11/rfc-extension-packaging-lookup/#future-deprecate-load
[2]: https://justatheory.com/2024/11/rfc-extension-packaging-lookup/#extension-preloading

#21Tom Lane
tgl@sss.pgh.pa.us
In reply to: David E. Wheeler (#20)
Re: Add Postgres module info

"David E. Wheeler" <david@justatheory.com> writes:

Not a mutually-exclusive proposal, of course; I think it makes sense to have metadata included in the binary itself. Would be useful to compare against what CREATE EXTENSION thinks is the version and raising an error or warning when they diverge.

How would that work for extensions where the C code is intentionally
supporting multiple versions of the SQL objects?

regards, tom lane

#22David E. Wheeler
david@justatheory.com
In reply to: Tom Lane (#21)
Re: Add Postgres module info

On Dec 23, 2024, at 15:17, Tom Lane <tgl@sss.pgh.pa.us> wrote:

How would that work for extensions where the C code is intentionally
supporting multiple versions of the SQL objects?

I guess some people do that, eh? In that case it wouldn’t.

D

#23Andrei Lepikhov
lepihov@gmail.com
In reply to: David E. Wheeler (#20)
Re: Add Postgres module info

On 12/24/24 02:23, David E. Wheeler wrote:

On Dec 11, 2024, at 19:49, Euler Taveira <euler@eulerto.com> wrote:

FWIW, Id like to have some more information in there, without commenting on
the specifics.

+1 for the general idea.

Same.

I received some reports like [1] related to wal2json
that people wants to obtain the output plugin version. Since it is not installed
via CREATE EXTENSION, it is not possible to detect what version is installed,
hence, some tools cannot have some logic to probe the module version.

I’m all for additional metadata for native extensions, but I’d also like to draw attention to the “Future” section my proposal[1] to require that module-only extensions also include a control file and be loadable via CREATE EXTENSION (and proposed *_preload_extensions GUCs[2]). This would unify how all types of extensions are added to a database, and would include version information as for all other CREATE EXTENSION extensions.

Looking into the control file, I see that most parameters are
unnecessary for the library. Why do we have to maintain this file?
In my experience, extra features are usually designed as shared
libraries to 1) reduce complexity, 2) work across the overall cluster,
3) be dynamically loaded, 4) be hidden, and not waste the database with
any type of object. - remember, applications sometimes manage their data
through an API; databases and any objects inside may be created/moved
automatically, and we want to work in any database.
The 'CREATE EXTENSION' statement would have made sense if we had
register/unregister hook machinery. Without that, it seems it is just
about maintaining the library's version and comments locally in a
specific database.
It would be interesting to read about your real-life cases that caused
your proposal.

--
regards, Andrei Lepikhov

#24Yurii Rashkovskii
yrashk@omnigres.com
In reply to: Andrei Lepikhov (#19)
Re: Add Postgres module info

On Mon, Dec 16, 2024 at 12:02 PM Andrei Lepikhov <lepihov@gmail.com> wrote:

On 12/13/24 10:17, Tom Lane wrote:

Andrei Lepikhov <lepihov@gmail.com> writes:

On 12/12/24 21:02, Yurii Rashkovskii wrote:

2. Any reasons to dictate MAJ.MIN format? With semantic versioning
abound, it's rather common to use MAJ.MIN.PATCH.

Okay, thanks; that's a good catch. I wonder how to follow these rules
with a static fixed-sized structure. I would like to read about any
suggestions and implementation examples.

There's nothing stopping a field of the magic block from being
a "const char *" pointer to a string literal.

Ok, See v.2 in attachment.

I've reviewed the patch, and it is great that you support more flexible
versioning now. I am just wondering a bit about the case where
`minfo->name` can be `NULL` but `minfo->version` isn't, or where both are
`NULL` – should we skip any of these?

#25Chapman Flack
jcflack@acm.org
In reply to: David E. Wheeler (#22)
Re: Add Postgres module info

On 12/23/24 17:26, David E. Wheeler wrote:

On Dec 23, 2024, at 15:17, Tom Lane <tgl@sss.pgh.pa.us> wrote:

How would that work for extensions where the C code is intentionally
supporting multiple versions of the SQL objects?

I guess some people do that, eh? In that case it wouldn’t.

A function pointer rather than a version constant?

Or a function pointer, to be used if the version constant is null?

Regards,
-Chap

#26David Wheeler
david@justatheory.com
In reply to: Andrei Lepikhov (#23)
Re: Add Postgres module info

On Mon, Dec 23, 2024, at 8:49 PM, Andrei Lepikhov wrote:

Looking into the control file, I see that most parameters are
unnecessary for the library. Why do we have to maintain this file?

Most of the parameters apply to SQL extensions.

The 'CREATE EXTENSION' statement would have made sense if we had
register/unregister hook machinery. Without that, it seems it is just
about maintaining the library's version and comments locally in a
specific database.

Well, either way you have to load the extension, either CREATE EXTENSION to load an SQL extension (and any related shared modules), or LOAD or *_preload_libraries to load a shared module. I propose to add support for shared-module-only extensions to CREATE/UPDATE/DROP EXTENSION. It would then both insert the version info in the database (from the control file, at least), and load the shares module(s).

It would be interesting to read about your real-life cases that caused
your proposal.

They're in the first section of [1]https://justatheory.com/2024/11/rfc-extension-packaging-lookup/. The desire to group all the files for an extension in a single directory led to a conflict with the exiting LOAD patterns, which in the final section of [1]https://justatheory.com/2024/11/rfc-extension-packaging-lookup/ I attempt to resolve by proposing a single way to manage *all* extensions, instead of the two separate patterns we have today.

Best,

David

[1]: https://justatheory.com/2024/11/rfc-extension-packaging-lookup/

#27Andrei Lepikhov
lepihov@gmail.com
In reply to: David Wheeler (#26)
Re: Add Postgres module info

On 12/27/24 01:26, David Wheeler wrote:

On Mon, Dec 23, 2024, at 8:49 PM, Andrei Lepikhov wrote:

Looking into the control file, I see that most parameters are
unnecessary for the library. Why do we have to maintain this file?

Well, either way you have to load the extension, either CREATE EXTENSION to load an SQL extension (and any related shared modules), or LOAD or *_preload_libraries to load a shared module. I propose to add support for shared-module-only extensions to CREATE/UPDATE/DROP EXTENSION. It would then both insert the version info in the database (from the control file, at least), and load the shares module(s).

I still can't get your point.
We intentionally wrote a library, not an extension. According to user
usage and upgrade patterns, it works across the whole instance and in
any database or locally in a single backend and ends its impact at the
end of its life.
Also, it doesn't maintain any object in the database and is managed by GUCs.
For example, my libraries add query tree transformations/path
recommendations to the planner. It doesn't depend on a database and
doesn't maintain DSM segments and users sometimes want to use it in
specific backends, not databases - in a backend dedicated to analytic
queries without extra overhead to backends, picked out for short
queries. For what reason do I need to add complexity and call 'CREATE
EXTENSION' here and add version info only in a specific database? Just
because of a formal one-directory structure?

--
regards, Andrei Lepikhov

#28Andrei Lepikhov
lepihov@gmail.com
In reply to: Yurii Rashkovskii (#24)
Re: Add Postgres module info

On 12/24/24 10:42, Yurii Rashkovskii wrote:

On Mon, Dec 16, 2024 at 12:02 PM Andrei Lepikhov <lepihov@gmail.com
I've reviewed the patch, and it is great that you support more flexible
versioning now. I am just wondering a bit about the case where `minfo-

name` can be `NULL` but `minfo->version` isn't, or where both are

`NULL` – should we skip any of these?

Depends. I wrote code that way so as not to restrict a maintainer by
initialising all the fields; remember, it may grow in the future.
But I am open to changing that logic. Do you have any specific rule on
which fields may be empty and that must be initialised? Do you think all
fields maintainer must fill with non-zero-length constants?

Also, I've added this patch to commitfest:
https://commitfest.postgresql.org/51/5465/

--
regards, Andrei Lepikhov

#29Yurii Rashkovskii
yrashk@omnigres.com
In reply to: Andrei Lepikhov (#28)
Re: Add Postgres module info

On Fri, Dec 27, 2024 at 8:34 AM Andrei Lepikhov <lepihov@gmail.com> wrote:

On 12/24/24 10:42, Yurii Rashkovskii wrote:

On Mon, Dec 16, 2024 at 12:02 PM Andrei Lepikhov <lepihov@gmail.com
I've reviewed the patch, and it is great that you support more flexible
versioning now. I am just wondering a bit about the case where `minfo-

name` can be `NULL` but `minfo->version` isn't, or where both are

`NULL` – should we skip any of these?

Depends. I wrote code that way so as not to restrict a maintainer by
initialising all the fields; remember, it may grow in the future.
But I am open to changing that logic. Do you have any specific rule on
which fields may be empty and that must be initialised? Do you think all
fields maintainer must fill with non-zero-length constants?

After more thinking, I'll concede that not doing anything about null
metadata is probably better – making the function always return the list of
modules, regardless of whether any metadata was supplied. It's beneficial
to be able to get the entire list of modules regardless of metadata.

The only other minor concern I have left is that some modules might have a
clashing name or may change the name during the extension's lifetime
(happened to some of my early work). Providing a permanent identifier and a
human-digestible identifier may be worth it.

#30David E. Wheeler
david@justatheory.com
In reply to: Andrei Lepikhov (#27)
Re: Add Postgres module info

On Dec 26, 2024, at 20:09, Andrei Lepikhov <lepihov@gmail.com> wrote:

We intentionally wrote a library, not an extension. According to user usage and upgrade patterns, it works across the whole instance and in any database or locally in a single backend and ends its impact at the end of its life.

The same is true for the shared libraries included in many extensions. A shared library is just an extension that’s available in all databases and has no associated SQL interface.

Also, it doesn't maintain any object in the database and is managed by GUCs.

Sure, but this is just a semantic argument. The Postgres developers get to decide what terms mean. I’m I argue it can be worthwhile to merge the idea of a library into extensions.

For example, my libraries add query tree transformations/path recommendations to the planner. It doesn't depend on a database and doesn't maintain DSM segments and users sometimes want to use it in specific backends, not databases - in a backend dedicated to analytic queries without extra overhead to backends, picked out for short queries. For what reason do I need to add complexity and call 'CREATE EXTENSION' here and add version info only in a specific database? Just because of a formal one-directory structure?

Perhaps shared-library only extensions are not limited to a single database.

Best,

David

#31Alexander Korotkov
aekorotkov@gmail.com
In reply to: Andrei Lepikhov (#19)
Re: Add Postgres module info

Hi, Andrei!

On Mon, Dec 16, 2024 at 7:02 AM Andrei Lepikhov <lepihov@gmail.com> wrote:

ly
On 12/13/24 10:17, Tom Lane wrote:

Andrei Lepikhov <lepihov@gmail.com> writes:

On 12/12/24 21:02, Yurii Rashkovskii wrote:

2. Any reasons to dictate MAJ.MIN format? With semantic versioning
abound, it's rather common to use MAJ.MIN.PATCH.

Okay, thanks; that's a good catch. I wonder how to follow these rules
with a static fixed-sized structure. I would like to read about any
suggestions and implementation examples.

There's nothing stopping a field of the magic block from being
a "const char *" pointer to a string literal.

Ok, See v.2 in attachment.

Generally, the patch looks good to me. I have couple of questions.

1) Is it intended to switch all in-core libraries to use PG_MODULE_MAGIC_EXT()?
2) Once we have module version information, it looks natural to
specify the required version for dependant objects, e.g. SQL-funcions
implemented in shared libraries. For instance,
CREATE FUNCTION ... AS 'MODULE_PATHNAME' LANGUAGE C module_version >= '1.0';
For this, and probably other purposes, it's desirable for version to
be something comparable at SQL level. Should we add some builtin
analogue of pg_text_semver?

------
Regards,
Alexander Korotkov
Supabase

#32Michael Paquier
michael@paquier.xyz
In reply to: Alexander Korotkov (#31)
Re: Add Postgres module info

On Mon, Feb 17, 2025 at 03:41:56AM +0200, Alexander Korotkov wrote:

1) Is it intended to switch all in-core libraries to use PG_MODULE_MAGIC_EXT()?
2) Once we have module version information, it looks natural to
specify the required version for dependant objects, e.g. SQL-funcions
implemented in shared libraries. For instance,
CREATE FUNCTION ... AS 'MODULE_PATHNAME' LANGUAGE C module_version >= '1.0';
For this, and probably other purposes, it's desirable for version to
be something comparable at SQL level. Should we add some builtin
analogue of pg_text_semver?

I see that this is just a way for extensions to map to some data
statically stored in the modules themselves based on what I can see at
[1]: /messages/by-id/0e82bf8c-ae70-498d-861e-dba2bb154cad@gmail.com -- Michael

+ bool isnull[3] = {0,0,0};

Could be a simpler {0}.

-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+	.name = "auto_explain",
+	.version = "1.0.0"
+);

It does not make sense to me to stick that into into of the contrib
modules officially supported just for the sake of the API. I'd
suggest to switch in one of the modules of src/test/modules/ that are
loaded with shared_preload_libraries. A second thing I would suggest
to check is a SQL call with a library loaded via SQL with a LOAD.
test_oat_hooks is already LOAD'ed in a couple of scripts, for example.
For the shared_preload_libraries can, you could choose anything to
prove your point with tests.

+Datum
+module_info(PG_FUNCTION_ARGS)

This should use a "pg_" prefix, should use a plural term as it is a
SRF returning information about all the modules loaded. Perhaps just
name it to pg_get_modules() and also map it to a new system view?

Some problems with `git diff --check\` showing up here.

No documentation provided.

[1]: /messages/by-id/0e82bf8c-ae70-498d-861e-dba2bb154cad@gmail.com -- Michael
--
Michael

#33Andrei Lepikhov
lepihov@gmail.com
In reply to: Alexander Korotkov (#31)
Re: Add Postgres module info

On 17/2/2025 02:41, Alexander Korotkov wrote:

On Mon, Dec 16, 2024 at 7:02 AM Andrei Lepikhov <lepihov@gmail.com> wrote:

On 12/13/24 10:17, Tom Lane wrote:

There's nothing stopping a field of the magic block from being
a "const char *" pointer to a string literal.

Ok, See v.2 in attachment.

Generally, the patch looks good to me. I have couple of questions.

1) Is it intended to switch all in-core libraries to use PG_MODULE_MAGIC_EXT()?

I haven't such intention. Just wanted to demonstrate how it might work.

2) Once we have module version information, it looks natural to
specify the required version for dependant objects, e.g. SQL-funcions
implemented in shared libraries. For instance,
CREATE FUNCTION ... AS 'MODULE_PATHNAME' LANGUAGE C module_version >= '1.0';

Just to be clear. You want this stuff to let the core manage situations
of stale binaries and throw an error like the following:
"No function matches the given name, argument types and module version"
Do I understand you correctly?
It may make sense, but I can't figure out a use case. Could you describe
at least one example?

--
regards, Andrei Lepikhov

#34Andrei Lepikhov
lepihov@gmail.com
In reply to: Michael Paquier (#32)
1 attachment(s)
Re: Add Postgres module info

On 17/2/2025 04:00, Michael Paquier wrote:

On Mon, Feb 17, 2025 at 03:41:56AM +0200, Alexander Korotkov wrote:

1) Is it intended to switch all in-core libraries to use PG_MODULE_MAGIC_EXT()?
2) Once we have module version information, it looks natural to
specify the required version for dependant objects, e.g. SQL-funcions
implemented in shared libraries. For instance,
CREATE FUNCTION ... AS 'MODULE_PATHNAME' LANGUAGE C module_version >= '1.0';
For this, and probably other purposes, it's desirable for version to
be something comparable at SQL level. Should we add some builtin
analogue of pg_text_semver?

I see that this is just a way for extensions to map to some data
statically stored in the modules themselves based on what I can see at
[1]. Why not.

+ bool isnull[3] = {0,0,0};

Could be a simpler {0}.

Done

-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+	.name = "auto_explain",
+	.version = "1.0.0"
+);

It does not make sense to me to stick that into into of the contrib
modules officially supported just for the sake of the API. I'd

Done

suggest to switch in one of the modules of src/test/modules/ that are
loaded with shared_preload_libraries. A second thing I would suggest
to check is a SQL call with a library loaded via SQL with a LOAD.
test_oat_hooks is already LOAD'ed in a couple of scripts, for example.
For the shared_preload_libraries can, you could choose anything to
prove your point with tests.

Done

+Datum
+module_info(PG_FUNCTION_ARGS)

This should use a "pg_" prefix, should use a plural term as it is a
SRF returning information about all the modules loaded. Perhaps just
name it to pg_get_modules() and also map it to a new system view?

Sure, done.

Some problems with `git diff --check\` showing up here.

Done

No documentation provided.

Ok, I haven't been sure this idea has a chance to be committed. I will
introduce the docs in the next version.

--
regards, Andrei Lepikhov

Attachments:

v3-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchtext/plain; charset=UTF-8; name=v3-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchDownload
From b7472026bbf2ef49df164492a19d8433cbaa1d12 Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepihov@gmail.com>
Date: Tue, 19 Nov 2024 18:45:36 +0700
Subject: [PATCH v3] Introduce PG_MODULE_MAGIC_EXT macro.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This macro provides dynamically loaded shared libraries (modules) with standard
way to incorporate version (supposedly, defined according to semantic versioning
specification) and name data. The introduced catalogue routine pg_get_modules≈ can
be used to find this module by name and check the version. It makes users
independent from file naming conventions.

With a growing number of Postgres core hooks and the introduction of named DSM
segments, the number of modules that don't need to be loaded on startup may
grow fast. Moreover, in many cases related to query tree transformation or
extra path recommendation, such modules might not need database objects except
GUCs - see auto_explain as an example. That means they don't need to execute
the 'CREATE EXTENSION' statement at all and don't have a record in
the pg_extension table. Such a trick provides much flexibility, including
an online upgrade and may become widespread.

In addition, it is also convenient in support to be sure that the installation
(or at least the backend) includes a specific version of the module. Even if
a module has an installation script, it is not rare that it provides
an implementation for a range of UI routine versions. It makes sense to ensure
which specific version of the code is used.

Discussions [1,2] already mentioned module-info stuff, but at that time,
extensibility techniques and extension popularity were low, and it wasn't
necessary to provide that data.

[1] https://www.postgresql.org/message-id/flat/20060507211705.GB3808%40svana.org
[2] https://www.postgresql.org/message-id/flat/20051106162658.34c31d57%40thunder.logicalchaos.org
---
 src/backend/utils/fmgr/dfmgr.c                | 62 ++++++++++++++++++-
 src/include/catalog/pg_proc.dat               |  6 ++
 src/include/fmgr.h                            | 28 ++++++++-
 .../modules/test_dsa/expected/test_dsa.out    |  8 +++
 src/test/modules/test_dsa/sql/test_dsa.sql    |  4 ++
 .../test_shm_mq/expected/test_shm_mq.out      |  8 +++
 .../modules/test_shm_mq/sql/test_shm_mq.sql   |  4 ++
 src/test/modules/test_shm_mq/test.c           |  5 +-
 .../modules/test_slru/expected/test_slru.out  | 16 +++++
 src/test/modules/test_slru/sql/test_slru.sql  |  8 +++
 src/test/modules/test_slru/test_slru.c        |  5 +-
 11 files changed, 147 insertions(+), 7 deletions(-)

diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 87b233cb887..0ae9b21c2bc 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -21,10 +21,12 @@
 #endif							/* !WIN32 */
 
 #include "fmgr.h"
+#include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
+#include "utils/builtins.h"
 #include "utils/hsearch.h"
 
 
@@ -51,6 +53,7 @@ typedef struct df_files
 	ino_t		inode;			/* Inode number of file */
 #endif
 	void	   *handle;			/* a handle for pg_dl* functions */
+	const pg_module_info *minfo;
 	char		filename[FLEXIBLE_ARRAY_MEMBER];	/* Full pathname of file */
 } DynamicFileList;
 
@@ -75,7 +78,7 @@ static char *substitute_libpath_macro(const char *name);
 static char *find_in_dynamic_libpath(const char *basename);
 
 /* Magic structure that module needs to match to be accepted */
-static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
+static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA(0);
 
 
 /*
@@ -245,8 +248,14 @@ internal_load_library(const char *libname)
 		{
 			const Pg_magic_struct *magic_data_ptr = (*magic_func) ();
 
+			/*
+			 * Check magic field from loading library to be sure it  compiled
+			 * for the same Postgres code. Skip maintainer fields at the end
+			 * of the struct.
+			 */
 			if (magic_data_ptr->len != magic_data.len ||
-				memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0)
+				memcmp(magic_data_ptr, &magic_data,
+					   offsetof(Pg_magic_struct, module_extra)) != 0)
 			{
 				/* copy data block before unlinking library */
 				Pg_magic_struct module_magic_data = *magic_data_ptr;
@@ -258,6 +267,9 @@ internal_load_library(const char *libname)
 				/* issue suitable complaint */
 				incompatible_module_error(libname, &module_magic_data);
 			}
+
+			/* Save link to the maintainer-provided info */
+			file_scanner->minfo = &magic_data_ptr->module_extra;
 		}
 		else
 		{
@@ -675,3 +687,49 @@ RestoreLibraryState(char *start_address)
 		start_address += strlen(start_address) + 1;
 	}
 }
+
+Datum
+pg_get_modules(PG_FUNCTION_ARGS)
+{
+	FuncCallContext	   *funcctx;
+	MemoryContext		oldcontext;
+	DynamicFileList	   *file_scanner = NULL;
+	TupleDesc			tupdesc;
+	Datum				result;
+	Datum				values[3];
+	bool				isnull[3] = {0};
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		funcctx = SRF_FIRSTCALL_INIT();
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+
+		file_scanner = file_list;
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	funcctx = SRF_PERCALL_SETUP();
+
+	if (file_scanner != NULL)
+	{
+		if (file_scanner->minfo->name == NULL)
+			isnull[0] = true;
+		else
+			values[0] = CStringGetTextDatum(file_scanner->minfo->name);
+		if (file_scanner->minfo->version == NULL)
+			isnull[1] = true;
+		else
+			values[1] = CStringGetTextDatum(file_scanner->minfo->version);
+
+		values[2] = CStringGetTextDatum(file_scanner->filename);
+		result = HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
+		file_scanner = file_scanner->next;
+		SRF_RETURN_NEXT(funcctx, result);
+	}
+	else
+		SRF_RETURN_DONE(funcctx);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cd9422d0bac..8e230f7ce2b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -311,6 +311,12 @@
   proname => 'scalargtjoinsel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int2 internal',
   prosrc => 'scalargtjoinsel' },
+{ oid => '111', descr => 'Module Info',
+  proname => 'pg_get_modules', provolatile => 's', prorettype => 'record',
+  proretset => 't', proargtypes => '', proallargtypes => '{text,text,text}',
+  proargmodes => '{o,o,o}', prorows => 10,
+  proargnames => '{module_name,version,libname}',
+  prosrc => 'pg_get_modules' },
 
 { oid => '336',
   descr => 'restriction selectivity of <= and related operators on scalar datatypes',
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index e609ea875a7..5018abf3a18 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -459,6 +459,12 @@ extern PGDLLEXPORT void _PG_init(void);
  *-------------------------------------------------------------------------
  */
 
+typedef struct pg_module_info
+{
+	const char *name;
+	const char *version;
+} pg_module_info;
+
 /* Definition of the magic block structure */
 typedef struct
 {
@@ -469,10 +475,15 @@ typedef struct
 	int			namedatalen;	/* NAMEDATALEN */
 	int			float8byval;	/* FLOAT8PASSBYVAL */
 	char		abi_extra[32];	/* see pg_config_manual.h */
+	pg_module_info module_extra;
 } Pg_magic_struct;
 
-/* The actual data block contents */
-#define PG_MODULE_MAGIC_DATA \
+/* The actual data block contents
+ *
+ * Fill the module info part with zeros by default. Zero-length module name
+ * indicates that it is not initialised.
+ */
+#define PG_MODULE_MAGIC_DATA(...) \
 { \
 	sizeof(Pg_magic_struct), \
 	PG_VERSION_NUM / 100, \
@@ -481,6 +492,7 @@ typedef struct
 	NAMEDATALEN, \
 	FLOAT8PASSBYVAL, \
 	FMGR_ABI_EXTRA, \
+	{__VA_ARGS__}, \
 }
 
 StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_magic_struct *) 0)->abi_extra),
@@ -500,11 +512,21 @@ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
 const Pg_magic_struct * \
 PG_MAGIC_FUNCTION_NAME(void) \
 { \
-	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \
+	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(0); \
 	return &Pg_magic_data; \
 } \
 extern int no_such_variable
 
+#define PG_MODULE_MAGIC_EXT(...) \
+extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
+const Pg_magic_struct * \
+PG_MAGIC_FUNCTION_NAME(void) \
+{ \
+	static const Pg_magic_struct Pg_magic_data = \
+		PG_MODULE_MAGIC_DATA(__VA_ARGS__); \
+	return &Pg_magic_data; \
+} \
+extern int no_such_variable
 
 /*-------------------------------------------------------------------------
  *		Support routines and macros for callers of fmgr-compatible functions
diff --git a/src/test/modules/test_dsa/expected/test_dsa.out b/src/test/modules/test_dsa/expected/test_dsa.out
index 266010e77fe..942bf616b5b 100644
--- a/src/test/modules/test_dsa/expected/test_dsa.out
+++ b/src/test/modules/test_dsa/expected/test_dsa.out
@@ -11,3 +11,11 @@ SELECT test_dsa_resowners();
  
 (1 row)
 
+-- test empty module info
+SELECT module_name,version
+FROM pg_get_modules() WHERE libname LIKE '%test_dsa%';
+ module_name | version 
+-------------+---------
+             | 
+(1 row)
+
diff --git a/src/test/modules/test_dsa/sql/test_dsa.sql b/src/test/modules/test_dsa/sql/test_dsa.sql
index c3d8db94372..55d982df45f 100644
--- a/src/test/modules/test_dsa/sql/test_dsa.sql
+++ b/src/test/modules/test_dsa/sql/test_dsa.sql
@@ -2,3 +2,7 @@ CREATE EXTENSION test_dsa;
 
 SELECT test_dsa_basic();
 SELECT test_dsa_resowners();
+
+-- test empty module info
+SELECT module_name,version
+FROM pg_get_modules() WHERE libname LIKE '%test_dsa%';
diff --git a/src/test/modules/test_shm_mq/expected/test_shm_mq.out b/src/test/modules/test_shm_mq/expected/test_shm_mq.out
index c4858b0c205..45d1587ce9b 100644
--- a/src/test/modules/test_shm_mq/expected/test_shm_mq.out
+++ b/src/test/modules/test_shm_mq/expected/test_shm_mq.out
@@ -34,3 +34,11 @@ SELECT test_shm_mq_pipelined(16384, (select string_agg(chr(32+(random()*95)::int
  
 (1 row)
 
+-- Check module version
+SELECT module_name, version
+FROM pg_get_modules() WHERE libname LIKE '%test_shm_mq%';
+ module_name | version 
+-------------+---------
+ test_shm_mq | 1.0.0
+(1 row)
+
diff --git a/src/test/modules/test_shm_mq/sql/test_shm_mq.sql b/src/test/modules/test_shm_mq/sql/test_shm_mq.sql
index 9de19d304a2..9576e9f6483 100644
--- a/src/test/modules/test_shm_mq/sql/test_shm_mq.sql
+++ b/src/test/modules/test_shm_mq/sql/test_shm_mq.sql
@@ -10,3 +10,7 @@ SELECT test_shm_mq(1024, 'a', 2001, 1);
 SELECT test_shm_mq(32768, (select string_agg(chr(32+(random()*95)::int), '') from generate_series(1,(100+900*random())::int)), 10000, 1);
 SELECT test_shm_mq(100, (select string_agg(chr(32+(random()*95)::int), '') from generate_series(1,(100+200*random())::int)), 10000, 1);
 SELECT test_shm_mq_pipelined(16384, (select string_agg(chr(32+(random()*95)::int), '') from generate_series(1,270000)), 200, 3);
+
+-- Check module version
+SELECT module_name, version
+FROM pg_get_modules() WHERE libname LIKE '%test_shm_mq%';
diff --git a/src/test/modules/test_shm_mq/test.c b/src/test/modules/test_shm_mq/test.c
index 443281addd0..130d1f81300 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -20,7 +20,10 @@
 
 #include "test_shm_mq.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+	.name = "test_shm_mq",
+	.version = "1.0.0"
+);
 
 PG_FUNCTION_INFO_V1(test_shm_mq);
 PG_FUNCTION_INFO_V1(test_shm_mq_pipelined);
diff --git a/src/test/modules/test_slru/expected/test_slru.out b/src/test/modules/test_slru/expected/test_slru.out
index 185c56e5d62..65ed2916f8f 100644
--- a/src/test/modules/test_slru/expected/test_slru.out
+++ b/src/test/modules/test_slru/expected/test_slru.out
@@ -267,4 +267,20 @@ SELECT test_slru_page_exists(0x1234500000030);
  f
 (1 row)
 
+-- Check module version in case it was loaded on startup
+SELECT module_name, version
+FROM pg_get_modules() WHERE libname LIKE '%test_slru%';
+ module_name | version 
+-------------+---------
+ test_slru   | 1.2.3
+(1 row)
+
 DROP EXTENSION test_slru;
+-- Module still exists and returns the same value after DROP EXTENSION
+SELECT module_name, version
+FROM pg_get_modules() WHERE libname LIKE '%test_slru%';
+ module_name | version 
+-------------+---------
+ test_slru   | 1.2.3
+(1 row)
+
diff --git a/src/test/modules/test_slru/sql/test_slru.sql b/src/test/modules/test_slru/sql/test_slru.sql
index b1b376581ab..11064ad9201 100644
--- a/src/test/modules/test_slru/sql/test_slru.sql
+++ b/src/test/modules/test_slru/sql/test_slru.sql
@@ -73,4 +73,12 @@ SELECT test_slru_page_exists(0x1234500000000);
 SELECT test_slru_page_exists(0x1234500000020);
 SELECT test_slru_page_exists(0x1234500000030);
 
+-- Check module version in case it was loaded on startup
+SELECT module_name, version
+FROM pg_get_modules() WHERE libname LIKE '%test_slru%';
+
 DROP EXTENSION test_slru;
+
+-- Module still exists and returns the same value after DROP EXTENSION
+SELECT module_name, version
+FROM pg_get_modules() WHERE libname LIKE '%test_slru%';
diff --git a/src/test/modules/test_slru/test_slru.c b/src/test/modules/test_slru/test_slru.c
index 3ea5ceb8552..ed19ee1e719 100644
--- a/src/test/modules/test_slru/test_slru.c
+++ b/src/test/modules/test_slru/test_slru.c
@@ -22,7 +22,10 @@
 #include "storage/shmem.h"
 #include "utils/builtins.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+	.name = "test_slru",
+	.version = "1.2.3"
+);
 
 /*
  * SQL-callable entry points
-- 
2.48.1

#35Andrei Lepikhov
lepihov@gmail.com
In reply to: Andrei Lepikhov (#34)
1 attachment(s)
Re: Add Postgres module info

On 2/3/2025 20:35, Andrei Lepikhov wrote:

On 17/2/2025 04:00, Michael Paquier wrote:

No documentation provided.

Ok, I haven't been sure this idea has a chance to be committed. I will
introduce the docs in the next version.

This is a new version with bug fixes. Also, use TAP tests instead of
regression tests. Still, no documentation is included.

--
regards, Andrei Lepikhov

Attachments:

v4-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchtext/plain; charset=UTF-8; name=v4-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchDownload
From 3ef2579bfa3dd0306d63e355ceae65f9f476dea8 Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepihov@gmail.com>
Date: Tue, 19 Nov 2024 18:45:36 +0700
Subject: [PATCH v4] Introduce PG_MODULE_MAGIC_EXT macro.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This macro provides dynamically loaded shared libraries (modules) with standard
way to incorporate version (supposedly, defined according to semantic versioning
specification) and name data. The introduced catalogue routine pg_get_modules≈ can
be used to find this module by name and check the version. It makes users
independent from file naming conventions.

With a growing number of Postgres core hooks and the introduction of named DSM
segments, the number of modules that don't need to be loaded on startup may
grow fast. Moreover, in many cases related to query tree transformation or
extra path recommendation, such modules might not need database objects except
GUCs - see auto_explain as an example. That means they don't need to execute
the 'CREATE EXTENSION' statement at all and don't have a record in
the pg_extension table. Such a trick provides much flexibility, including
an online upgrade and may become widespread.

In addition, it is also convenient in support to be sure that the installation
(or at least the backend) includes a specific version of the module. Even if
a module has an installation script, it is not rare that it provides
an implementation for a range of UI routine versions. It makes sense to ensure
which specific version of the code is used.

Discussions [1,2] already mentioned module-info stuff, but at that time,
extensibility techniques and extension popularity were low, and it wasn't
necessary to provide that data.

[1] https://www.postgresql.org/message-id/flat/20060507211705.GB3808%40svana.org
[2] https://www.postgresql.org/message-id/flat/20051106162658.34c31d57%40thunder.logicalchaos.org
---
 src/backend/utils/fmgr/dfmgr.c                | 66 ++++++++++++++++++-
 src/include/catalog/pg_proc.dat               |  6 ++
 src/include/fmgr.h                            | 28 +++++++-
 src/test/modules/test_misc/Makefile           |  5 +-
 .../test_misc/t/008_check_pg_get_modules.pl   | 65 ++++++++++++++++++
 src/test/modules/test_shm_mq/test.c           |  5 +-
 src/test/modules/test_slru/test_slru.c        |  5 +-
 7 files changed, 172 insertions(+), 8 deletions(-)
 create mode 100644 src/test/modules/test_misc/t/008_check_pg_get_modules.pl

diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 87b233cb887..70d6ced4157 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -21,10 +21,12 @@
 #endif							/* !WIN32 */
 
 #include "fmgr.h"
+#include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
+#include "utils/builtins.h"
 #include "utils/hsearch.h"
 
 
@@ -51,6 +53,7 @@ typedef struct df_files
 	ino_t		inode;			/* Inode number of file */
 #endif
 	void	   *handle;			/* a handle for pg_dl* functions */
+	const pg_module_info *minfo;
 	char		filename[FLEXIBLE_ARRAY_MEMBER];	/* Full pathname of file */
 } DynamicFileList;
 
@@ -75,7 +78,7 @@ static char *substitute_libpath_macro(const char *name);
 static char *find_in_dynamic_libpath(const char *basename);
 
 /* Magic structure that module needs to match to be accepted */
-static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
+static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA(0);
 
 
 /*
@@ -245,8 +248,14 @@ internal_load_library(const char *libname)
 		{
 			const Pg_magic_struct *magic_data_ptr = (*magic_func) ();
 
+			/*
+			 * Check magic field from loading library to be sure it  compiled
+			 * for the same Postgres code. Skip maintainer fields at the end
+			 * of the struct.
+			 */
 			if (magic_data_ptr->len != magic_data.len ||
-				memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0)
+				memcmp(magic_data_ptr, &magic_data,
+					   offsetof(Pg_magic_struct, module_extra)) != 0)
 			{
 				/* copy data block before unlinking library */
 				Pg_magic_struct module_magic_data = *magic_data_ptr;
@@ -258,6 +267,9 @@ internal_load_library(const char *libname)
 				/* issue suitable complaint */
 				incompatible_module_error(libname, &module_magic_data);
 			}
+
+			/* Save link to the maintainer-provided info */
+			file_scanner->minfo = &magic_data_ptr->module_extra;
 		}
 		else
 		{
@@ -675,3 +687,53 @@ RestoreLibraryState(char *start_address)
 		start_address += strlen(start_address) + 1;
 	}
 }
+
+Datum
+pg_get_modules(PG_FUNCTION_ARGS)
+{
+	FuncCallContext	   *funcctx;
+	MemoryContext		oldcontext;
+	DynamicFileList	   *file_scanner;
+	Datum				result;
+	Datum				values[3];
+	bool				isnull[3] = {0};
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		TupleDesc	tupdesc;
+
+		funcctx = SRF_FIRSTCALL_INIT();
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+
+		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+		funcctx->user_fctx = (void *) file_list;
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	funcctx = SRF_PERCALL_SETUP();
+	file_scanner = (DynamicFileList *) funcctx->user_fctx;
+
+	if (file_scanner != NULL)
+	{
+		if (file_scanner->minfo->name == NULL)
+			isnull[0] = true;
+		else
+			values[0] = CStringGetTextDatum(file_scanner->minfo->name);
+		if (file_scanner->minfo->version == NULL)
+			isnull[1] = true;
+		else
+			values[1] = CStringGetTextDatum(file_scanner->minfo->version);
+
+		values[2] = CStringGetTextDatum(file_scanner->filename);
+		result = HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc,
+												   values, isnull));
+		funcctx->user_fctx = file_scanner->next;
+		SRF_RETURN_NEXT(funcctx, result);
+	}
+	else
+		SRF_RETURN_DONE(funcctx);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134b3dd8689..254f16e0496 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -311,6 +311,12 @@
   proname => 'scalargtjoinsel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int2 internal',
   prosrc => 'scalargtjoinsel' },
+{ oid => '111', descr => 'Module Info',
+  proname => 'pg_get_modules', provolatile => 's', prorettype => 'record',
+  proretset => 't', proargtypes => '', proallargtypes => '{text,text,text}',
+  proargmodes => '{o,o,o}', prorows => 10,
+  proargnames => '{module_name,version,libname}',
+  prosrc => 'pg_get_modules' },
 
 { oid => '336',
   descr => 'restriction selectivity of <= and related operators on scalar datatypes',
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index e609ea875a7..5018abf3a18 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -459,6 +459,12 @@ extern PGDLLEXPORT void _PG_init(void);
  *-------------------------------------------------------------------------
  */
 
+typedef struct pg_module_info
+{
+	const char *name;
+	const char *version;
+} pg_module_info;
+
 /* Definition of the magic block structure */
 typedef struct
 {
@@ -469,10 +475,15 @@ typedef struct
 	int			namedatalen;	/* NAMEDATALEN */
 	int			float8byval;	/* FLOAT8PASSBYVAL */
 	char		abi_extra[32];	/* see pg_config_manual.h */
+	pg_module_info module_extra;
 } Pg_magic_struct;
 
-/* The actual data block contents */
-#define PG_MODULE_MAGIC_DATA \
+/* The actual data block contents
+ *
+ * Fill the module info part with zeros by default. Zero-length module name
+ * indicates that it is not initialised.
+ */
+#define PG_MODULE_MAGIC_DATA(...) \
 { \
 	sizeof(Pg_magic_struct), \
 	PG_VERSION_NUM / 100, \
@@ -481,6 +492,7 @@ typedef struct
 	NAMEDATALEN, \
 	FLOAT8PASSBYVAL, \
 	FMGR_ABI_EXTRA, \
+	{__VA_ARGS__}, \
 }
 
 StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_magic_struct *) 0)->abi_extra),
@@ -500,11 +512,21 @@ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
 const Pg_magic_struct * \
 PG_MAGIC_FUNCTION_NAME(void) \
 { \
-	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \
+	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(0); \
 	return &Pg_magic_data; \
 } \
 extern int no_such_variable
 
+#define PG_MODULE_MAGIC_EXT(...) \
+extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
+const Pg_magic_struct * \
+PG_MAGIC_FUNCTION_NAME(void) \
+{ \
+	static const Pg_magic_struct Pg_magic_data = \
+		PG_MODULE_MAGIC_DATA(__VA_ARGS__); \
+	return &Pg_magic_data; \
+} \
+extern int no_such_variable
 
 /*-------------------------------------------------------------------------
  *		Support routines and macros for callers of fmgr-compatible functions
diff --git a/src/test/modules/test_misc/Makefile b/src/test/modules/test_misc/Makefile
index 919a25fc67f..c576e6d61de 100644
--- a/src/test/modules/test_misc/Makefile
+++ b/src/test/modules/test_misc/Makefile
@@ -2,7 +2,10 @@
 
 TAP_TESTS = 1
 
-EXTRA_INSTALL=src/test/modules/injection_points
+EXTRA_INSTALL=src/test/modules/injection_points \
+				src/test/modules/test_shm_mq \
+				src/test/modules/test_dsa \
+				src/test/modules/test_slru
 
 export enable_injection_points
 
diff --git a/src/test/modules/test_misc/t/008_check_pg_get_modules.pl b/src/test/modules/test_misc/t/008_check_pg_get_modules.pl
new file mode 100644
index 00000000000..57196eb3864
--- /dev/null
+++ b/src/test/modules/test_misc/t/008_check_pg_get_modules.pl
@@ -0,0 +1,65 @@
+#
+# Copyright (c) 2025, PostgreSQL Global Development Group
+#
+# Tests of the pg_get_modules() function
+#
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+# Empty set of loaded libraries
+my $result = $node->safe_psql('postgres',
+	"SELECT module_name,version FROM pg_get_modules()");
+is($result, "", "No modules in the list");
+
+$result = $node->safe_psql('postgres',
+	qq{
+		LOAD 'test_dsa';
+		SELECT module_name,version FROM pg_get_modules();
+	});
+is($result, "|", "One library without version info");
+
+$result = $node->safe_psql('postgres',
+	qq{
+		LOAD 'test_shm_mq';
+		SELECT module_name,version FROM pg_get_modules();
+	});
+is($result, "test_shm_mq|1.0.0", "One library with version num");
+
+$result = $node->safe_psql('postgres',
+	qq{
+		LOAD 'test_dsa';
+		LOAD 'test_shm_mq';
+		SELECT module_name,version FROM pg_get_modules();
+	});
+is($result, "|\ntest_shm_mq|1.0.0", "Couple of libraries, only one with not-null version number and name");
+
+# Combine loaded on startup and dynamically loaded libraries
+
+$node->append_conf('postgresql.conf',
+	"shared_preload_libraries = 'test_slru'");
+$node->restart;
+
+$result = $node->safe_psql('postgres',
+	qq{
+		SELECT module_name,version FROM pg_get_modules();
+	});
+is($result, "test_slru|1.2.3", "One library with version info");
+
+$result = $node->safe_psql('postgres',
+	qq{
+		LOAD 'test_dsa';
+		LOAD 'test_shm_mq';
+		SELECT module_name,version FROM pg_get_modules();
+	});
+is($result, "test_slru|1.2.3\n|\ntest_shm_mq|1.0.0", "Three libraries, two with not-null version number and name");
+
+
+done_testing();
diff --git a/src/test/modules/test_shm_mq/test.c b/src/test/modules/test_shm_mq/test.c
index 443281addd0..130d1f81300 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -20,7 +20,10 @@
 
 #include "test_shm_mq.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+	.name = "test_shm_mq",
+	.version = "1.0.0"
+);
 
 PG_FUNCTION_INFO_V1(test_shm_mq);
 PG_FUNCTION_INFO_V1(test_shm_mq_pipelined);
diff --git a/src/test/modules/test_slru/test_slru.c b/src/test/modules/test_slru/test_slru.c
index 3ea5ceb8552..ed19ee1e719 100644
--- a/src/test/modules/test_slru/test_slru.c
+++ b/src/test/modules/test_slru/test_slru.c
@@ -22,7 +22,10 @@
 #include "storage/shmem.h"
 #include "utils/builtins.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+	.name = "test_slru",
+	.version = "1.2.3"
+);
 
 /*
  * SQL-callable entry points
-- 
2.48.1

#36Andrei Lepikhov
lepihov@gmail.com
In reply to: Andrei Lepikhov (#35)
1 attachment(s)
Re: Add Postgres module info

On 7/3/2025 16:56, Andrei Lepikhov wrote:

On 2/3/2025 20:35, Andrei Lepikhov wrote:

On 17/2/2025 04:00, Michael Paquier wrote:

No documentation provided.

Ok, I haven't been sure this idea has a chance to be committed. I will
introduce the docs in the next version.

This is a new version with bug fixes. Also, use TAP tests instead of
regression tests. Still, no documentation is included.

v5 contains documentation entries for the pg_get_modules function and
the PG_MODULE_MAGIC_EXT macro. Also, commit comment is smoothed a little.

--
regards, Andrei Lepikhov

Attachments:

v5-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchtext/plain; charset=UTF-8; name=v5-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchDownload
From b5072ce898e0760a717411535c0429303f4e5a45 Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepihov@gmail.com>
Date: Tue, 19 Nov 2024 18:45:36 +0700
Subject: [PATCH v5] Introduce PG_MODULE_MAGIC_EXT macro.

This macro provides dynamically loaded shared libraries (modules) with standard
way to incorporate version (supposedly, defined according to semantic versioning
specification) and name data. The introduced catalogue routine pg_get_modules can
be used to find this module by name and check the version. It makes users
independent from file naming conventions.

With a growing number of Postgres core hooks and the introduction of named DSM
segments, the number of modules that don't need to be loaded on startup may
grow fast. Moreover, in many cases related to query tree transformation or
extra path recommendation, such modules might not need database objects except
GUCs - see auto_explain as an example. That means they don't need to execute
the 'CREATE EXTENSION' statement at all and don't have a record in
the pg_extension table. Such a trick provides much flexibility, including
an online upgrade and may become widespread.

In addition, it is also convenient in support to be sure that the installation
(or at least the backend) includes a specific version of the module. Even if
a module has an installation script, it is not rare that it provides
an implementation for a range of UI routine versions. It makes sense to ensure
which specific version of the code is used.

Discussions [1,2] already mentioned module-info stuff, but at that time,
extensibility techniques and extension popularity were low, and it wasn't
necessary to provide that data.

[1] https://www.postgresql.org/message-id/flat/20060507211705.GB3808%40svana.org
[2] https://www.postgresql.org/message-id/flat/20051106162658.34c31d57%40thunder.logicalchaos.org
---
 doc/src/sgml/xfunc.sgml                       | 44 +++++++++++++
 src/backend/utils/fmgr/dfmgr.c                | 66 ++++++++++++++++++-
 src/include/catalog/pg_proc.dat               |  6 ++
 src/include/fmgr.h                            | 28 +++++++-
 src/test/modules/test_misc/Makefile           |  5 +-
 .../test_misc/t/008_check_pg_get_modules.pl   | 65 ++++++++++++++++++
 src/test/modules/test_shm_mq/test.c           |  5 +-
 src/test/modules/test_slru/test_slru.c        |  5 +-
 8 files changed, 216 insertions(+), 8 deletions(-)
 create mode 100644 src/test/modules/test_misc/t/008_check_pg_get_modules.pl

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 9f22dacac7d..22e26258b15 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1967,6 +1967,28 @@ CREATE FUNCTION square_root(double precision) RETURNS double precision
 
 <programlisting>
 PG_MODULE_MAGIC;
+</programlisting>
+or
+<programlisting>
+PG_MODULE_MAGIC_EXT(name, version);
+</programlisting>
+   </para>
+
+   <indexterm zone="xfunc-c-dynload">
+    <primary>extended magic block</primary>
+   </indexterm>
+   <para>
+    Extended magic block provides the same functionality like PG_MODULE_MAGIC,
+    allowing the extension provider to hard code into the binary file
+    information about the
+    version and module name that can be read and processed later by the <function>
+    pg_get_modules</function> function.
+
+<programlisting>
+PG_MODULE_MAGIC_EXT(
+    .name = "my_module_name",
+    .version = "1.2.3"
+);
 </programlisting>
    </para>
 
@@ -1993,6 +2015,28 @@ PG_MODULE_MAGIC;
     return void.  There is presently no way to unload a dynamically loaded file.
    </para>
 
+   <indexterm zone="xfunc-c-dynload">
+    <primary>pg_get_modules</primary>
+   </indexterm>
+   <indexterm zone="xfunc-c-dynload">
+    <primary>List of loaded modules</primary>
+   </indexterm>
+
+   <para>
+    To see the status of loaded modules a function named <function>
+    pg_get_modules</function> is used.
+    The function receives no parameters and returns whole set of loaded modules
+    in current backend. The result can differ among backends because some
+    modules might be loaded dynamically by the <command>LOAD</command> command.
+    Returns a set of records describing the module.
+    The <parameter>module_name</parameter> column of <type>text</type> type
+    contains the name the maintainer provided at the compilation stage or NULL
+    if not defined.
+    The <parameter>version</parameter> column of <type>text</type> type contains
+    version info hard coded during the compilation or NULL if not defined.
+    The <parameter>libname</parameter> column of <type>text</type> type contains
+    full path to the file of loaded module.
+   </para>
   </sect2>
 
    <sect2 id="xfunc-c-basetype">
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 87b233cb887..70d6ced4157 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -21,10 +21,12 @@
 #endif							/* !WIN32 */
 
 #include "fmgr.h"
+#include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
+#include "utils/builtins.h"
 #include "utils/hsearch.h"
 
 
@@ -51,6 +53,7 @@ typedef struct df_files
 	ino_t		inode;			/* Inode number of file */
 #endif
 	void	   *handle;			/* a handle for pg_dl* functions */
+	const pg_module_info *minfo;
 	char		filename[FLEXIBLE_ARRAY_MEMBER];	/* Full pathname of file */
 } DynamicFileList;
 
@@ -75,7 +78,7 @@ static char *substitute_libpath_macro(const char *name);
 static char *find_in_dynamic_libpath(const char *basename);
 
 /* Magic structure that module needs to match to be accepted */
-static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
+static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA(0);
 
 
 /*
@@ -245,8 +248,14 @@ internal_load_library(const char *libname)
 		{
 			const Pg_magic_struct *magic_data_ptr = (*magic_func) ();
 
+			/*
+			 * Check magic field from loading library to be sure it  compiled
+			 * for the same Postgres code. Skip maintainer fields at the end
+			 * of the struct.
+			 */
 			if (magic_data_ptr->len != magic_data.len ||
-				memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0)
+				memcmp(magic_data_ptr, &magic_data,
+					   offsetof(Pg_magic_struct, module_extra)) != 0)
 			{
 				/* copy data block before unlinking library */
 				Pg_magic_struct module_magic_data = *magic_data_ptr;
@@ -258,6 +267,9 @@ internal_load_library(const char *libname)
 				/* issue suitable complaint */
 				incompatible_module_error(libname, &module_magic_data);
 			}
+
+			/* Save link to the maintainer-provided info */
+			file_scanner->minfo = &magic_data_ptr->module_extra;
 		}
 		else
 		{
@@ -675,3 +687,53 @@ RestoreLibraryState(char *start_address)
 		start_address += strlen(start_address) + 1;
 	}
 }
+
+Datum
+pg_get_modules(PG_FUNCTION_ARGS)
+{
+	FuncCallContext	   *funcctx;
+	MemoryContext		oldcontext;
+	DynamicFileList	   *file_scanner;
+	Datum				result;
+	Datum				values[3];
+	bool				isnull[3] = {0};
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		TupleDesc	tupdesc;
+
+		funcctx = SRF_FIRSTCALL_INIT();
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+
+		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+		funcctx->user_fctx = (void *) file_list;
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	funcctx = SRF_PERCALL_SETUP();
+	file_scanner = (DynamicFileList *) funcctx->user_fctx;
+
+	if (file_scanner != NULL)
+	{
+		if (file_scanner->minfo->name == NULL)
+			isnull[0] = true;
+		else
+			values[0] = CStringGetTextDatum(file_scanner->minfo->name);
+		if (file_scanner->minfo->version == NULL)
+			isnull[1] = true;
+		else
+			values[1] = CStringGetTextDatum(file_scanner->minfo->version);
+
+		values[2] = CStringGetTextDatum(file_scanner->filename);
+		result = HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc,
+												   values, isnull));
+		funcctx->user_fctx = file_scanner->next;
+		SRF_RETURN_NEXT(funcctx, result);
+	}
+	else
+		SRF_RETURN_DONE(funcctx);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 42e427f8fe8..1bbcc5211d3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -311,6 +311,12 @@
   proname => 'scalargtjoinsel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int2 internal',
   prosrc => 'scalargtjoinsel' },
+{ oid => '111', descr => 'Module Info',
+  proname => 'pg_get_modules', provolatile => 's', prorettype => 'record',
+  proretset => 't', proargtypes => '', proallargtypes => '{text,text,text}',
+  proargmodes => '{o,o,o}', prorows => 10,
+  proargnames => '{module_name,version,libname}',
+  prosrc => 'pg_get_modules' },
 
 { oid => '336',
   descr => 'restriction selectivity of <= and related operators on scalar datatypes',
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index e609ea875a7..5018abf3a18 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -459,6 +459,12 @@ extern PGDLLEXPORT void _PG_init(void);
  *-------------------------------------------------------------------------
  */
 
+typedef struct pg_module_info
+{
+	const char *name;
+	const char *version;
+} pg_module_info;
+
 /* Definition of the magic block structure */
 typedef struct
 {
@@ -469,10 +475,15 @@ typedef struct
 	int			namedatalen;	/* NAMEDATALEN */
 	int			float8byval;	/* FLOAT8PASSBYVAL */
 	char		abi_extra[32];	/* see pg_config_manual.h */
+	pg_module_info module_extra;
 } Pg_magic_struct;
 
-/* The actual data block contents */
-#define PG_MODULE_MAGIC_DATA \
+/* The actual data block contents
+ *
+ * Fill the module info part with zeros by default. Zero-length module name
+ * indicates that it is not initialised.
+ */
+#define PG_MODULE_MAGIC_DATA(...) \
 { \
 	sizeof(Pg_magic_struct), \
 	PG_VERSION_NUM / 100, \
@@ -481,6 +492,7 @@ typedef struct
 	NAMEDATALEN, \
 	FLOAT8PASSBYVAL, \
 	FMGR_ABI_EXTRA, \
+	{__VA_ARGS__}, \
 }
 
 StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_magic_struct *) 0)->abi_extra),
@@ -500,11 +512,21 @@ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
 const Pg_magic_struct * \
 PG_MAGIC_FUNCTION_NAME(void) \
 { \
-	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \
+	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(0); \
 	return &Pg_magic_data; \
 } \
 extern int no_such_variable
 
+#define PG_MODULE_MAGIC_EXT(...) \
+extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
+const Pg_magic_struct * \
+PG_MAGIC_FUNCTION_NAME(void) \
+{ \
+	static const Pg_magic_struct Pg_magic_data = \
+		PG_MODULE_MAGIC_DATA(__VA_ARGS__); \
+	return &Pg_magic_data; \
+} \
+extern int no_such_variable
 
 /*-------------------------------------------------------------------------
  *		Support routines and macros for callers of fmgr-compatible functions
diff --git a/src/test/modules/test_misc/Makefile b/src/test/modules/test_misc/Makefile
index 919a25fc67f..c576e6d61de 100644
--- a/src/test/modules/test_misc/Makefile
+++ b/src/test/modules/test_misc/Makefile
@@ -2,7 +2,10 @@
 
 TAP_TESTS = 1
 
-EXTRA_INSTALL=src/test/modules/injection_points
+EXTRA_INSTALL=src/test/modules/injection_points \
+				src/test/modules/test_shm_mq \
+				src/test/modules/test_dsa \
+				src/test/modules/test_slru
 
 export enable_injection_points
 
diff --git a/src/test/modules/test_misc/t/008_check_pg_get_modules.pl b/src/test/modules/test_misc/t/008_check_pg_get_modules.pl
new file mode 100644
index 00000000000..57196eb3864
--- /dev/null
+++ b/src/test/modules/test_misc/t/008_check_pg_get_modules.pl
@@ -0,0 +1,65 @@
+#
+# Copyright (c) 2025, PostgreSQL Global Development Group
+#
+# Tests of the pg_get_modules() function
+#
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+# Empty set of loaded libraries
+my $result = $node->safe_psql('postgres',
+	"SELECT module_name,version FROM pg_get_modules()");
+is($result, "", "No modules in the list");
+
+$result = $node->safe_psql('postgres',
+	qq{
+		LOAD 'test_dsa';
+		SELECT module_name,version FROM pg_get_modules();
+	});
+is($result, "|", "One library without version info");
+
+$result = $node->safe_psql('postgres',
+	qq{
+		LOAD 'test_shm_mq';
+		SELECT module_name,version FROM pg_get_modules();
+	});
+is($result, "test_shm_mq|1.0.0", "One library with version num");
+
+$result = $node->safe_psql('postgres',
+	qq{
+		LOAD 'test_dsa';
+		LOAD 'test_shm_mq';
+		SELECT module_name,version FROM pg_get_modules();
+	});
+is($result, "|\ntest_shm_mq|1.0.0", "Couple of libraries, only one with not-null version number and name");
+
+# Combine loaded on startup and dynamically loaded libraries
+
+$node->append_conf('postgresql.conf',
+	"shared_preload_libraries = 'test_slru'");
+$node->restart;
+
+$result = $node->safe_psql('postgres',
+	qq{
+		SELECT module_name,version FROM pg_get_modules();
+	});
+is($result, "test_slru|1.2.3", "One library with version info");
+
+$result = $node->safe_psql('postgres',
+	qq{
+		LOAD 'test_dsa';
+		LOAD 'test_shm_mq';
+		SELECT module_name,version FROM pg_get_modules();
+	});
+is($result, "test_slru|1.2.3\n|\ntest_shm_mq|1.0.0", "Three libraries, two with not-null version number and name");
+
+
+done_testing();
diff --git a/src/test/modules/test_shm_mq/test.c b/src/test/modules/test_shm_mq/test.c
index 443281addd0..130d1f81300 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -20,7 +20,10 @@
 
 #include "test_shm_mq.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+	.name = "test_shm_mq",
+	.version = "1.0.0"
+);
 
 PG_FUNCTION_INFO_V1(test_shm_mq);
 PG_FUNCTION_INFO_V1(test_shm_mq_pipelined);
diff --git a/src/test/modules/test_slru/test_slru.c b/src/test/modules/test_slru/test_slru.c
index 3ea5ceb8552..ed19ee1e719 100644
--- a/src/test/modules/test_slru/test_slru.c
+++ b/src/test/modules/test_slru/test_slru.c
@@ -22,7 +22,10 @@
 #include "storage/shmem.h"
 #include "utils/builtins.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+	.name = "test_slru",
+	.version = "1.2.3"
+);
 
 /*
  * SQL-callable entry points
-- 
2.48.1

#37Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrei Lepikhov (#36)
1 attachment(s)
Re: Add Postgres module info

I spent awhile reviewing the v5 patch, and here's a proposed v6.
Some notes:

* I didn't like depending on offsetof(Pg_magic_struct, module_extra)
to determine which parts of the struct are checked for compatibility.
It just seems way too easy to break that with careless insertion
of new fields, and such breakage might not cause obvious failures.
I think the right thing is to break out the ABI-checking fields as
their own sub-struct, rather than breaking out the new fields as a
sub-struct.

* I renamed the inquiry function to pg_get_loaded_modules, since
it only works on loaded modules but that's hardly clear from the
previous name.

* It is not clear to me what permission restrictions we should put
on pg_get_loaded_modules, but it is clear that "none" is the wrong
answer. In particular, exposing the full file path of loaded modules
is against our rules: unprivileged users are not supposed to be able
to learn anything about the filesystem underneath the server. (This
is why for instance an unprivileged user can't read the data_directory
GUC.) In the attached I made the library path read as NULL unless the
user has pg_read_server_files, but I'm not attached to that specific
solution. One thing not to like is that it's very likely that you'd
just get a row of NULLs and no useful info about a module at all.
Another idea perhaps could be to strip off the directory path and
maybe the filename extension if the user doesn't have privilege.
Or we could remove the internal permission check and instead gate
access to the function altogether with grantable EXECUTE privilege.
(This might be the right answer, since it's not clear that Joe
Unprivileged User should be able to know what modules are loaded; some
of them might have security implications.) In any case, requiring
pg_read_server_files feels a little too strong, but I don't see an
alternative role I like better. The EXECUTE-privilege answer would at
least let installations adjust the function's availability to their
liking.

* I didn't like anything about the test setup. Making test_misc
dependent on other modules is a recipe for confusion, and perhaps for
failures in parallel builds. (Yes, I see somebody already made it
depend on injection_points. But doubling down on a bad idea doesn't
make it less bad.) Also, the test would fail completely in an
installation that came with any preloaded modules, which hardly seems
like an improbable future situation. I think we need to restrict what
modules we're looking at with a WHERE clause to prevent that. After
some thought I went back to the upthread idea of just having
auto_explain as a test case.

Still TBD:

* I'm not happy with putting pg_get_loaded_modules into dfmgr.c.
It feels like the wrong layer to have a SQL-callable function,
and the large expansion in its #include list is evidence that we're
adding functionality that doesn't belong there. But I'm not quite
sure where to put it instead. Also, the naive way to do that would
require exporting DynamicFileList which doesn't feel nice either.
Maybe we could make dfmgr.c export some sort of iterator function?

* Should we convert our existing modules to use PG_MODULE_MAGIC_EXT?
I'm mildly in favor of that, but I think we'd need some automated way
to manage their version strings, and I don't know what that ought to
look like. Maybe it'd be enough to make all the in-core modules use
PG_VERSION as their version string, but I think that might put a dent
in the idea of the version strings following semantic versioning
rules.

regards, tom lane

Attachments:

v6-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchtext/x-diff; charset=us-ascii; name=v6-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchDownload
From 2f8e182dbd26e03a9568393307b64cb26b3842fd Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sat, 22 Mar 2025 18:39:15 -0400
Subject: [PATCH v6] Introduce PG_MODULE_MAGIC_EXT macro.

This macro allows dynamically loaded shared libraries (modules) to
provide a wired-in module name and version, and possibly other
compile-time-constant fields in future.  This information can be
retrieved with the new pg_get_loaded_modules() function.

This feature is expected to be particularly useful for modules
that do not have any exposed SQL functionality and thus are
not associated with a SQL-level extension object.  But even for
modules that do belong to extensions, being able to verify the
actual code version can be useful.

Author: Andrei Lepikhov <lepihov@gmail.com>
Reviewed-by: Yurii Rashkovskii <yrashk@omnigres.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/dd4d1b59-d0fe-49d5-b28f-1e463b68fa32@gmail.com
---
 contrib/auto_explain/auto_explain.c        |  5 +-
 contrib/auto_explain/t/001_auto_explain.pl | 11 +++
 doc/src/sgml/func.sgml                     | 24 +++++++
 doc/src/sgml/xfunc.sgml                    | 24 +++++++
 src/backend/utils/fmgr/dfmgr.c             | 81 ++++++++++++++++++++--
 src/include/catalog/pg_proc.dat            |  7 ++
 src/include/fmgr.h                         | 62 ++++++++++++++---
 src/tools/pgindent/typedefs.list           |  1 +
 8 files changed, 196 insertions(+), 19 deletions(-)

diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index 3b73bd19107..72615a3c116 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -22,7 +22,10 @@
 #include "executor/instrument.h"
 #include "utils/guc.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "auto_explain",
+					.version = "1.0.0"
+);
 
 /* GUC variables */
 static int	auto_explain_log_min_duration = -1; /* msec or -1 */
diff --git a/contrib/auto_explain/t/001_auto_explain.pl b/contrib/auto_explain/t/001_auto_explain.pl
index 25252604b7d..07692b20e71 100644
--- a/contrib/auto_explain/t/001_auto_explain.pl
+++ b/contrib/auto_explain/t/001_auto_explain.pl
@@ -212,4 +212,15 @@ REVOKE SET ON PARAMETER auto_explain.log_format FROM regress_user1;
 DROP USER regress_user1;
 });
 
+# Test pg_get_loaded_modules() function.  This function is particularly
+# useful for modules with no SQL presence, such as auto_explain.
+
+my $res = $node->safe_psql(
+	"postgres", q{
+SELECT module_name, version, library_path != '' as library_path_provided
+FROM pg_get_loaded_modules()
+WHERE module_name = 'auto_explain';
+});
+like($res, qr/^auto_explain\|1\.0\.0\|t$/, "pg_get_loaded_modules() ok");
+
 done_testing();
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6fa1d6586b8..306f1be2715 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25002,6 +25002,30 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_get_loaded_modules</primary>
+        </indexterm>
+        <function>pg_get_loaded_modules</function> ()
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>module_name</parameter> <type>text</type>,
+        <parameter>version</parameter> <type>text</type>,
+        <parameter>library_path</parameter> <type>text</type> )
+       </para>
+       <para>
+        Returns a list of the loadable modules that are loaded into the
+        current server session.  The <parameter>module_name</parameter>
+        and <parameter>version</parameter> fields are NULL unless the
+        module author supplied values for them using
+        the <literal>PG_MODULE_MAGIC_EXT</literal> macro.
+        The <parameter>library_path</parameter> field gives the full path
+        name of the loaded module, but it is NULL if the user does not
+        have the privileges of the <literal>pg_read_server_files</literal>
+        role.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 9f22dacac7d..0f8f6040d9b 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1968,6 +1968,30 @@ CREATE FUNCTION square_root(double precision) RETURNS double precision
 <programlisting>
 PG_MODULE_MAGIC;
 </programlisting>
+or
+<programlisting>
+PG_MODULE_MAGIC_EXT(<replaceable>parameters</replaceable>);
+</programlisting>
+   </para>
+
+   <para>
+    The <literal>PG_MODULE_MAGIC_EXT</literal> macro allows the specification
+    of additional information about the module; currently, a name and/or a
+    version string can be added.  (More fields might be allowed in future.)
+    Write something like this:
+
+<programlisting>
+PG_MODULE_MAGIC_EXT(
+    .name = "my_module_name",
+    .version = "1.2.3"
+);
+</programlisting>
+
+    Subsequently the name and version can be examined via
+    the <function>pg_get_loaded_modules()</function> function.
+    The meaning of the version string is not restricted
+    by <productname>PostgreSQL</productname>, but use of semantic versioning
+    rules is recommended.
    </para>
 
    <para>
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index dd4c83d1bba..e430410d080 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -20,11 +20,15 @@
 #include <dlfcn.h>
 #endif							/* !WIN32 */
 
+#include "catalog/pg_authid_d.h"
 #include "fmgr.h"
+#include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
 #include "utils/hsearch.h"
 
 
@@ -51,6 +55,7 @@ typedef struct df_files
 	ino_t		inode;			/* Inode number of file */
 #endif
 	void	   *handle;			/* a handle for pg_dl* functions */
+	const Pg_magic_struct *magic;	/* Location of module's magic block */
 	char		filename[FLEXIBLE_ARRAY_MEMBER];	/* Full pathname of file */
 } DynamicFileList;
 
@@ -68,12 +73,12 @@ char	   *Dynamic_library_path;
 
 static void *internal_load_library(const char *libname);
 pg_noreturn static void incompatible_module_error(const char *libname,
-												  const Pg_magic_struct *module_magic_data);
+												  const Pg_abi_values *module_magic_data);
 static char *expand_dynamic_library_name(const char *name);
 static void check_restricted_library_name(const char *name);
 
-/* Magic structure that module needs to match to be accepted */
-static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
+/* ABI values that module needs to match to be accepted */
+static const Pg_abi_values magic_data = PG_MODULE_ABI_DATA;
 
 
 /*
@@ -243,8 +248,10 @@ internal_load_library(const char *libname)
 		{
 			const Pg_magic_struct *magic_data_ptr = (*magic_func) ();
 
-			if (magic_data_ptr->len != magic_data.len ||
-				memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0)
+			/* Check ABI compatibility fields */
+			if (magic_data_ptr->len != sizeof(Pg_magic_struct) ||
+				memcmp(&magic_data_ptr->abi_fields, &magic_data,
+					   sizeof(Pg_abi_values)) != 0)
 			{
 				/* copy data block before unlinking library */
 				Pg_magic_struct module_magic_data = *magic_data_ptr;
@@ -254,8 +261,11 @@ internal_load_library(const char *libname)
 				free(file_scanner);
 
 				/* issue suitable complaint */
-				incompatible_module_error(libname, &module_magic_data);
+				incompatible_module_error(libname, &module_magic_data.abi_fields);
 			}
+
+			/* Remember the magic block's location for future use */
+			file_scanner->magic = magic_data_ptr;
 		}
 		else
 		{
@@ -292,7 +302,7 @@ internal_load_library(const char *libname)
  */
 static void
 incompatible_module_error(const char *libname,
-						  const Pg_magic_struct *module_magic_data)
+						  const Pg_abi_values *module_magic_data)
 {
 	StringInfoData details;
 
@@ -694,3 +704,60 @@ RestoreLibraryState(char *start_address)
 		start_address += strlen(start_address) + 1;
 	}
 }
+
+/*
+ * SQL-callable function to get per-loaded-module information.
+ */
+Datum
+pg_get_loaded_modules(PG_FUNCTION_ARGS)
+{
+	FuncCallContext *funcctx;
+	MemoryContext oldcontext;
+	DynamicFileList *file_scanner;
+	Datum		result;
+	Datum		values[3];
+	bool		isnull[3] = {0};
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		TupleDesc	tupdesc;
+
+		funcctx = SRF_FIRSTCALL_INIT();
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+
+		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+		funcctx->user_fctx = (void *) file_list;
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	funcctx = SRF_PERCALL_SETUP();
+	file_scanner = (DynamicFileList *) funcctx->user_fctx;
+
+	if (file_scanner != NULL)
+	{
+		if (file_scanner->magic->name == NULL)
+			isnull[0] = true;
+		else
+			values[0] = CStringGetTextDatum(file_scanner->magic->name);
+		if (file_scanner->magic->version == NULL)
+			isnull[1] = true;
+		else
+			values[1] = CStringGetTextDatum(file_scanner->magic->version);
+		if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+			isnull[2] = true;
+		else
+			values[2] = CStringGetTextDatum(file_scanner->filename);
+
+		result = HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc,
+												   values, isnull));
+
+		funcctx->user_fctx = file_scanner->next;
+		SRF_RETURN_NEXT(funcctx, result);
+	}
+	else
+		SRF_RETURN_DONE(funcctx);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 890822eaf79..86389afa3f2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6749,6 +6749,13 @@
   proargnames => '{rm_id, rm_name, rm_builtin}',
   prosrc => 'pg_get_wal_resource_managers' },
 
+{ oid => '8303', descr => 'get info about loaded modules',
+  proname => 'pg_get_loaded_modules', prorows => '10', proretset => 't',
+  provolatile => 'v', proparallel => 'r', prorettype => 'record',
+  proargtypes => '', proallargtypes => '{text,text,text}',
+  proargmodes => '{o,o,o}', proargnames => '{module_name,version,library_path}',
+  prosrc => 'pg_get_loaded_modules' },
+
 { oid => '2621', descr => 'reload configuration files',
   proname => 'pg_reload_conf', provolatile => 'v', prorettype => 'bool',
   proargtypes => '', prosrc => 'pg_reload_conf' },
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 82ee38b31e5..c6dbb76d68e 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -440,11 +440,14 @@ extern PGDLLEXPORT void _PG_init(void);
  * We require dynamically-loaded modules to include the macro call
  *		PG_MODULE_MAGIC;
  * so that we can check for obvious incompatibility, such as being compiled
- * for a different major PostgreSQL version.
+ * for a different major PostgreSQL version.  Alternatively, write
+ *		PG_MODULE_MAGIC_EXT(...);
+ * where the optional arguments can specify module name and version, and
+ * perhaps other values in future.  Note that in a multiple-source-file
+ * module, there should be exactly one such macro call.
  *
- * To compile with versions of PostgreSQL that do not support this,
- * you may put an #ifdef/#endif test around it.  Note that in a multiple-
- * source-file module, the macro call should only appear once.
+ * You may need an #ifdef test to verify that the version of PostgreSQL
+ * you are compiling against supports PG_MODULE_MAGIC_EXT().
  *
  * The specific items included in the magic block are intended to be ones that
  * are custom-configurable and especially likely to break dynamically loaded
@@ -459,22 +462,30 @@ extern PGDLLEXPORT void _PG_init(void);
  *-------------------------------------------------------------------------
  */
 
-/* Definition of the magic block structure */
+/* Definition of the values we check to verify ABI compatibility */
 typedef struct
 {
-	int			len;			/* sizeof(this struct) */
 	int			version;		/* PostgreSQL major version */
 	int			funcmaxargs;	/* FUNC_MAX_ARGS */
 	int			indexmaxkeys;	/* INDEX_MAX_KEYS */
 	int			namedatalen;	/* NAMEDATALEN */
 	int			float8byval;	/* FLOAT8PASSBYVAL */
 	char		abi_extra[32];	/* see pg_config_manual.h */
+} Pg_abi_values;
+
+/* Definition of the magic block structure */
+typedef struct
+{
+	int			len;			/* sizeof(this struct) */
+	Pg_abi_values abi_fields;	/* see above */
+	/* Remaining fields are zero unless filled via PG_MODULE_MAGIC_EXT */
+	const char *name;			/* optional module name */
+	const char *version;		/* optional module version */
 } Pg_magic_struct;
 
-/* The actual data block contents */
-#define PG_MODULE_MAGIC_DATA \
+/* Macro to fill the ABI fields */
+#define PG_MODULE_ABI_DATA \
 { \
-	sizeof(Pg_magic_struct), \
 	PG_VERSION_NUM / 100, \
 	FUNC_MAX_ARGS, \
 	INDEX_MAX_KEYS, \
@@ -483,7 +494,18 @@ typedef struct
 	FMGR_ABI_EXTRA, \
 }
 
-StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_magic_struct *) 0)->abi_extra),
+/*
+ * Macro to fill a magic block.  If any arguments are given, they should
+ * be field initializers.
+ */
+#define PG_MODULE_MAGIC_DATA(...) \
+{ \
+	.len = sizeof(Pg_magic_struct), \
+	.abi_fields = PG_MODULE_ABI_DATA, \
+	__VA_ARGS__ \
+}
+
+StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_abi_values *) 0)->abi_extra),
 				 "FMGR_ABI_EXTRA too long");
 
 /*
@@ -500,11 +522,29 @@ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
 const Pg_magic_struct * \
 PG_MAGIC_FUNCTION_NAME(void) \
 { \
-	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \
+	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(0); \
 	return &Pg_magic_data; \
 } \
 extern int no_such_variable
 
+/*
+ * Alternate declaration that allows specification of additional fields.
+ * The additional values should be written as field initializers, for example
+ *	PG_MODULE_MAGIC_EXT(
+ *		.name = "some string",
+ *		.version = "some string"
+ *	);
+ */
+#define PG_MODULE_MAGIC_EXT(...) \
+extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
+const Pg_magic_struct * \
+PG_MAGIC_FUNCTION_NAME(void) \
+{ \
+	static const Pg_magic_struct Pg_magic_data = \
+		PG_MODULE_MAGIC_DATA(__VA_ARGS__); \
+	return &Pg_magic_data; \
+} \
+extern int no_such_variable
 
 /*-------------------------------------------------------------------------
  *		Support routines and macros for callers of fmgr-compatible functions
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bfa276d2d35..854b16d0dcc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2228,6 +2228,7 @@ PgStat_WalCounters
 PgStat_WalStats
 PgXmlErrorContext
 PgXmlStrictness
+Pg_abi_values
 Pg_finfo_record
 Pg_magic_struct
 PipeProtoChunk
-- 
2.43.5

#38Andrei Lepikhov
lepihov@gmail.com
In reply to: Tom Lane (#37)
Re: Add Postgres module info

On 3/22/25 23:49, Tom Lane wrote:

I spent awhile reviewing the v5 patch, and here's a proposed v6.
Some notes:

* I didn't like depending on offsetof(Pg_magic_struct, module_extra)
to determine which parts of the struct are checked for compatibility.
It just seems way too easy to break that with careless insertion
of new fields, and such breakage might not cause obvious failures.
I think the right thing is to break out the ABI-checking fields as
their own sub-struct, rather than breaking out the new fields as a
sub-struct.

Agree. It is a clear approach. I like it.

* I renamed the inquiry function to pg_get_loaded_modules, since
it only works on loaded modules but that's hardly clear from the
previous name.

+1

* It is not clear to me what permission restrictions we should put
on pg_get_loaded_modules, ...

I vote for the idea of stripping the full path to just a filename. My
initial use cases were:
1. User reports the issue and need to provide me all loaded modules at
the moment of query execution. Higher privileges needs administrative
procedures that is a long way and not all the time possible.
2. A module needs to detect another loaded module - it is not a frequent
case so far, but concurrency on queryId with pg_stat_statements is at
least one of my examples happening sometimes.

Also, permissions here should be in agreement with permissions on
pg_available_extensions(), right?

* I didn't like anything about the test setup. ...

Ok, thanks. I just played with alternatives.

Still TBD:

* I'm not happy with putting pg_get_loaded_modules into dfmgr.c.
It feels like the wrong layer to have a SQL-callable function,
and the large expansion in its #include list is evidence that we're
adding functionality that doesn't belong there. But I'm not quite
sure where to put it instead. Also, the naive way to do that would
require exporting DynamicFileList which doesn't feel nice either.
Maybe we could make dfmgr.c export some sort of iterator function?

I just attempted to reduce number of exported objects here. If it is ok
to introduce an iterator, the pg_get_loaded_modules() may live in
extension.c

* Should we convert our existing modules to use PG_MODULE_MAGIC_EXT?
I'm mildly in favor of that, but I think we'd need some automated way
to manage their version strings, and I don't know what that ought to
look like. Maybe it'd be enough to make all the in-core modules use
PG_VERSION as their version string, but I think that might put a dent
in the idea of the version strings following semantic versioning
rules.

Yes, additional burden to bump version string was a stopper for me to
propose such a brave idea.

--
regards, Andrei Lepikhov

#39Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrei Lepikhov (#38)
2 attachment(s)
Re: Add Postgres module info

Andrei Lepikhov <lepihov@gmail.com> writes:

On 3/22/25 23:49, Tom Lane wrote:

* It is not clear to me what permission restrictions we should put
on pg_get_loaded_modules, ...

I vote for the idea of stripping the full path to just a filename.

Works for me. v7 attached does it that way.

* I'm not happy with putting pg_get_loaded_modules into dfmgr.c.

I just attempted to reduce number of exported objects here. If it is ok
to introduce an iterator, the pg_get_loaded_modules() may live in
extension.c

Yeah, I like that better than leaving it in dfmgr.c, so done that way.
The iterator functions also provide some cover for dealing with
on-the-fly changes of the file list, if we ever need that.

I converted pg_get_loaded_modules to run just once and deliver its
results in a tuplestore. That's partly because the adjacent SRFs
in extension.c work like that, but mostly because it removes the
hazard of the file list changing mid-run.

* Should we convert our existing modules to use PG_MODULE_MAGIC_EXT?
I'm mildly in favor of that, but I think we'd need some automated way
to manage their version strings, and I don't know what that ought to
look like. Maybe it'd be enough to make all the in-core modules use
PG_VERSION as their version string, but I think that might put a dent
in the idea of the version strings following semantic versioning
rules.

Yes, additional burden to bump version string was a stopper for me to
propose such a brave idea.

After sleeping on it, I think we really ought to do that, so 0002
attached does so.

I think this version is ready to commit, if there are not objections
to the decisions mentioned above.

regards, tom lane

Attachments:

v7-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchtext/x-diff; charset=us-ascii; name=v7-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patchDownload
From f9b40d4588ee96b2a86ccb0c44202ac05e7d18ca Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 23 Mar 2025 14:09:14 -0400
Subject: [PATCH v7 1/2] Introduce PG_MODULE_MAGIC_EXT macro.

This macro allows dynamically loaded shared libraries (modules) to
provide a wired-in module name and version, and possibly other
compile-time-constant fields in future.  This information can be
retrieved with the new pg_get_loaded_modules() function.

This feature is expected to be particularly useful for modules
that do not have any exposed SQL functionality and thus are
not associated with a SQL-level extension object.  But even for
modules that do belong to extensions, being able to verify the
actual code version can be useful.

Author: Andrei Lepikhov <lepihov@gmail.com>
Reviewed-by: Yurii Rashkovskii <yrashk@omnigres.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/dd4d1b59-d0fe-49d5-b28f-1e463b68fa32@gmail.com
---
 contrib/auto_explain/auto_explain.c        |  5 +-
 contrib/auto_explain/t/001_auto_explain.pl | 13 ++++
 doc/src/sgml/func.sgml                     | 22 +++++++
 doc/src/sgml/xfunc.sgml                    | 24 ++++++++
 src/backend/commands/extension.c           | 53 ++++++++++++++++
 src/backend/utils/fmgr/dfmgr.c             | 67 ++++++++++++++++----
 src/include/catalog/pg_proc.dat            |  7 +++
 src/include/fmgr.h                         | 71 ++++++++++++++++++----
 src/tools/pgindent/typedefs.list           |  1 +
 9 files changed, 240 insertions(+), 23 deletions(-)

diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index 3b73bd19107..cd6625020a7 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -22,7 +22,10 @@
 #include "executor/instrument.h"
 #include "utils/guc.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "auto_explain",
+					.version = PG_VERSION
+);
 
 /* GUC variables */
 static int	auto_explain_log_min_duration = -1; /* msec or -1 */
diff --git a/contrib/auto_explain/t/001_auto_explain.pl b/contrib/auto_explain/t/001_auto_explain.pl
index 25252604b7d..6af5ac1da18 100644
--- a/contrib/auto_explain/t/001_auto_explain.pl
+++ b/contrib/auto_explain/t/001_auto_explain.pl
@@ -212,4 +212,17 @@ REVOKE SET ON PARAMETER auto_explain.log_format FROM regress_user1;
 DROP USER regress_user1;
 });
 
+# Test pg_get_loaded_modules() function.  This function is particularly
+# useful for modules with no SQL presence, such as auto_explain.
+
+my $res = $node->safe_psql(
+	"postgres", q{
+SELECT module_name,
+       version = current_setting('server_version') as version_ok,
+       regexp_replace(file_name, '\..*', '') as file_name_stripped
+FROM pg_get_loaded_modules()
+WHERE module_name = 'auto_explain';
+});
+like($res, qr/^auto_explain\|t\|auto_explain$/, "pg_get_loaded_modules() ok");
+
 done_testing();
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6fa1d6586b8..0f7fdd88851 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25002,6 +25002,28 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_get_loaded_modules</primary>
+        </indexterm>
+        <function>pg_get_loaded_modules</function> ()
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>module_name</parameter> <type>text</type>,
+        <parameter>version</parameter> <type>text</type>,
+        <parameter>file_name</parameter> <type>text</type> )
+       </para>
+       <para>
+        Returns a list of the loadable modules that are loaded into the
+        current server session.  The <parameter>module_name</parameter>
+        and <parameter>version</parameter> fields are NULL unless the
+        module author supplied values for them using
+        the <literal>PG_MODULE_MAGIC_EXT</literal> macro.
+        The <parameter>file_name</parameter> field gives the file
+        name of the module (shared library).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 9f22dacac7d..44682ff2054 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1968,6 +1968,30 @@ CREATE FUNCTION square_root(double precision) RETURNS double precision
 <programlisting>
 PG_MODULE_MAGIC;
 </programlisting>
+or
+<programlisting>
+PG_MODULE_MAGIC_EXT(<replaceable>parameters</replaceable>);
+</programlisting>
+   </para>
+
+   <para>
+    The <literal>PG_MODULE_MAGIC_EXT</literal> variant allows the specification
+    of additional information about the module; currently, a name and/or a
+    version string can be added.  (More fields might be allowed in future.)
+    Write something like this:
+
+<programlisting>
+PG_MODULE_MAGIC_EXT(
+    .name = "my_module_name",
+    .version = "1.2.3"
+);
+</programlisting>
+
+    Subsequently the name and version can be examined via
+    the <function>pg_get_loaded_modules()</function> function.
+    The meaning of the version string is not restricted
+    by <productname>PostgreSQL</productname>, but use of semantic versioning
+    rules is recommended.
    </para>
 
    <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index dc38c325770..180f4af9be3 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2811,6 +2811,59 @@ pg_extension_config_dump(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * pg_get_loaded_modules
+ *
+ * SQL-callable function to get per-loaded-module information.  Modules
+ * (shared libraries) aren't necessarily one-to-one with extensions, but
+ * they're sufficiently closely related to make this file a good home.
+ */
+Datum
+pg_get_loaded_modules(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	DynamicFileList *file_scanner;
+
+	/* Build tuplestore to hold the result rows */
+	InitMaterializedSRF(fcinfo, 0);
+
+	for (file_scanner = get_first_loaded_module(); file_scanner != NULL;
+		 file_scanner = get_next_loaded_module(file_scanner))
+	{
+		const char *library_path,
+				   *module_name,
+				   *module_version;
+		const char *sep;
+		Datum		values[3] = {0};
+		bool		nulls[3] = {0};
+
+		get_loaded_module_details(file_scanner,
+								  &library_path,
+								  &module_name,
+								  &module_version);
+
+		if (module_name == NULL)
+			nulls[0] = true;
+		else
+			values[0] = CStringGetTextDatum(module_name);
+		if (module_version == NULL)
+			nulls[1] = true;
+		else
+			values[1] = CStringGetTextDatum(module_version);
+
+		/* For security reasons, we don't show the directory path */
+		sep = last_dir_separator(library_path);
+		if (sep)
+			library_path = sep + 1;
+		values[2] = CStringGetTextDatum(library_path);
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
+							 values, nulls);
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * extension_config_remove
  *
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index dd4c83d1bba..603632581d0 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -40,19 +40,21 @@ typedef struct
 
 /*
  * List of dynamically loaded files (kept in malloc'd memory).
+ *
+ * Note: "typedef struct DynamicFileList DynamicFileList" appears in fmgr.h.
  */
-
-typedef struct df_files
+struct DynamicFileList
 {
-	struct df_files *next;		/* List link */
+	DynamicFileList *next;		/* List link */
 	dev_t		device;			/* Device file is on */
 #ifndef WIN32					/* ensures we never again depend on this under
 								 * win32 */
 	ino_t		inode;			/* Inode number of file */
 #endif
 	void	   *handle;			/* a handle for pg_dl* functions */
+	const Pg_magic_struct *magic;	/* Location of module's magic block */
 	char		filename[FLEXIBLE_ARRAY_MEMBER];	/* Full pathname of file */
-} DynamicFileList;
+};
 
 static DynamicFileList *file_list = NULL;
 static DynamicFileList *file_tail = NULL;
@@ -68,12 +70,12 @@ char	   *Dynamic_library_path;
 
 static void *internal_load_library(const char *libname);
 pg_noreturn static void incompatible_module_error(const char *libname,
-												  const Pg_magic_struct *module_magic_data);
+												  const Pg_abi_values *module_magic_data);
 static char *expand_dynamic_library_name(const char *name);
 static void check_restricted_library_name(const char *name);
 
-/* Magic structure that module needs to match to be accepted */
-static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
+/* ABI values that module needs to match to be accepted */
+static const Pg_abi_values magic_data = PG_MODULE_ABI_DATA;
 
 
 /*
@@ -243,8 +245,10 @@ internal_load_library(const char *libname)
 		{
 			const Pg_magic_struct *magic_data_ptr = (*magic_func) ();
 
-			if (magic_data_ptr->len != magic_data.len ||
-				memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0)
+			/* Check ABI compatibility fields */
+			if (magic_data_ptr->len != sizeof(Pg_magic_struct) ||
+				memcmp(&magic_data_ptr->abi_fields, &magic_data,
+					   sizeof(Pg_abi_values)) != 0)
 			{
 				/* copy data block before unlinking library */
 				Pg_magic_struct module_magic_data = *magic_data_ptr;
@@ -254,8 +258,11 @@ internal_load_library(const char *libname)
 				free(file_scanner);
 
 				/* issue suitable complaint */
-				incompatible_module_error(libname, &module_magic_data);
+				incompatible_module_error(libname, &module_magic_data.abi_fields);
 			}
+
+			/* Remember the magic block's location for future use */
+			file_scanner->magic = magic_data_ptr;
 		}
 		else
 		{
@@ -292,7 +299,7 @@ internal_load_library(const char *libname)
  */
 static void
 incompatible_module_error(const char *libname,
-						  const Pg_magic_struct *module_magic_data)
+						  const Pg_abi_values *module_magic_data)
 {
 	StringInfoData details;
 
@@ -393,6 +400,44 @@ incompatible_module_error(const char *libname,
 }
 
 
+/*
+ * Iterator functions to allow callers to scan the list of loaded modules.
+ *
+ * Note: currently, there is no special provision for dealing with changes
+ * in the list while a scan is happening.  Current callers don't need it.
+ */
+DynamicFileList *
+get_first_loaded_module(void)
+{
+	return file_list;
+}
+
+DynamicFileList *
+get_next_loaded_module(DynamicFileList *dfptr)
+{
+	return dfptr->next;
+}
+
+/*
+ * Return some details about the specified module.
+ *
+ * Note that module_name and module_version could be returned as NULL.
+ *
+ * We could dispense with this function by exposing struct DynamicFileList
+ * globally, but this way seems preferable.
+ */
+void
+get_loaded_module_details(DynamicFileList *dfptr,
+						  const char **library_path,
+						  const char **module_name,
+						  const char **module_version)
+{
+	*library_path = dfptr->filename;
+	*module_name = dfptr->magic->name;
+	*module_version = dfptr->magic->version;
+}
+
+
 /*
  * If name contains a slash, check if the file exists, if so return
  * the name.  Else (no slash) try to expand using search path (see
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 890822eaf79..6c049cb3834 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6749,6 +6749,13 @@
   proargnames => '{rm_id, rm_name, rm_builtin}',
   prosrc => 'pg_get_wal_resource_managers' },
 
+{ oid => '8303', descr => 'get info about loaded modules',
+  proname => 'pg_get_loaded_modules', prorows => '10', proretset => 't',
+  provolatile => 'v', proparallel => 'r', prorettype => 'record',
+  proargtypes => '', proallargtypes => '{text,text,text}',
+  proargmodes => '{o,o,o}', proargnames => '{module_name,version,file_name}',
+  prosrc => 'pg_get_loaded_modules' },
+
 { oid => '2621', descr => 'reload configuration files',
   proname => 'pg_reload_conf', provolatile => 'v', prorettype => 'bool',
   proargtypes => '', prosrc => 'pg_reload_conf' },
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 82ee38b31e5..1829ac01a8c 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -440,11 +440,14 @@ extern PGDLLEXPORT void _PG_init(void);
  * We require dynamically-loaded modules to include the macro call
  *		PG_MODULE_MAGIC;
  * so that we can check for obvious incompatibility, such as being compiled
- * for a different major PostgreSQL version.
+ * for a different major PostgreSQL version.  Alternatively, write
+ *		PG_MODULE_MAGIC_EXT(...);
+ * where the optional arguments can specify module name and version, and
+ * perhaps other values in future.  Note that in a multiple-source-file
+ * module, there should be exactly one such macro call.
  *
- * To compile with versions of PostgreSQL that do not support this,
- * you may put an #ifdef/#endif test around it.  Note that in a multiple-
- * source-file module, the macro call should only appear once.
+ * You may need an #ifdef test to verify that the version of PostgreSQL
+ * you are compiling against supports PG_MODULE_MAGIC_EXT().
  *
  * The specific items included in the magic block are intended to be ones that
  * are custom-configurable and especially likely to break dynamically loaded
@@ -459,22 +462,30 @@ extern PGDLLEXPORT void _PG_init(void);
  *-------------------------------------------------------------------------
  */
 
-/* Definition of the magic block structure */
+/* Definition of the values we check to verify ABI compatibility */
 typedef struct
 {
-	int			len;			/* sizeof(this struct) */
 	int			version;		/* PostgreSQL major version */
 	int			funcmaxargs;	/* FUNC_MAX_ARGS */
 	int			indexmaxkeys;	/* INDEX_MAX_KEYS */
 	int			namedatalen;	/* NAMEDATALEN */
 	int			float8byval;	/* FLOAT8PASSBYVAL */
 	char		abi_extra[32];	/* see pg_config_manual.h */
+} Pg_abi_values;
+
+/* Definition of the magic block structure */
+typedef struct
+{
+	int			len;			/* sizeof(this struct) */
+	Pg_abi_values abi_fields;	/* see above */
+	/* Remaining fields are zero unless filled via PG_MODULE_MAGIC_EXT */
+	const char *name;			/* optional module name */
+	const char *version;		/* optional module version */
 } Pg_magic_struct;
 
-/* The actual data block contents */
-#define PG_MODULE_MAGIC_DATA \
+/* Macro to fill the ABI fields */
+#define PG_MODULE_ABI_DATA \
 { \
-	sizeof(Pg_magic_struct), \
 	PG_VERSION_NUM / 100, \
 	FUNC_MAX_ARGS, \
 	INDEX_MAX_KEYS, \
@@ -483,7 +494,18 @@ typedef struct
 	FMGR_ABI_EXTRA, \
 }
 
-StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_magic_struct *) 0)->abi_extra),
+/*
+ * Macro to fill a magic block.  If any arguments are given, they should
+ * be field initializers.
+ */
+#define PG_MODULE_MAGIC_DATA(...) \
+{ \
+	.len = sizeof(Pg_magic_struct), \
+	.abi_fields = PG_MODULE_ABI_DATA, \
+	__VA_ARGS__ \
+}
+
+StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_abi_values *) 0)->abi_extra),
 				 "FMGR_ABI_EXTRA too long");
 
 /*
@@ -500,7 +522,26 @@ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
 const Pg_magic_struct * \
 PG_MAGIC_FUNCTION_NAME(void) \
 { \
-	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \
+	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(0); \
+	return &Pg_magic_data; \
+} \
+extern int no_such_variable
+
+/*
+ * Alternate declaration that allows specification of additional fields.
+ * The additional values should be written as field initializers, for example
+ *	PG_MODULE_MAGIC_EXT(
+ *		.name = "some string",
+ *		.version = "some string"
+ *	);
+ */
+#define PG_MODULE_MAGIC_EXT(...) \
+extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
+const Pg_magic_struct * \
+PG_MAGIC_FUNCTION_NAME(void) \
+{ \
+	static const Pg_magic_struct Pg_magic_data = \
+		PG_MODULE_MAGIC_DATA(__VA_ARGS__); \
 	return &Pg_magic_data; \
 } \
 extern int no_such_variable
@@ -738,6 +779,8 @@ extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid);
 /*
  * Routines in dfmgr.c
  */
+typedef struct DynamicFileList DynamicFileList; /* opaque outside dfmgr.h */
+
 extern PGDLLIMPORT char *Dynamic_library_path;
 
 extern char *substitute_path_macro(const char *str, const char *macro, const char *value);
@@ -747,6 +790,12 @@ extern void *load_external_function(const char *filename, const char *funcname,
 									bool signalNotFound, void **filehandle);
 extern void *lookup_external_function(void *filehandle, const char *funcname);
 extern void load_file(const char *filename, bool restricted);
+extern DynamicFileList *get_first_loaded_module(void);
+extern DynamicFileList *get_next_loaded_module(DynamicFileList *dfptr);
+extern void get_loaded_module_details(DynamicFileList *dfptr,
+									  const char **library_path,
+									  const char **module_name,
+									  const char **module_version);
 extern void **find_rendezvous_variable(const char *varName);
 extern Size EstimateLibraryStateSpace(void);
 extern void SerializeLibraryState(Size maxsize, char *start_address);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bfa276d2d35..854b16d0dcc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2228,6 +2228,7 @@ PgStat_WalCounters
 PgStat_WalStats
 PgXmlErrorContext
 PgXmlStrictness
+Pg_abi_values
 Pg_finfo_record
 Pg_magic_struct
 PipeProtoChunk
-- 
2.43.5

v7-0002-Use-PG_MODULE_MAGIC_EXT-in-our-installable-librar.patchtext/x-diff; charset=us-ascii; name*0=v7-0002-Use-PG_MODULE_MAGIC_EXT-in-our-installable-librar.p; name*1=atchDownload
From 178e7ca571a31e5a3a6e400a2278fcd422fab5f6 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 23 Mar 2025 14:59:52 -0400
Subject: [PATCH v7 2/2] Use PG_MODULE_MAGIC_EXT in our installable libraries.

It seems potentially useful to label our shared libraries with version
information, now that a facility exists for retrieving that.  This
patch labels them with the PG_VERSION string.  There was some
discussion about using semantic versioning conventions, but that
doesn't seem terribly helpful for modules with no SQL-level presence;
and for those that do have SQL objects, we typically expect them
to support multiple revisions of the SQL definitions, so it'd still
not be very helpful.

I did not label any of src/test/modules/.  It seems unnecessary since
we don't install those, and besides there ought to be someplace that
still provides test coverage for the original PG_MODULE_MAGIC macro.

Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/dd4d1b59-d0fe-49d5-b28f-1e463b68fa32@gmail.com
---
 contrib/amcheck/verify_nbtree.c                              | 5 ++++-
 contrib/auth_delay/auth_delay.c                              | 5 ++++-
 contrib/basebackup_to_shell/basebackup_to_shell.c            | 5 ++++-
 contrib/basic_archive/basic_archive.c                        | 5 ++++-
 contrib/bloom/blinsert.c                                     | 5 ++++-
 contrib/bool_plperl/bool_plperl.c                            | 5 ++++-
 contrib/btree_gin/btree_gin.c                                | 5 ++++-
 contrib/btree_gist/btree_gist.c                              | 5 ++++-
 contrib/citext/citext.c                                      | 5 ++++-
 contrib/cube/cube.c                                          | 5 ++++-
 contrib/dblink/dblink.c                                      | 5 ++++-
 contrib/dict_int/dict_int.c                                  | 5 ++++-
 contrib/dict_xsyn/dict_xsyn.c                                | 5 ++++-
 contrib/earthdistance/earthdistance.c                        | 5 ++++-
 contrib/file_fdw/file_fdw.c                                  | 5 ++++-
 contrib/fuzzystrmatch/fuzzystrmatch.c                        | 5 ++++-
 contrib/hstore/hstore_io.c                                   | 5 ++++-
 contrib/hstore_plperl/hstore_plperl.c                        | 5 ++++-
 contrib/hstore_plpython/hstore_plpython.c                    | 5 ++++-
 contrib/intarray/_int_op.c                                   | 5 ++++-
 contrib/isn/isn.c                                            | 5 ++++-
 contrib/jsonb_plperl/jsonb_plperl.c                          | 5 ++++-
 contrib/jsonb_plpython/jsonb_plpython.c                      | 5 ++++-
 contrib/lo/lo.c                                              | 5 ++++-
 contrib/ltree/ltree_op.c                                     | 5 ++++-
 contrib/ltree_plpython/ltree_plpython.c                      | 5 ++++-
 contrib/pageinspect/rawpage.c                                | 5 ++++-
 contrib/passwordcheck/passwordcheck.c                        | 5 ++++-
 contrib/pg_buffercache/pg_buffercache_pages.c                | 5 ++++-
 contrib/pg_freespacemap/pg_freespacemap.c                    | 5 ++++-
 contrib/pg_logicalinspect/pg_logicalinspect.c                | 5 ++++-
 contrib/pg_prewarm/pg_prewarm.c                              | 5 ++++-
 contrib/pg_stat_statements/pg_stat_statements.c              | 5 ++++-
 contrib/pg_surgery/heap_surgery.c                            | 5 ++++-
 contrib/pg_trgm/trgm_op.c                                    | 5 ++++-
 contrib/pg_visibility/pg_visibility.c                        | 5 ++++-
 contrib/pg_walinspect/pg_walinspect.c                        | 5 ++++-
 contrib/pgcrypto/pgcrypto.c                                  | 5 ++++-
 contrib/pgrowlocks/pgrowlocks.c                              | 5 ++++-
 contrib/pgstattuple/pgstattuple.c                            | 5 ++++-
 contrib/postgres_fdw/postgres_fdw.c                          | 5 ++++-
 contrib/seg/seg.c                                            | 5 ++++-
 contrib/sepgsql/hooks.c                                      | 5 ++++-
 contrib/spi/autoinc.c                                        | 5 ++++-
 contrib/spi/insert_username.c                                | 5 ++++-
 contrib/spi/moddatetime.c                                    | 5 ++++-
 contrib/spi/refint.c                                         | 5 ++++-
 contrib/sslinfo/sslinfo.c                                    | 5 ++++-
 contrib/tablefunc/tablefunc.c                                | 5 ++++-
 contrib/tcn/tcn.c                                            | 5 ++++-
 contrib/test_decoding/test_decoding.c                        | 5 ++++-
 contrib/tsm_system_rows/tsm_system_rows.c                    | 5 ++++-
 contrib/tsm_system_time/tsm_system_time.c                    | 5 ++++-
 contrib/unaccent/unaccent.c                                  | 5 ++++-
 contrib/uuid-ossp/uuid-ossp.c                                | 5 ++++-
 contrib/xml2/xpath.c                                         | 5 ++++-
 src/backend/jit/llvm/llvmjit.c                               | 5 ++++-
 src/backend/replication/libpqwalreceiver/libpqwalreceiver.c  | 5 ++++-
 src/backend/replication/pgoutput/pgoutput.c                  | 5 ++++-
 src/backend/snowball/dict_snowball.c                         | 5 ++++-
 .../mb/conversion_procs/cyrillic_and_mic/cyrillic_and_mic.c  | 5 ++++-
 .../mb/conversion_procs/euc2004_sjis2004/euc2004_sjis2004.c  | 5 ++++-
 .../mb/conversion_procs/euc_cn_and_mic/euc_cn_and_mic.c      | 5 ++++-
 .../mb/conversion_procs/euc_jp_and_sjis/euc_jp_and_sjis.c    | 5 ++++-
 .../mb/conversion_procs/euc_kr_and_mic/euc_kr_and_mic.c      | 5 ++++-
 .../mb/conversion_procs/euc_tw_and_big5/euc_tw_and_big5.c    | 5 ++++-
 .../conversion_procs/latin2_and_win1250/latin2_and_win1250.c | 5 ++++-
 .../utils/mb/conversion_procs/latin_and_mic/latin_and_mic.c  | 5 ++++-
 .../utils/mb/conversion_procs/utf8_and_big5/utf8_and_big5.c  | 5 ++++-
 .../conversion_procs/utf8_and_cyrillic/utf8_and_cyrillic.c   | 5 ++++-
 .../mb/conversion_procs/utf8_and_euc2004/utf8_and_euc2004.c  | 5 ++++-
 .../mb/conversion_procs/utf8_and_euc_cn/utf8_and_euc_cn.c    | 5 ++++-
 .../mb/conversion_procs/utf8_and_euc_jp/utf8_and_euc_jp.c    | 5 ++++-
 .../mb/conversion_procs/utf8_and_euc_kr/utf8_and_euc_kr.c    | 5 ++++-
 .../mb/conversion_procs/utf8_and_euc_tw/utf8_and_euc_tw.c    | 5 ++++-
 .../mb/conversion_procs/utf8_and_gb18030/utf8_and_gb18030.c  | 5 ++++-
 .../utils/mb/conversion_procs/utf8_and_gbk/utf8_and_gbk.c    | 5 ++++-
 .../mb/conversion_procs/utf8_and_iso8859/utf8_and_iso8859.c  | 5 ++++-
 .../conversion_procs/utf8_and_iso8859_1/utf8_and_iso8859_1.c | 5 ++++-
 .../mb/conversion_procs/utf8_and_johab/utf8_and_johab.c      | 5 ++++-
 .../utils/mb/conversion_procs/utf8_and_sjis/utf8_and_sjis.c  | 5 ++++-
 .../conversion_procs/utf8_and_sjis2004/utf8_and_sjis2004.c   | 5 ++++-
 .../utils/mb/conversion_procs/utf8_and_uhc/utf8_and_uhc.c    | 5 ++++-
 .../utils/mb/conversion_procs/utf8_and_win/utf8_and_win.c    | 5 ++++-
 src/pl/plperl/plperl.c                                       | 5 ++++-
 src/pl/plpgsql/src/pl_handler.c                              | 5 ++++-
 src/pl/plpython/plpy_main.c                                  | 5 ++++-
 src/pl/tcl/pltcl.c                                           | 5 ++++-
 src/test/regress/regress.c                                   | 5 ++++-
 89 files changed, 356 insertions(+), 89 deletions(-)

diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 825b677c47c..d56eb7637d3 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -42,7 +42,10 @@
 #include "utils/snapmgr.h"
 
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "amcheck",
+					.version = PG_VERSION
+);
 
 /*
  * A B-Tree cannot possibly have this many levels, since there must be one
diff --git a/contrib/auth_delay/auth_delay.c b/contrib/auth_delay/auth_delay.c
index f611da2158b..8681b54fc3a 100644
--- a/contrib/auth_delay/auth_delay.c
+++ b/contrib/auth_delay/auth_delay.c
@@ -16,7 +16,10 @@
 #include "libpq/auth.h"
 #include "utils/guc.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "auth_delay",
+					.version = PG_VERSION
+);
 
 /* GUC Variables */
 static int	auth_delay_milliseconds = 0;
diff --git a/contrib/basebackup_to_shell/basebackup_to_shell.c b/contrib/basebackup_to_shell/basebackup_to_shell.c
index d91366b06d2..8720f5a4372 100644
--- a/contrib/basebackup_to_shell/basebackup_to_shell.c
+++ b/contrib/basebackup_to_shell/basebackup_to_shell.c
@@ -18,7 +18,10 @@
 #include "utils/acl.h"
 #include "utils/guc.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "basebackup_to_shell",
+					.version = PG_VERSION
+);
 
 typedef struct bbsink_shell
 {
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index cb839582348..4a8b8c7ac29 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -37,7 +37,10 @@
 #include "storage/fd.h"
 #include "utils/guc.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "basic_archive",
+					.version = PG_VERSION
+);
 
 static char *archive_directory = NULL;
 
diff --git a/contrib/bloom/blinsert.c b/contrib/bloom/blinsert.c
index ee8ebaf3caf..7866438122f 100644
--- a/contrib/bloom/blinsert.c
+++ b/contrib/bloom/blinsert.c
@@ -22,7 +22,10 @@
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "bloom",
+					.version = PG_VERSION
+);
 
 /*
  * State of bloom index build.  We accumulate one page data here before
diff --git a/contrib/bool_plperl/bool_plperl.c b/contrib/bool_plperl/bool_plperl.c
index 0fa1eee8e57..7c611bd52a7 100644
--- a/contrib/bool_plperl/bool_plperl.c
+++ b/contrib/bool_plperl/bool_plperl.c
@@ -4,7 +4,10 @@
 #include "plperl.h"
 
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "bool_plperl",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(bool_to_plperl);
 
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index 533c55e9eaf..98663cb8611 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -14,7 +14,10 @@
 #include "utils/timestamp.h"
 #include "utils/uuid.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "btree_gin",
+					.version = PG_VERSION
+);
 
 typedef struct QueryInfo
 {
diff --git a/contrib/btree_gist/btree_gist.c b/contrib/btree_gist/btree_gist.c
index 7fcb0cd6d03..280ce808456 100644
--- a/contrib/btree_gist/btree_gist.c
+++ b/contrib/btree_gist/btree_gist.c
@@ -7,7 +7,10 @@
 #include "access/stratnum.h"
 #include "utils/builtins.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "btree_gist",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(gbt_decompress);
 PG_FUNCTION_INFO_V1(gbtreekey_in);
diff --git a/contrib/citext/citext.c b/contrib/citext/citext.c
index 3c461ff2ff2..a15ce5db829 100644
--- a/contrib/citext/citext.c
+++ b/contrib/citext/citext.c
@@ -10,7 +10,10 @@
 #include "utils/varlena.h"
 #include "varatt.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "citext",
+					.version = PG_VERSION
+);
 
 /*
  *		====================
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
index bf8fc489dca..8d3654ab7aa 100644
--- a/contrib/cube/cube.c
+++ b/contrib/cube/cube.c
@@ -17,7 +17,10 @@
 #include "utils/array.h"
 #include "utils/float.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "cube",
+					.version = PG_VERSION
+);
 
 /*
  * Taken from the intarray contrib header
diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 58c1a6221c8..3e4d7c952eb 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -63,7 +63,10 @@
 #include "utils/varlena.h"
 #include "utils/wait_event.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "dblink",
+					.version = PG_VERSION
+);
 
 typedef struct remoteConn
 {
diff --git a/contrib/dict_int/dict_int.c b/contrib/dict_int/dict_int.c
index 3cfe406f669..bdad52d2028 100644
--- a/contrib/dict_int/dict_int.c
+++ b/contrib/dict_int/dict_int.c
@@ -15,7 +15,10 @@
 #include "commands/defrem.h"
 #include "tsearch/ts_public.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "dict_int",
+					.version = PG_VERSION
+);
 
 typedef struct
 {
diff --git a/contrib/dict_xsyn/dict_xsyn.c b/contrib/dict_xsyn/dict_xsyn.c
index 756ba5998c5..1ec5285d6d1 100644
--- a/contrib/dict_xsyn/dict_xsyn.c
+++ b/contrib/dict_xsyn/dict_xsyn.c
@@ -20,7 +20,10 @@
 #include "tsearch/ts_public.h"
 #include "utils/formatting.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "dict_xsyn",
+					.version = PG_VERSION
+);
 
 typedef struct
 {
diff --git a/contrib/earthdistance/earthdistance.c b/contrib/earthdistance/earthdistance.c
index ded048c8ac5..f3011803d08 100644
--- a/contrib/earthdistance/earthdistance.c
+++ b/contrib/earthdistance/earthdistance.c
@@ -11,7 +11,10 @@
 #define M_PI 3.14159265358979323846
 #endif
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "earthdistance",
+					.version = PG_VERSION
+);
 
 /* Earth's radius is in statute miles. */
 static const double EARTH_RADIUS = 3958.747716;
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 56ececac70b..d94690e89dd 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -42,7 +42,10 @@
 #include "utils/sampling.h"
 #include "utils/varlena.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "file_fdw",
+					.version = PG_VERSION
+);
 
 /*
  * Describes the valid options for objects that use this wrapper.
diff --git a/contrib/fuzzystrmatch/fuzzystrmatch.c b/contrib/fuzzystrmatch/fuzzystrmatch.c
index 850d017ac65..e7cc314b763 100644
--- a/contrib/fuzzystrmatch/fuzzystrmatch.c
+++ b/contrib/fuzzystrmatch/fuzzystrmatch.c
@@ -44,7 +44,10 @@
 #include "utils/varlena.h"
 #include "varatt.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "fuzzystrmatch",
+					.version = PG_VERSION
+);
 
 /*
  * Soundex
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index 2125436e40c..4f867e4bd1f 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -21,7 +21,10 @@
 #include "utils/memutils.h"
 #include "utils/typcache.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "hstore",
+					.version = PG_VERSION
+);
 
 /* old names for C functions */
 HSTORE_POLLUTE(hstore_from_text, tconvert);
diff --git a/contrib/hstore_plperl/hstore_plperl.c b/contrib/hstore_plperl/hstore_plperl.c
index 4a1629cad51..31393b4fa50 100644
--- a/contrib/hstore_plperl/hstore_plperl.c
+++ b/contrib/hstore_plperl/hstore_plperl.c
@@ -4,7 +4,10 @@
 #include "hstore/hstore.h"
 #include "plperl.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "hstore_plperl",
+					.version = PG_VERSION
+);
 
 /* Linkage to functions in hstore module */
 typedef HStore *(*hstoreUpgrade_t) (Datum orig);
diff --git a/contrib/hstore_plpython/hstore_plpython.c b/contrib/hstore_plpython/hstore_plpython.c
index 310f63c30d4..8812fb3f3e4 100644
--- a/contrib/hstore_plpython/hstore_plpython.c
+++ b/contrib/hstore_plpython/hstore_plpython.c
@@ -5,7 +5,10 @@
 #include "plpy_typeio.h"
 #include "plpython.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "hstore_plpython",
+					.version = PG_VERSION
+);
 
 /* Linkage to functions in plpython module */
 typedef char *(*PLyObject_AsString_t) (PyObject *plrv);
diff --git a/contrib/intarray/_int_op.c b/contrib/intarray/_int_op.c
index 5b164f6788f..ba6d0a99995 100644
--- a/contrib/intarray/_int_op.c
+++ b/contrib/intarray/_int_op.c
@@ -5,7 +5,10 @@
 
 #include "_int.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "intarray",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(_int_different);
 PG_FUNCTION_INFO_V1(_int_same);
diff --git a/contrib/isn/isn.c b/contrib/isn/isn.c
index 5783c188737..038c8ed4db7 100644
--- a/contrib/isn/isn.c
+++ b/contrib/isn/isn.c
@@ -23,7 +23,10 @@
 #include "isn.h"
 #include "utils/guc.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "isn",
+					.version = PG_VERSION
+);
 
 #ifdef USE_ASSERT_CHECKING
 #define ISN_DEBUG 1
diff --git a/contrib/jsonb_plperl/jsonb_plperl.c b/contrib/jsonb_plperl/jsonb_plperl.c
index 2af1e0c02af..c02e2d41af1 100644
--- a/contrib/jsonb_plperl/jsonb_plperl.c
+++ b/contrib/jsonb_plperl/jsonb_plperl.c
@@ -7,7 +7,10 @@
 #include "utils/fmgrprotos.h"
 #include "utils/jsonb.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "jsonb_plperl",
+					.version = PG_VERSION
+);
 
 static SV  *Jsonb_to_SV(JsonbContainer *jsonb);
 static JsonbValue *SV_to_JsonbValue(SV *obj, JsonbParseState **ps, bool is_elem);
diff --git a/contrib/jsonb_plpython/jsonb_plpython.c b/contrib/jsonb_plpython/jsonb_plpython.c
index a625727c5e8..680445a006f 100644
--- a/contrib/jsonb_plpython/jsonb_plpython.c
+++ b/contrib/jsonb_plpython/jsonb_plpython.c
@@ -7,7 +7,10 @@
 #include "utils/jsonb.h"
 #include "utils/numeric.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "jsonb_plpython",
+					.version = PG_VERSION
+);
 
 /* for PLyObject_AsString in plpy_typeio.c */
 typedef char *(*PLyObject_AsString_t) (PyObject *plrv);
diff --git a/contrib/lo/lo.c b/contrib/lo/lo.c
index 62488e45f3a..f9348a16b66 100644
--- a/contrib/lo/lo.c
+++ b/contrib/lo/lo.c
@@ -12,7 +12,10 @@
 #include "utils/fmgrprotos.h"
 #include "utils/rel.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "lo",
+					.version = PG_VERSION
+);
 
 
 /*
diff --git a/contrib/ltree/ltree_op.c b/contrib/ltree/ltree_op.c
index 0e30dee4658..ce9f4caad4f 100644
--- a/contrib/ltree/ltree_op.c
+++ b/contrib/ltree/ltree_op.c
@@ -13,7 +13,10 @@
 #include "utils/selfuncs.h"
 #include "varatt.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "ltree",
+					.version = PG_VERSION
+);
 
 /* compare functions */
 PG_FUNCTION_INFO_V1(ltree_cmp);
diff --git a/contrib/ltree_plpython/ltree_plpython.c b/contrib/ltree_plpython/ltree_plpython.c
index ac159ea3141..ba5926b8be6 100644
--- a/contrib/ltree_plpython/ltree_plpython.c
+++ b/contrib/ltree_plpython/ltree_plpython.c
@@ -4,7 +4,10 @@
 #include "ltree/ltree.h"
 #include "plpython.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "ltree_plpython",
+					.version = PG_VERSION
+);
 
 /* Linkage to functions in plpython module */
 typedef PyObject *(*PLyUnicode_FromStringAndSize_t) (const char *s, Py_ssize_t size);
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index 617dff821a6..0d57123aa26 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -29,7 +29,10 @@
 #include "utils/rel.h"
 #include "utils/varlena.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pageinspect",
+					.version = PG_VERSION
+);
 
 static bytea *get_raw_page_internal(text *relname, ForkNumber forknum,
 									BlockNumber blkno);
diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index 3db42a5b99d..39ded17afa4 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -25,7 +25,10 @@
 #include "fmgr.h"
 #include "libpq/crypt.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "passwordcheck",
+					.version = PG_VERSION
+);
 
 /* Saved hook value */
 static check_password_hook_type prev_check_password_hook = NULL;
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 3ae0a018e10..62602af1775 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -20,7 +20,10 @@
 #define NUM_BUFFERCACHE_SUMMARY_ELEM 5
 #define NUM_BUFFERCACHE_USAGE_COUNTS_ELEM 4
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pg_buffercache",
+					.version = PG_VERSION
+);
 
 /*
  * Record structure holding the to be exposed cache data.
diff --git a/contrib/pg_freespacemap/pg_freespacemap.c b/contrib/pg_freespacemap/pg_freespacemap.c
index 565167aaef0..c0eac7a2016 100644
--- a/contrib/pg_freespacemap/pg_freespacemap.c
+++ b/contrib/pg_freespacemap/pg_freespacemap.c
@@ -12,7 +12,10 @@
 #include "fmgr.h"
 #include "storage/freespace.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pg_freespacemap",
+					.version = PG_VERSION
+);
 
 /*
  * Returns the amount of free space on a given page, according to the
diff --git a/contrib/pg_logicalinspect/pg_logicalinspect.c b/contrib/pg_logicalinspect/pg_logicalinspect.c
index 5a44718bea8..50e805d3195 100644
--- a/contrib/pg_logicalinspect/pg_logicalinspect.c
+++ b/contrib/pg_logicalinspect/pg_logicalinspect.c
@@ -18,7 +18,10 @@
 #include "utils/builtins.h"
 #include "utils/pg_lsn.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pg_logicalinspect",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(pg_get_logical_snapshot_meta);
 PG_FUNCTION_INFO_V1(pg_get_logical_snapshot_info);
diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c
index a2f0ac4af0c..f496ec9d85d 100644
--- a/contrib/pg_prewarm/pg_prewarm.c
+++ b/contrib/pg_prewarm/pg_prewarm.c
@@ -26,7 +26,10 @@
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pg_prewarm",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(pg_prewarm);
 
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 8ab9ad58e1c..9778407cba3 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -71,7 +71,10 @@
 #include "utils/memutils.h"
 #include "utils/timestamp.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pg_stat_statements",
+					.version = PG_VERSION
+);
 
 /* Location of permanent stats file (valid when database is shut down) */
 #define PGSS_DUMP_FILE	PGSTAT_STAT_PERMANENT_DIRECTORY "/pg_stat_statements.stat"
diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c
index 5b94b3d523e..3e86283beb7 100644
--- a/contrib/pg_surgery/heap_surgery.c
+++ b/contrib/pg_surgery/heap_surgery.c
@@ -23,7 +23,10 @@
 #include "utils/array.h"
 #include "utils/rel.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pg_surgery",
+					.version = PG_VERSION
+);
 
 /* Options to forcefully change the state of a heap tuple. */
 typedef enum HeapTupleForceOption
diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c
index 94b9015fd67..29b39ec8a4c 100644
--- a/contrib/pg_trgm/trgm_op.c
+++ b/contrib/pg_trgm/trgm_op.c
@@ -18,7 +18,10 @@
 #include "utils/memutils.h"
 #include "utils/pg_crc.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pg_trgm",
+					.version = PG_VERSION
+);
 
 /* GUC variables */
 double		similarity_threshold = 0.3f;
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 7f268a18a74..ca91819852c 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -25,7 +25,10 @@
 #include "storage/smgr.h"
 #include "utils/rel.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pg_visibility",
+					.version = PG_VERSION
+);
 
 typedef struct vbits
 {
diff --git a/contrib/pg_walinspect/pg_walinspect.c b/contrib/pg_walinspect/pg_walinspect.c
index 9e609415789..64745564cc2 100644
--- a/contrib/pg_walinspect/pg_walinspect.c
+++ b/contrib/pg_walinspect/pg_walinspect.c
@@ -29,7 +29,10 @@
  * give a thought about doing the same in pg_waldump tool as well.
  */
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pg_walinspect",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(pg_get_wal_block_info);
 PG_FUNCTION_INFO_V1(pg_get_wal_record_info);
diff --git a/contrib/pgcrypto/pgcrypto.c b/contrib/pgcrypto/pgcrypto.c
index b7e5383b9a6..9ecbbd2e2f8 100644
--- a/contrib/pgcrypto/pgcrypto.c
+++ b/contrib/pgcrypto/pgcrypto.c
@@ -41,7 +41,10 @@
 #include "utils/guc.h"
 #include "varatt.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pgcrypto",
+					.version = PG_VERSION
+);
 
 /* private stuff */
 
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 7e40ab21dda..b75d80fa7a9 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -42,7 +42,10 @@
 #include "utils/snapmgr.h"
 #include "utils/varlena.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pgrowlocks",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(pgrowlocks);
 
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 48cb8f59c4f..0d9c2b0b653 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -38,7 +38,10 @@
 #include "storage/lmgr.h"
 #include "utils/varlena.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pgstattuple",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(pgstattuple);
 PG_FUNCTION_INFO_V1(pgstattuple_v1_5);
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6beae0fa37f..6e2b983c3d0 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -49,7 +49,10 @@
 #include "utils/sampling.h"
 #include "utils/selfuncs.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "postgres_fdw",
+					.version = PG_VERSION
+);
 
 /* Default CPU cost to start up a foreign query. */
 #define DEFAULT_FDW_STARTUP_COST	100.0
diff --git a/contrib/seg/seg.c b/contrib/seg/seg.c
index fd4216edc5d..151cbb954b9 100644
--- a/contrib/seg/seg.c
+++ b/contrib/seg/seg.c
@@ -28,7 +28,10 @@
 #define GIST_QUERY_DEBUG
 */
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "seg",
+					.version = PG_VERSION
+);
 
 /*
  * Auxiliary data structure for picksplit method.
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 1b1dfe6792f..7aff15c6aec 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -25,7 +25,10 @@
 #include "utils/guc.h"
 #include "utils/queryenvironment.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "sepgsql",
+					.version = PG_VERSION
+);
 
 /*
  * Declarations
diff --git a/contrib/spi/autoinc.c b/contrib/spi/autoinc.c
index 8bf742230e0..b5609f20251 100644
--- a/contrib/spi/autoinc.c
+++ b/contrib/spi/autoinc.c
@@ -11,7 +11,10 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "autoinc",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(autoinc);
 
diff --git a/contrib/spi/insert_username.c b/contrib/spi/insert_username.c
index a2e1747ff74..e44241f9d6c 100644
--- a/contrib/spi/insert_username.c
+++ b/contrib/spi/insert_username.c
@@ -14,7 +14,10 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "insert_username",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(insert_username);
 
diff --git a/contrib/spi/moddatetime.c b/contrib/spi/moddatetime.c
index 5130804ce2a..5013eee433e 100644
--- a/contrib/spi/moddatetime.c
+++ b/contrib/spi/moddatetime.c
@@ -22,7 +22,10 @@ OH, me, I'm Terry Mackintosh <terry@terrym.com>
 #include "utils/fmgrprotos.h"
 #include "utils/rel.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "moddatetime",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(moddatetime);
 
diff --git a/contrib/spi/refint.c b/contrib/spi/refint.c
index e1aef7cd2a3..d954f5c838f 100644
--- a/contrib/spi/refint.c
+++ b/contrib/spi/refint.c
@@ -15,7 +15,10 @@
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "refint",
+					.version = PG_VERSION
+);
 
 typedef struct
 {
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b98741..d1e73942104 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -30,7 +30,10 @@
 #undef X509_NAME
 #endif
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "sslinfo",
+					.version = PG_VERSION
+);
 
 static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
 static Datum ASN1_STRING_to_text(ASN1_STRING *str);
diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c
index 4f2abed702c..74afdc0977f 100644
--- a/contrib/tablefunc/tablefunc.c
+++ b/contrib/tablefunc/tablefunc.c
@@ -44,7 +44,10 @@
 #include "miscadmin.h"
 #include "utils/builtins.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "tablefunc",
+					.version = PG_VERSION
+);
 
 static HTAB *load_categories_hash(char *cats_sql, MemoryContext per_query_ctx);
 static Tuplestorestate *get_crosstab_tuplestore(char *sql,
diff --git a/contrib/tcn/tcn.c b/contrib/tcn/tcn.c
index 10088802c63..3158dee0f26 100644
--- a/contrib/tcn/tcn.c
+++ b/contrib/tcn/tcn.c
@@ -23,7 +23,10 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "tcn",
+					.version = PG_VERSION
+);
 
 /*
  * Copy from s (for source) to r (for result), wrapping with q (quote)
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index 0113b196363..bb495563200 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -22,7 +22,10 @@
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "test_decoding",
+					.version = PG_VERSION
+);
 
 typedef struct
 {
diff --git a/contrib/tsm_system_rows/tsm_system_rows.c b/contrib/tsm_system_rows/tsm_system_rows.c
index 0c65763d37e..f401efa2131 100644
--- a/contrib/tsm_system_rows/tsm_system_rows.c
+++ b/contrib/tsm_system_rows/tsm_system_rows.c
@@ -34,7 +34,10 @@
 #include "optimizer/optimizer.h"
 #include "utils/sampling.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "tsm_system_rows",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(tsm_system_rows_handler);
 
diff --git a/contrib/tsm_system_time/tsm_system_time.c b/contrib/tsm_system_time/tsm_system_time.c
index f7bed98d1b7..c9c71d8c3af 100644
--- a/contrib/tsm_system_time/tsm_system_time.c
+++ b/contrib/tsm_system_time/tsm_system_time.c
@@ -33,7 +33,10 @@
 #include "utils/sampling.h"
 #include "utils/spccache.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "tsm_system_time",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(tsm_system_time_handler);
 
diff --git a/contrib/unaccent/unaccent.c b/contrib/unaccent/unaccent.c
index 352802ef8e8..336ba31047a 100644
--- a/contrib/unaccent/unaccent.c
+++ b/contrib/unaccent/unaccent.c
@@ -23,7 +23,10 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "unaccent",
+					.version = PG_VERSION
+);
 
 /*
  * An unaccent dictionary uses a trie to find a string to replace.  Each node
diff --git a/contrib/uuid-ossp/uuid-ossp.c b/contrib/uuid-ossp/uuid-ossp.c
index ca83f116a8a..58e312a0682 100644
--- a/contrib/uuid-ossp/uuid-ossp.c
+++ b/contrib/uuid-ossp/uuid-ossp.c
@@ -102,7 +102,10 @@ do { \
 
 #endif							/* !HAVE_UUID_OSSP */
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "uuid-ossp",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(uuid_nil);
 PG_FUNCTION_INFO_V1(uuid_ns_dns);
diff --git a/contrib/xml2/xpath.c b/contrib/xml2/xpath.c
index 19180b9a6c2..23d3f332dba 100644
--- a/contrib/xml2/xpath.c
+++ b/contrib/xml2/xpath.c
@@ -22,7 +22,10 @@
 #include <libxml/xmlerror.h>
 #include <libxml/parserInternals.h>
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "xml2",
+					.version = PG_VERSION
+);
 
 /* exported for use by xslt_proc.c */
 
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 614926720fb..46511624f01 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -138,7 +138,10 @@ ResourceOwnerForgetJIT(ResourceOwner owner, LLVMJitContext *handle)
 	ResourceOwnerForget(owner, PointerGetDatum(handle), &jit_resowner_desc);
 }
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "llvmjit",
+					.version = PG_VERSION
+);
 
 
 /*
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index c650935ef5d..ee3101c093e 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -36,7 +36,10 @@
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "libpqwalreceiver",
+					.version = PG_VERSION
+);
 
 struct WalReceiverConn
 {
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 8357bf8b4c0..38b7dce863f 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -36,7 +36,10 @@
 #include "utils/syscache.h"
 #include "utils/varlena.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pgoutput",
+					.version = PG_VERSION
+);
 
 static void pgoutput_startup(LogicalDecodingContext *ctx,
 							 OutputPluginOptions *opt, bool is_init);
diff --git a/src/backend/snowball/dict_snowball.c b/src/backend/snowball/dict_snowball.c
index 4c9cafbef35..e2b811a3806 100644
--- a/src/backend/snowball/dict_snowball.c
+++ b/src/backend/snowball/dict_snowball.c
@@ -77,7 +77,10 @@
 #include "snowball/libstemmer/stem_UTF_8_turkish.h"
 #include "snowball/libstemmer/stem_UTF_8_yiddish.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "dict_snowball",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(dsnowball_init);
 
diff --git a/src/backend/utils/mb/conversion_procs/cyrillic_and_mic/cyrillic_and_mic.c b/src/backend/utils/mb/conversion_procs/cyrillic_and_mic/cyrillic_and_mic.c
index 18c59491816..f00432a6981 100644
--- a/src/backend/utils/mb/conversion_procs/cyrillic_and_mic/cyrillic_and_mic.c
+++ b/src/backend/utils/mb/conversion_procs/cyrillic_and_mic/cyrillic_and_mic.c
@@ -15,7 +15,10 @@
 #include "fmgr.h"
 #include "mb/pg_wchar.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "cyrillic_and_mic",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(koi8r_to_mic);
 PG_FUNCTION_INFO_V1(mic_to_koi8r);
diff --git a/src/backend/utils/mb/conversion_procs/euc2004_sjis2004/euc2004_sjis2004.c b/src/backend/utils/mb/conversion_procs/euc2004_sjis2004/euc2004_sjis2004.c
index e09fb835205..14bd66e16f2 100644
--- a/src/backend/utils/mb/conversion_procs/euc2004_sjis2004/euc2004_sjis2004.c
+++ b/src/backend/utils/mb/conversion_procs/euc2004_sjis2004/euc2004_sjis2004.c
@@ -14,7 +14,10 @@
 #include "fmgr.h"
 #include "mb/pg_wchar.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "euc2004_sjis2004",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(euc_jis_2004_to_shift_jis_2004);
 PG_FUNCTION_INFO_V1(shift_jis_2004_to_euc_jis_2004);
diff --git a/src/backend/utils/mb/conversion_procs/euc_cn_and_mic/euc_cn_and_mic.c b/src/backend/utils/mb/conversion_procs/euc_cn_and_mic/euc_cn_and_mic.c
index 17528d80484..14e157e14f5 100644
--- a/src/backend/utils/mb/conversion_procs/euc_cn_and_mic/euc_cn_and_mic.c
+++ b/src/backend/utils/mb/conversion_procs/euc_cn_and_mic/euc_cn_and_mic.c
@@ -15,7 +15,10 @@
 #include "fmgr.h"
 #include "mb/pg_wchar.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "euc_cn_and_mic",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(euc_cn_to_mic);
 PG_FUNCTION_INFO_V1(mic_to_euc_cn);
diff --git a/src/backend/utils/mb/conversion_procs/euc_jp_and_sjis/euc_jp_and_sjis.c b/src/backend/utils/mb/conversion_procs/euc_jp_and_sjis/euc_jp_and_sjis.c
index f2f92a5c66e..d2744bd69b2 100644
--- a/src/backend/utils/mb/conversion_procs/euc_jp_and_sjis/euc_jp_and_sjis.c
+++ b/src/backend/utils/mb/conversion_procs/euc_jp_and_sjis/euc_jp_and_sjis.c
@@ -27,7 +27,10 @@
  */
 #include "sjis.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "euc_jp_and_sjis",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(euc_jp_to_sjis);
 PG_FUNCTION_INFO_V1(sjis_to_euc_jp);
diff --git a/src/backend/utils/mb/conversion_procs/euc_kr_and_mic/euc_kr_and_mic.c b/src/backend/utils/mb/conversion_procs/euc_kr_and_mic/euc_kr_and_mic.c
index 6f31f9a2c4c..0213768c452 100644
--- a/src/backend/utils/mb/conversion_procs/euc_kr_and_mic/euc_kr_and_mic.c
+++ b/src/backend/utils/mb/conversion_procs/euc_kr_and_mic/euc_kr_and_mic.c
@@ -15,7 +15,10 @@
 #include "fmgr.h"
 #include "mb/pg_wchar.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "euc_kr_and_mic",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(euc_kr_to_mic);
 PG_FUNCTION_INFO_V1(mic_to_euc_kr);
diff --git a/src/backend/utils/mb/conversion_procs/euc_tw_and_big5/euc_tw_and_big5.c b/src/backend/utils/mb/conversion_procs/euc_tw_and_big5/euc_tw_and_big5.c
index 8e38a787b67..c1834ca4181 100644
--- a/src/backend/utils/mb/conversion_procs/euc_tw_and_big5/euc_tw_and_big5.c
+++ b/src/backend/utils/mb/conversion_procs/euc_tw_and_big5/euc_tw_and_big5.c
@@ -15,7 +15,10 @@
 #include "fmgr.h"
 #include "mb/pg_wchar.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "euc_tw_and_big5",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(euc_tw_to_big5);
 PG_FUNCTION_INFO_V1(big5_to_euc_tw);
diff --git a/src/backend/utils/mb/conversion_procs/latin2_and_win1250/latin2_and_win1250.c b/src/backend/utils/mb/conversion_procs/latin2_and_win1250/latin2_and_win1250.c
index 2ca197e75d6..80370528264 100644
--- a/src/backend/utils/mb/conversion_procs/latin2_and_win1250/latin2_and_win1250.c
+++ b/src/backend/utils/mb/conversion_procs/latin2_and_win1250/latin2_and_win1250.c
@@ -15,7 +15,10 @@
 #include "fmgr.h"
 #include "mb/pg_wchar.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "latin2_and_win1250",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(latin2_to_mic);
 PG_FUNCTION_INFO_V1(mic_to_latin2);
diff --git a/src/backend/utils/mb/conversion_procs/latin_and_mic/latin_and_mic.c b/src/backend/utils/mb/conversion_procs/latin_and_mic/latin_and_mic.c
index aca747f725f..19757afa2d9 100644
--- a/src/backend/utils/mb/conversion_procs/latin_and_mic/latin_and_mic.c
+++ b/src/backend/utils/mb/conversion_procs/latin_and_mic/latin_and_mic.c
@@ -15,7 +15,10 @@
 #include "fmgr.h"
 #include "mb/pg_wchar.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "latin_and_mic",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(latin1_to_mic);
 PG_FUNCTION_INFO_V1(mic_to_latin1);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_big5/utf8_and_big5.c b/src/backend/utils/mb/conversion_procs/utf8_and_big5/utf8_and_big5.c
index f8aa01d840a..eae2d2d69f3 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_big5/utf8_and_big5.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_big5/utf8_and_big5.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/big5_to_utf8.map"
 #include "../../Unicode/utf8_to_big5.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_big5",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(big5_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_big5);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_cyrillic/utf8_and_cyrillic.c b/src/backend/utils/mb/conversion_procs/utf8_and_cyrillic/utf8_and_cyrillic.c
index fb9c9a586fa..5addade582f 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_cyrillic/utf8_and_cyrillic.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_cyrillic/utf8_and_cyrillic.c
@@ -19,7 +19,10 @@
 #include "../../Unicode/utf8_to_koi8u.map"
 #include "../../Unicode/koi8u_to_utf8.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_cyrillic",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(utf8_to_koi8r);
 PG_FUNCTION_INFO_V1(koi8r_to_utf8);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_euc2004/utf8_and_euc2004.c b/src/backend/utils/mb/conversion_procs/utf8_and_euc2004/utf8_and_euc2004.c
index 04882115e90..3e660da89b8 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_euc2004/utf8_and_euc2004.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_euc2004/utf8_and_euc2004.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/euc_jis_2004_to_utf8.map"
 #include "../../Unicode/utf8_to_euc_jis_2004.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_euc2004",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(euc_jis_2004_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_euc_jis_2004);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_euc_cn/utf8_and_euc_cn.c b/src/backend/utils/mb/conversion_procs/utf8_and_euc_cn/utf8_and_euc_cn.c
index d1be9fc1278..260b75c6bc5 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_euc_cn/utf8_and_euc_cn.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_euc_cn/utf8_and_euc_cn.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/euc_cn_to_utf8.map"
 #include "../../Unicode/utf8_to_euc_cn.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_euc_cn",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(euc_cn_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_euc_cn);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_euc_jp/utf8_and_euc_jp.c b/src/backend/utils/mb/conversion_procs/utf8_and_euc_jp/utf8_and_euc_jp.c
index a63efd7f33a..ad11594753d 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_euc_jp/utf8_and_euc_jp.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_euc_jp/utf8_and_euc_jp.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/euc_jp_to_utf8.map"
 #include "../../Unicode/utf8_to_euc_jp.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_euc_jp",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(euc_jp_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_euc_jp);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_euc_kr/utf8_and_euc_kr.c b/src/backend/utils/mb/conversion_procs/utf8_and_euc_kr/utf8_and_euc_kr.c
index cd37cc34209..e3f953263f3 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_euc_kr/utf8_and_euc_kr.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_euc_kr/utf8_and_euc_kr.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/euc_kr_to_utf8.map"
 #include "../../Unicode/utf8_to_euc_kr.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_euc_kr",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(euc_kr_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_euc_kr);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_euc_tw/utf8_and_euc_tw.c b/src/backend/utils/mb/conversion_procs/utf8_and_euc_tw/utf8_and_euc_tw.c
index be8907bc46c..25663bbda5d 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_euc_tw/utf8_and_euc_tw.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_euc_tw/utf8_and_euc_tw.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/euc_tw_to_utf8.map"
 #include "../../Unicode/utf8_to_euc_tw.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_euc_tw",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(euc_tw_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_euc_tw);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_gb18030/utf8_and_gb18030.c b/src/backend/utils/mb/conversion_procs/utf8_and_gb18030/utf8_and_gb18030.c
index 82e09b0c588..ffc9c58cd13 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_gb18030/utf8_and_gb18030.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_gb18030/utf8_and_gb18030.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/gb18030_to_utf8.map"
 #include "../../Unicode/utf8_to_gb18030.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_gb18030",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(gb18030_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_gb18030);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_gbk/utf8_and_gbk.c b/src/backend/utils/mb/conversion_procs/utf8_and_gbk/utf8_and_gbk.c
index 5609e9fdfd2..9adc0ce7d89 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_gbk/utf8_and_gbk.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_gbk/utf8_and_gbk.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/gbk_to_utf8.map"
 #include "../../Unicode/utf8_to_gbk.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_gbk",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(gbk_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_gbk);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_iso8859/utf8_and_iso8859.c b/src/backend/utils/mb/conversion_procs/utf8_and_iso8859/utf8_and_iso8859.c
index 53fabbc4e76..5a15981b2de 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_iso8859/utf8_and_iso8859.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_iso8859/utf8_and_iso8859.c
@@ -41,7 +41,10 @@
 #include "../../Unicode/utf8_to_iso8859_9.map"
 #include "../../Unicode/iso8859_16_to_utf8.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_iso8859",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(iso8859_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_iso8859);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_iso8859_1/utf8_and_iso8859_1.c b/src/backend/utils/mb/conversion_procs/utf8_and_iso8859_1/utf8_and_iso8859_1.c
index c922638e111..c077b986bcd 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_iso8859_1/utf8_and_iso8859_1.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_iso8859_1/utf8_and_iso8859_1.c
@@ -15,7 +15,10 @@
 #include "fmgr.h"
 #include "mb/pg_wchar.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_iso8859_1",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(iso8859_1_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_iso8859_1);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_johab/utf8_and_johab.c b/src/backend/utils/mb/conversion_procs/utf8_and_johab/utf8_and_johab.c
index 3513613d78b..08e38026a40 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_johab/utf8_and_johab.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_johab/utf8_and_johab.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/johab_to_utf8.map"
 #include "../../Unicode/utf8_to_johab.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_johab",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(johab_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_johab);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_sjis/utf8_and_sjis.c b/src/backend/utils/mb/conversion_procs/utf8_and_sjis/utf8_and_sjis.c
index b53179747e6..911a6342c60 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_sjis/utf8_and_sjis.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_sjis/utf8_and_sjis.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/sjis_to_utf8.map"
 #include "../../Unicode/utf8_to_sjis.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_sjis",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(sjis_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_sjis);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_sjis2004/utf8_and_sjis2004.c b/src/backend/utils/mb/conversion_procs/utf8_and_sjis2004/utf8_and_sjis2004.c
index 4f637932a38..d0361784a39 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_sjis2004/utf8_and_sjis2004.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_sjis2004/utf8_and_sjis2004.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/shift_jis_2004_to_utf8.map"
 #include "../../Unicode/utf8_to_shift_jis_2004.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_sjis2004",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(shift_jis_2004_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_shift_jis_2004);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_uhc/utf8_and_uhc.c b/src/backend/utils/mb/conversion_procs/utf8_and_uhc/utf8_and_uhc.c
index ed0aefeeae7..891a17014a1 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_uhc/utf8_and_uhc.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_uhc/utf8_and_uhc.c
@@ -17,7 +17,10 @@
 #include "../../Unicode/uhc_to_utf8.map"
 #include "../../Unicode/utf8_to_uhc.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_uhc",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(uhc_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_uhc);
diff --git a/src/backend/utils/mb/conversion_procs/utf8_and_win/utf8_and_win.c b/src/backend/utils/mb/conversion_procs/utf8_and_win/utf8_and_win.c
index caabcb33e4f..24c0dd9a552 100644
--- a/src/backend/utils/mb/conversion_procs/utf8_and_win/utf8_and_win.c
+++ b/src/backend/utils/mb/conversion_procs/utf8_and_win/utf8_and_win.c
@@ -37,7 +37,10 @@
 #include "../../Unicode/win874_to_utf8.map"
 #include "../../Unicode/win1258_to_utf8.map"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "utf8_and_win",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(win_to_utf8);
 PG_FUNCTION_INFO_V1(utf8_to_win);
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index ebf55fe663c..29cb4d7e47f 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -52,7 +52,10 @@ EXTERN_C void boot_DynaLoader(pTHX_ CV *cv);
 EXTERN_C void boot_PostgreSQL__InServer__Util(pTHX_ CV *cv);
 EXTERN_C void boot_PostgreSQL__InServer__SPI(pTHX_ CV *cv);
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "plperl",
+					.version = PG_VERSION
+);
 
 /**********************************************************************
  * Information associated with a Perl interpreter.  We have one interpreter
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 5af38d5773b..1bf12232862 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -31,7 +31,10 @@ static bool plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSo
 static void plpgsql_extra_warnings_assign_hook(const char *newvalue, void *extra);
 static void plpgsql_extra_errors_assign_hook(const char *newvalue, void *extra);
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "plpgsql",
+					.version = PG_VERSION
+);
 
 /* Custom GUC variable */
 static const struct config_enum_entry variable_conflict_options[] = {
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 8117e20efa2..8f56155f006 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -28,7 +28,10 @@
  * exported functions
  */
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "plpython",
+					.version = PG_VERSION
+);
 
 PG_FUNCTION_INFO_V1(plpython3_validator);
 PG_FUNCTION_INFO_V1(plpython3_call_handler);
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 08c8492050e..73d660e88a6 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -39,7 +39,10 @@
 #include "utils/typcache.h"
 
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "pltcl",
+					.version = PG_VERSION
+);
 
 #define HAVE_TCL_VERSION(maj,min) \
 	((TCL_MAJOR_VERSION > maj) || \
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index ed4a7937331..0bc0a9221de 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -79,7 +79,10 @@
 
 static void regress_lseg_construct(LSEG *lseg, Point *pt1, Point *pt2);
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT(
+					.name = "regress",
+					.version = PG_VERSION
+);
 
 
 /* return the point where two paths intersect, or NULL if no intersection. */
-- 
2.43.5

#40Andrei Lepikhov
lepihov@gmail.com
In reply to: Tom Lane (#39)
Re: Add Postgres module info

On 3/23/25 20:10, Tom Lane wrote:

Andrei Lepikhov <lepihov@gmail.com> writes:

On 3/22/25 23:49, Tom Lane wrote:

* It is not clear to me what permission restrictions we should put
on pg_get_loaded_modules, ...

I vote for the idea of stripping the full path to just a filename.

Works for me. v7 attached does it that way.

Thanks, you've done almost all the job.

* I'm not happy with putting pg_get_loaded_modules into dfmgr.c.

I just attempted to reduce number of exported objects here. If it is ok
to introduce an iterator, the pg_get_loaded_modules() may live in
extension.c

Yeah, I like that better than leaving it in dfmgr.c, so done that way.
The iterator functions also provide some cover for dealing with
on-the-fly changes of the file list, if we ever need that.

It also gives extension developers a tool to detect conflicting modules
any time we need it. More elegant than the SerializeLibraryState().

I converted pg_get_loaded_modules to run just once and deliver its
results in a tuplestore. That's partly because the adjacent SRFs
in extension.c work like that, but mostly because it removes the
hazard of the file list changing mid-run.

Ok.

Yes, additional burden to bump version string was a stopper for me to
propose such a brave idea.

After sleeping on it, I think we really ought to do that, so 0002
attached does so.

With the concept of the PG_VERSION string as a version, it looks more
meaningful than I've thought before.

Patch 0001 is ready to commit for me.
Patch 0002 I just checked on the errors in module names. That's more I
can do here? ;) Seems good, no errors found.

--
regards, Andrei Lepikhov

#41Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#39)
Re: Add Postgres module info

On Sun, Mar 23, 2025 at 3:10 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

I think this version is ready to commit, if there are not objections
to the decisions mentioned above.

It looks reasonable to me. I am a bit worried that using PG_VERSION as
the version string is going to feel like the wrong thing at some
stage, but I can't really say why, and I think it's better to do
something now and maybe have to revise it later than to do nothing now
and hope that we come up with a brilliant idea at some point in the
future.

--
Robert Haas
EDB: http://www.enterprisedb.com

#42Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#41)
Re: Add Postgres module info

Robert Haas <robertmhaas@gmail.com> writes:

It looks reasonable to me. I am a bit worried that using PG_VERSION as
the version string is going to feel like the wrong thing at some
stage, but I can't really say why, and I think it's better to do
something now and maybe have to revise it later than to do nothing now
and hope that we come up with a brilliant idea at some point in the
future.

Agreed. I think something is clearly better than nothing here, and
PG_VERSION has the huge advantage that we need no new mechanism to
maintain it. (A version identifier that isn't updated when it needs
to be is worse than no identifier, IMO.)

If somebody thinks of a better idea and is willing to do the legwork
to make it happen, we can surely change to something else later on.
Or invent another field with different semantics, or whatever.

regards, tom lane

#43Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#42)
Re: Add Postgres module info

On Mon, Mar 24, 2025 at 11:54 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

If somebody thinks of a better idea and is willing to do the legwork
to make it happen, we can surely change to something else later on.
Or invent another field with different semantics, or whatever.

Yeah, my thoughts exactly.

--
Robert Haas
EDB: http://www.enterprisedb.com

#44Euler Taveira
euler@eulerto.com
In reply to: Tom Lane (#42)
1 attachment(s)
Re: Add Postgres module info

On Mon, Mar 24, 2025, at 12:54 PM, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes:

It looks reasonable to me. I am a bit worried that using PG_VERSION as
the version string is going to feel like the wrong thing at some
stage, but I can't really say why, and I think it's better to do
something now and maybe have to revise it later than to do nothing now
and hope that we come up with a brilliant idea at some point in the
future.

Agreed. I think something is clearly better than nothing here, and
PG_VERSION has the huge advantage that we need no new mechanism to
maintain it. (A version identifier that isn't updated when it needs
to be is worse than no identifier, IMO.)

Agreed. My only concern is that people can confuse this version with the one
available in pg_extension or pg_available_extension* functions.

If somebody thinks of a better idea and is willing to do the legwork
to make it happen, we can surely change to something else later on.
Or invent another field with different semantics, or whatever.

I think those modules without control file, it is natural to use PG_VERSION.
However, I'm concerned that users can confuse the version if we provide
PG_VERSION as version and the extension catalog says something different.

postgres=# select * from pg_available_extensions where name = 'plperl';
name | default_version | installed_version | comment
--------+-----------------+-------------------+-----------------------------
plperl | 1.0 | | PL/Perl procedural language
(1 row)

postgres=# load 'plperl';
LOAD
postgres=# select * from pg_get_loaded_modules();
module_name | version | file_name
-------------+---------+-----------
plperl | 18devel | plperl.so
(1 row)

Maybe a note into default_version [1]https://www.postgresql.org/docs/current/extend-extensions.html is sufficient to clarify or a mechanism
to grab the information from control file and expose it as a macro. (I attached
an idea to accomplish this goal although it lacks meson support.) Thoughts?

I played with it a bit and it seems good to go.

postgres=# select version();
version
----------------------------------------------------------------------------------------------
PostgreSQL 18devel on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
(1 row)

postgres=# select * from pg_get_loaded_modules();
module_name | version | file_name
-------------+---------+-----------
(0 rows)

postgres=# load 'wal2json';
LOAD
postgres=# select * from pg_get_loaded_modules();
module_name | version | file_name
-------------+---------+-------------
wal2json | 2.6 | wal2json.so
(1 row)

Code:

diff --git a/wal2json.c b/wal2json.c
index 0c6295d..1f439be 100644
--- a/wal2json.c
+++ b/wal2json.c
@@ -40,7 +40,14 @@
#define    WAL2JSON_FORMAT_VERSION         2
#define    WAL2JSON_FORMAT_MIN_VERSION     1
+#if PG_VERSION_NUM >= 180000
+PG_MODULE_MAGIC_EXT(
+       .name = "wal2json",
+       .version = WAL2JSON_VERSION
+);
+#else
PG_MODULE_MAGIC;
+#endif

[1]: https://www.postgresql.org/docs/current/extend-extensions.html

--
Euler Taveira
EDB https://www.enterprisedb.com/

Attachments:

autoversion.patch.nocfbotapplication/octet-stream; name=autoversion.patch.nocfbotDownload
diff --git a/contrib/amcheck/Makefile b/contrib/amcheck/Makefile
index 5e9002d2501..a7a81126e4e 100644
--- a/contrib/amcheck/Makefile
+++ b/contrib/amcheck/Makefile
@@ -15,6 +15,8 @@ REGRESS = check check_btree check_heap
 EXTRA_INSTALL = contrib/pg_walinspect
 TAP_TESTS = 1
 
+PG_CPPFLAGS = -DPG_MODULE_VERSION=\"$(shell sed -e "s/default_version = '\(.*\)'/\1/;t;d" $(EXTENSION).control)\"
+
 ifdef USE_PGXS
 PG_CONFIG = pg_config
 PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index d56eb7637d3..4716a9db186 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -44,7 +44,7 @@
 
 PG_MODULE_MAGIC_EXT(
 					.name = "amcheck",
-					.version = PG_VERSION
+					.version = PG_MODULE_VERSION
 );
 
 /*
#45Tom Lane
tgl@sss.pgh.pa.us
In reply to: Euler Taveira (#44)
Re: Add Postgres module info

"Euler Taveira" <euler@eulerto.com> writes:

I think those modules without control file, it is natural to use PG_VERSION.
However, I'm concerned that users can confuse the version if we provide
PG_VERSION as version and the extension catalog says something different.

Maybe, but the values will be sufficiently different that I don't
think the confusion will last long. Anyway I don't want the version
in an extension's module to mean something totally different than
the version in a non-extension module. I could possibly get behind
setting version = PG_VERSION and having another field "ext_version"
or such that shows the expected current extension version if the
module belongs to an extension. I'm not really convinced it's worth
the trouble, though.

regards, tom lane

#46Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#45)
Re: Add Postgres module info

Hearing no further discussion, I've pushed this.

regards, tom lane

#47Yurii Rashkovskii
yrashk@omnigres.com
In reply to: Tom Lane (#46)
Re: Add Postgres module info

Hi Tom,

This recent patch is great but causes a small problem. It mixes designated
and non-designated initializers, specifically in `PG_MODULE_MAGIC_DATA(0)`.

While this is permissible in C, when imported in C++ code (in extern "C"),
it causes GCC to emit an error: `either all initializer clauses should be
designated or none of them should be`.
In Clang, this is a warning: `mixture of designated and non-designated
initializers in the same initializer list is a C99 extension`

I understand that this won't affect C extensions, it causes a need for an
unnecessary workaround for C++ extensions. C++ extensions are, of course,
not first-class-supported, but they are documented as essentially feasible
(and I am exercising this successfully)

Can we amend `PG_MODULE_MAGIC_DATA` to use designated initializers
exclusively? This way there will be no special-casing for C++, yet it will
provide relief for its users.

On Wed, Mar 26, 2025 at 8:15 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Show quoted text

Hearing no further discussion, I've pushed this.

regards, tom lane

#48Tom Lane
tgl@sss.pgh.pa.us
In reply to: Yurii Rashkovskii (#47)
Re: Add Postgres module info

Yurii Rashkovskii <yrashk@omnigres.com> writes:

This recent patch is great but causes a small problem. It mixes designated
and non-designated initializers, specifically in `PG_MODULE_MAGIC_DATA(0)`.

Ugh. I felt a bit itchy about that, but my compiler wasn't
complaining...

Can you propose a specific change to clean it up? I wanted to write
just "PG_MODULE_MAGIC_DATA()", but I'm not sure that's valid C either.

regards, tom lane

#49Yurii Rashkovskii
yrashk@omnigres.com
In reply to: Tom Lane (#48)
Re: Add Postgres module info

On Wed, Mar 26, 2025 at 7:45 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

This recent patch is great but causes a small problem. It mixes

designated

and non-designated initializers, specifically in

`PG_MODULE_MAGIC_DATA(0)`.

Ugh. I felt a bit itchy about that, but my compiler wasn't
complaining...

That's because this is valid in C99/C11; it's just not valid in C++. That
said, I think it's confusing and error-prone.

Can you propose a specific change to clean it up? I wanted to write
just "PG_MODULE_MAGIC_DATA()", but I'm not sure that's valid C either.

I was thinking about passing `.name = NULL, .version = NULL` instead of

`0`—do you have any reservations about this?

Yurii

#50Tom Lane
tgl@sss.pgh.pa.us
In reply to: Yurii Rashkovskii (#49)
Re: Add Postgres module info

Yurii Rashkovskii <yrashk@omnigres.com> writes:

Can you propose a specific change to clean it up? I wanted to write
just "PG_MODULE_MAGIC_DATA()", but I'm not sure that's valid C either.

I was thinking about passing `.name = NULL, .version = NULL` instead of
`0`—do you have any reservations about this?

If we're going that way, I'd minimize it to just ".name = NULL".

regards, tom lane

#51Yurii Rashkovskii
yrashk@omnigres.com
In reply to: Tom Lane (#50)
1 attachment(s)
Re: Add Postgres module info

On Wed, Mar 26, 2025 at 8:17 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Yurii Rashkovskii <yrashk@omnigres.com> writes:

Can you propose a specific change to clean it up? I wanted to write
just "PG_MODULE_MAGIC_DATA()", but I'm not sure that's valid C either.

I was thinking about passing `.name = NULL, .version = NULL` instead of
`0`—do you have any reservations about this?

If we're going that way, I'd minimize it to just ".name = NULL".

Would something like this work?

Attachments:

v1-0001-PG_MODULE_MAGIC_DATA-0-mixes-designated-and-non-desi.patchapplication/octet-stream; name=v1-0001-PG_MODULE_MAGIC_DATA-0-mixes-designated-and-non-desi.patchDownload
From 63e20d3270ac5cee57a4c0cfdc988b17401509a1 Mon Sep 17 00:00:00 2001
From: Yurii Rashkovskii <yrashk@gmail.com>
Date: Thu, 27 Mar 2025 06:13:21 -0700
Subject: [PATCH] PG_MODULE_MAGIC_DATA(0) mixes designated and non-designated
 initializers

This is allowed in C but not in C++ and C++ exensions include headers,
and this is causing compilers to error out (GCC) or issue a warning (Clang).

Use designated initializers exclusively to counteract this issue.
---
 src/include/fmgr.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 853870d3abf..0fe7b4ebc77 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -522,7 +522,7 @@ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
 const Pg_magic_struct * \
 PG_MAGIC_FUNCTION_NAME(void) \
 { \
-	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(0); \
+	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(.name = NULL); \
 	return &Pg_magic_data; \
 } \
 extern int no_such_variable
-- 
2.39.5 (Apple Git-154)

#52Tom Lane
tgl@sss.pgh.pa.us
In reply to: Yurii Rashkovskii (#51)
Re: Add Postgres module info

Yurii Rashkovskii <yrashk@omnigres.com> writes:

Would something like this work?

Works for me; pushed.

regards, tom lane