Ability to reference other extensions by schema in extension scripts

Started by Regina Obeabout 3 years ago38 messages
#1Regina Obe
lr@pcorp.us

I think I had brought this up a while ago, but I forget what the opinion was
on the matter.

PostGIS has a number of extensions that rely on it. For the extensions that
are packaged with PostGIS, we force them all into the same schema except for
the postgis_topology and postgis_tiger_geocoder extensions which are already
installed in dedicated schemas.

This makes it impossible for postgis_topology and postgis_tiger_geocoder to
schema qualify their use of postgis. Other extensions like pgRouting,
h3-pg, mobilitydb have similar issues.

My proposal is this. If you think it's a good enough idea I can work up a
patch for this.

Extensions currently are allowed to specify a requires in the control file.

I propose to use this information, to allow replacement of phrases

@extschema_nameofextension@ as a variable, where nameofextension has to be
one of the extensions listed in the requires.

The extension plumbing will then use this information to look up the schema
that the current required extensions are installed in, and replace the
variables with the schema of where the dependent extension is installed.

Does anyone see any issue with this idea.

Thanks,
Regina

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Regina Obe (#1)
Re: Ability to reference other extensions by schema in extension scripts

"Regina Obe" <lr@pcorp.us> writes:

My proposal is this. If you think it's a good enough idea I can work up a
patch for this.
Extensions currently are allowed to specify a requires in the control file.
I propose to use this information, to allow replacement of phrases
@extschema_nameofextension@ as a variable, where nameofextension has to be
one of the extensions listed in the requires.

I have a distinct sense of deja vu here. I think this idea, or something
isomorphic to it, was previously discussed with some other syntax details.
I'm too lazy to go searching the archives right now, but I suggest that
you try to find that discussion and see if the discussed syntax seems
better or worse than what you mention.

I think it might've been along the line of @extschema:nameofextension@,
which seems like it might be superior because colon isn't a valid
identifier character so there's less risk of ambiguity.

regards, tom lane

#3Regina Obe
lr@pcorp.us
In reply to: Tom Lane (#2)
RE: Ability to reference other extensions by schema in extension scripts

"Regina Obe" <lr@pcorp.us> writes:

My proposal is this. If you think it's a good enough idea I can work
up a patch for this.
Extensions currently are allowed to specify a requires in the control

file.

I propose to use this information, to allow replacement of phrases
@extschema_nameofextension@ as a variable, where nameofextension has
to be one of the extensions listed in the requires.

I have a distinct sense of deja vu here. I think this idea, or something
isomorphic to it, was previously discussed with some other syntax details.
I'm too lazy to go searching the archives right now, but I suggest that

you try to

find that discussion and see if the discussed syntax seems better or worse

than

what you mention.

I think it might've been along the line of @extschema:nameofextension@,
which seems like it might be superior because colon isn't a valid

identifier

character so there's less risk of ambiguity.

regards, tom lane

I found the old discussion I recalled having and Stephen had suggested using

@extschema{'postgis'}@

On this thread --
/messages/by-id/20160425232251.GR10850@tamriel.snowman
.net

Is that the one you remember?

Thanks,
Regina

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Regina Obe (#3)
Re: Ability to reference other extensions by schema in extension scripts

"Regina Obe" <lr@pcorp.us> writes:

I have a distinct sense of deja vu here. I think this idea, or something
isomorphic to it, was previously discussed with some other syntax details.

I found the old discussion I recalled having and Stephen had suggested using
@extschema{'postgis'}@
On this thread --
/messages/by-id/20160425232251.GR10850@tamriel.snowman.net
Is that the one you remember?

Hmmm ... no, ISTM it was considerably more recent than that.
[ ...digs... ] Here we go, it was in the discussion around
converting contrib SQL functions to new-style:

/messages/by-id/3395418.1618352794@sss.pgh.pa.us

There are a few different ideas bandied around in there.
Personally I still like the @extschema:extensionname@
option the best, though.

regards, tom lane

#5Regina Obe
lr@pcorp.us
In reply to: Tom Lane (#4)
RE: Ability to reference other extensions by schema in extension scripts

"Regina Obe" <lr@pcorp.us> writes:

I have a distinct sense of deja vu here. I think this idea, or
something isomorphic to it, was previously discussed with some other

syntax details.

I found the old discussion I recalled having and Stephen had suggested
using @extschema{'postgis'}@ On this thread --
https://www.postgresql.org/message-

id/20160425232251.GR10850@tamriel.s

nowman.net
Is that the one you remember?

Hmmm ... no, ISTM it was considerably more recent than that.
[ ...digs... ] Here we go, it was in the discussion around converting

contrib SQL

functions to new-style:

https://www.postgresql.org/message-
id/flat/3395418.1618352794%40sss.pgh.pa.us

There are a few different ideas bandied around in there.
Personally I still like the @extschema:extensionname@ option the best,
though.

regards, tom lane

I had initially thought of a syntax that could always be used even outside
of extension install as some mentioned. Like the PG_EXTENSION_SCHEMA(cube)
example. Main benefit I see with that is that even if an extension is moved,
all the dependent extensions that reference it would still work fine.

I had dismissed that because it seemed too invasive. Seems like it would
require changes to the parser and possibly add query performance overhead to
resolve the schema. Not to mention the added testing required to do no harm.

The other reason I dismissed it is because at least for PostGIS it would be
harder to conditionally replace. The issue with
PG_EXTENSION_SCHEMA(cube) is we can't support that in lower PG versions so
we'd need to strip for lower versions, and that would introduce the
possibility of missing
PG_EXTENSION_SCHEMA(cube) vs. PG_EXTENSION_SCHEMA( cube ), not a huge deal
though, but not quite as easy and precise as just stripping
@extschema:extensionname@. References.

With the @extschema:extensionname@, it doesn't solve all problems, but the
key ones we care about like breakage of functions used in indexes,
materialized views, and added security and is a little easier to strip out.

I'll work on producing a patch.

Thanks,
Regina

#6Regina Obe
lr@pcorp.us
In reply to: Tom Lane (#4)
1 attachment(s)
RE: Ability to reference other extensions by schema in extension scripts

"Regina Obe" <lr@pcorp.us> writes:

I have a distinct sense of deja vu here. I think this idea, or
something isomorphic to it, was previously discussed with some other

syntax details.

I found the old discussion I recalled having and Stephen had suggested
using @extschema{'postgis'}@ On this thread --
https://www.postgresql.org/message-

id/20160425232251.GR10850@tamriel.s

nowman.net
Is that the one you remember?

Hmmm ... no, ISTM it was considerably more recent than that.
[ ...digs... ] Here we go, it was in the discussion around converting

contrib SQL

functions to new-style:

https://www.postgresql.org/message-
id/flat/3395418.1618352794%40sss.pgh.pa.us

There are a few different ideas bandied around in there.
Personally I still like the @extschema:extensionname@ option the best,
though.

regards, tom lane

Here is first version of my patch using the @extschema:extensionname@ syntax
you proposed.

This patch includes:
1) Changes to replace references of @extschema:extensionname@ with the
schema of the required extension
2) Documentation for the feature
3) Tests for the feature.

There is one issue I thought about that is not addressed by this.

If an extension is required by another extension and that required extension
schema is referenced in the extension scripts using the
@extschema:extensionname@ syntax, then ideally we should prevent the
required extension from being relocatable. This would prevent a user from
accidentally moving the required extension, thus breaking the dependent
extensions.

I didn't add that feature cause I wasn't sure if it was overstepping the
bounds of what should be done, or if we leave it up to the user to just know
better.

Thanks,
Regina

Attachments:

0001-Allow-use-of-extschema-reqextname-to-reference.patchapplication/octet-stream; name=0001-Allow-use-of-extschema-reqextname-to-reference.patchDownload
From 52d7f06a6663d2f6c8577bd5b60ba937b0565ac0 Mon Sep 17 00:00:00 2001
From: Regina Obe <lr@pcorp.us>
Date: Fri, 11 Nov 2022 00:58:46 -0500
Subject: [PATCH] Allow use of @extschema:reqextname@ to reference the schema
 of a required extension called reqextname.

This patch includes
1. Changes to extension execute logic
2. Extension tests both Makefile and meson.build
3. Documentation of the new feature
---
 doc/src/sgml/extend.sgml                      | 17 ++++++++++
 src/backend/commands/extension.c              | 33 +++++++++++++++++++
 src/test/modules/test_extensions/Makefile     |  9 +++--
 .../expected/test_extensions.out              | 29 ++++++++++++++++
 src/test/modules/test_extensions/meson.build  |  7 ++++
 .../test_extensions/sql/test_extensions.sql   |  9 +++++
 .../test_ext_req_schema1--1.0.sql             |  6 ++++
 .../test_ext_req_schema1.control              |  3 ++
 .../test_ext_req_schema2--1.0--2.0.sql        |  7 ++++
 .../test_ext_req_schema2--1.0.sql             | 10 ++++++
 .../test_ext_req_schema2.control              |  4 +++
 .../test_ext_req_schema3--1.0.sql             | 14 ++++++++
 .../test_ext_req_schema3.control              |  4 +++
 13 files changed, 150 insertions(+), 2 deletions(-)
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3.control

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 46e873a166..62fdd9b919 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -908,6 +908,23 @@ RETURNS anycompatible AS ...
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+       An extension might depend on other extensions.
+       It is useful to schema qualify calls to dependent extension to minimize reliance on search_path.
+       This is critical for cases such as functions used in indexes, materialized views, or check constraints.
+       To reference a required extension's schema, you must first have <literal>requires</literal>
+       variable specifying the list of extensions your extension requires.
+       In your extension sql scripts,
+       you can reference a required extension's schema with syntax
+       <literal>@extschema:reqextname@</literal> where <literal>reqextname</literal> 
+       is the name of an extension in your <literal>requires</literal> list.
+       All occurrences of this string will be
+       replaced by the schema the required extension is installed in before the script is
+       executed.
+      </para>
+     </listitem>
+
      <listitem>
       <para>
        If the extension does not support relocation at all, set
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 806d6056ab..15945fd432 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1028,6 +1028,39 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 											CStringGetTextDatum(qSchemaName));
 		}
 
+		/*
+		 * If an extension requires other extensions
+		 * Replace each occurence of @extschema:extname@ 
+		 * where extname is the name of the required extension
+		 * with the schema name that extname is located in.
+		 */
+		if (control->requires)
+		{
+			foreach(lc, control->requires)
+			{
+				char	   *curreq = (char *) lfirst(lc);
+				Oid			reqext;
+				Oid			reqschema;
+				reqext = get_required_extension(curreq,
+												control->name,
+												schemaName,
+												false,
+												NIL,
+												false);
+				reqschema = get_extension_schema(reqext);
+				char *reqname;
+				reqname = get_namespace_name(reqschema);
+				StringInfoData rToken;
+				initStringInfo(&rToken);
+				appendStringInfo(&rToken, "%s%s%s", "@extschema:", curreq, "@");
+
+				t_sql = DirectFunctionCall3Coll(replace_text,
+										C_COLLATION_OID,
+										t_sql,
+										CStringGetTextDatum(rToken.data),
+										CStringGetTextDatum(quote_identifier(reqname)));
+			}
+		}
 		/*
 		 * If module_pathname was set in the control file, substitute its
 		 * value for occurrences of MODULE_PATHNAME.
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
index c3139ab0fc..c073df963c 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -6,14 +6,19 @@ PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
 EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
             test_ext7 test_ext8 test_ext_cine test_ext_cor \
             test_ext_cyclic1 test_ext_cyclic2 \
-            test_ext_evttrig
+            test_ext_evttrig \
+            test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3
+
 DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
        test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \
        test_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \
        test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \
        test_ext_cor--1.0.sql \
        test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
-       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql
+       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql \
+       test_ext_req_schema1--1.0.sql \
+       test_ext_req_schema2--1.0.sql test_ext_req_schema2--1.0--2.0.sql \
+       test_ext_req_schema3--1.0.sql
 
 REGRESS = test_extensions test_extdepend
 
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index 821fed38d1..022504f1ec 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -312,3 +312,32 @@ Objects in extension "test_ext_cine"
  table ext_cine_tab3
 (9 rows)
 
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+NOTICE:  installing required extension "test_ext_req_schema2"
+SELECT dep_req();
+ dep_req 
+---------
+ 1032w
+(1 row)
+
+SELECT dep_req2();
+ dep_req2 
+----------
+ 1032w
+(1 row)
+
+SELECT dep_req3();
+ dep_req3 
+----------
+ 2032w
+(1 row)
+
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+SELECT dep_req();
+ dep_req 
+---------
+ 1update
+(1 row)
+
diff --git a/src/test/modules/test_extensions/meson.build b/src/test/modules/test_extensions/meson.build
index e95a9f2e7e..20bee210b8 100644
--- a/src/test/modules/test_extensions/meson.build
+++ b/src/test/modules/test_extensions/meson.build
@@ -29,6 +29,13 @@ install_data(
   'test_ext_evttrig--1.0--2.0.sql',
   'test_ext_evttrig--1.0.sql',
   'test_ext_evttrig.control',
+  'test_ext_req_schema1--1.0.sql',
+  'test_ext_req_schema1.control',
+  'test_ext_req_schema2--1.0.sql',
+  'test_ext_req_schema2.control',
+  'test_ext_req_schema2--1.0--2.0.sql',
+  'test_ext_req_schema3.control',
+  'test_ext_req_schema3--1.0.sql',
   kwargs: contrib_data_args,
 )
 
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
index 41b6cddf0b..ba087c26a1 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -209,3 +209,12 @@ CREATE EXTENSION test_ext_cine;
 ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
 
 \dx+ test_ext_cine
+
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+SELECT dep_req();
+SELECT dep_req2();
+SELECT dep_req3();
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+SELECT dep_req();
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
new file mode 100644
index 0000000000..462fb52145
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
@@ -0,0 +1,6 @@
+/* src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema1" to load this file. \quit
+
+CREATE DOMAIN req AS text
+  CONSTRAINT starts_with_1 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '1');
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1.control b/src/test/modules/test_extensions/test_ext_req_schema1.control
new file mode 100644
index 0000000000..9ea4558a90
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1.control
@@ -0,0 +1,3 @@
+comment = 'Create required extension to be referenced'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
new file mode 100644
index 0000000000..73a44a25e5
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
@@ -0,0 +1,7 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+
+CREATE OR REPLACE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1update''::@extschema:test_ext_req_schema1@.req';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
new file mode 100644
index 0000000000..05b0eb24e1
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
@@ -0,0 +1,10 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN dreq AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
+
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2.control b/src/test/modules/test_extensions/test_ext_req_schema2.control
new file mode 100644
index 0000000000..d2ba5add97
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1'
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
new file mode 100644
index 0000000000..f28be6c82d
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
@@ -0,0 +1,14 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN req2 AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req2() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
+
+CREATE FUNCTION dep_req3() RETURNS @extschema:test_ext_req_schema2@.dreq
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''2032w''::@extschema:test_ext_req_schema2@.dreq';
+
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3.control b/src/test/modules/test_extensions/test_ext_req_schema3.control
new file mode 100644
index 0000000000..b052fad785
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of 2 required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1,test_ext_req_schema2'
-- 
2.21.0.windows.1

In reply to: Regina Obe (#6)
Re: Ability to reference other extensions by schema in extension scripts

On Tue, Nov 22, 2022 at 11:24:19PM -0500, Regina Obe wrote:

Here is first version of my patch using the @extschema:extensionname@ syntax
you proposed.

This patch includes:
1) Changes to replace references of @extschema:extensionname@ with the
schema of the required extension
2) Documentation for the feature
3) Tests for the feature.

There is one issue I thought about that is not addressed by this.

If an extension is required by another extension and that required extension
schema is referenced in the extension scripts using the
@extschema:extensionname@ syntax, then ideally we should prevent the
required extension from being relocatable. This would prevent a user from
accidentally moving the required extension, thus breaking the dependent
extensions.

I didn't add that feature cause I wasn't sure if it was overstepping the
bounds of what should be done, or if we leave it up to the user to just know
better.

An alternative would be to forbid using @extschema:extensionname@ to
reference relocatable extensions. DBA can toggle relocatability of an
extension to allow it to be referenced.

--strk;

#8Regina Obe
lr@pcorp.us
In reply to: Sandro Santilli (#7)
RE: Ability to reference other extensions by schema in extension scripts

On Tue, Nov 22, 2022 at 11:24:19PM -0500, Regina Obe wrote:

Here is first version of my patch using the @extschema:extensionname@
syntax you proposed.

This patch includes:
1) Changes to replace references of @extschema:extensionname@ with the
schema of the required extension
2) Documentation for the feature
3) Tests for the feature.

There is one issue I thought about that is not addressed by this.

If an extension is required by another extension and that required
extension schema is referenced in the extension scripts using the
@extschema:extensionname@ syntax, then ideally we should prevent the
required extension from being relocatable. This would prevent a user
from accidentally moving the required extension, thus breaking the
dependent extensions.

I didn't add that feature cause I wasn't sure if it was overstepping
the bounds of what should be done, or if we leave it up to the user to
just know better.

An alternative would be to forbid using @extschema:extensionname@ to
reference relocatable extensions. DBA can toggle relocatability of an

extension

to allow it to be referenced.

--strk;

That would be hard to do in a DbaaS setup and not many users know they can
fiddle with extension control files.
Plus those would get overwritten with upgrades.

In my case for example I have postgis_tiger_geocoder that relies on both
postgis and fuzzystrmatch.
I'd rather not have to explain to users how to fiddle with the
fuzzystrmatch.control file to make it not relocatable.

But I don't think anyone would mind if it's forced after install because
it's a rare thing for people to be moving extensions to different schemas
after install.

Thanks,
Regina

In reply to: Regina Obe (#8)
Re: Ability to reference other extensions by schema in extension scripts

On Thu, Dec 15, 2022 at 08:04:22AM -0500, Regina Obe wrote:

On Tue, Nov 22, 2022 at 11:24:19PM -0500, Regina Obe wrote:

If an extension is required by another extension and that required
extension schema is referenced in the extension scripts using the
@extschema:extensionname@ syntax, then ideally we should prevent the
required extension from being relocatable. This would prevent a user
from accidentally moving the required extension, thus breaking the
dependent extensions.

I didn't add that feature cause I wasn't sure if it was overstepping
the bounds of what should be done, or if we leave it up to the user to
just know better.

An alternative would be to forbid using @extschema:extensionname@ to
reference relocatable extensions. DBA can toggle relocatability of an
extension to allow it to be referenced.

That would be hard to do in a DbaaS setup and not many users know they can
fiddle with extension control files.
Plus those would get overwritten with upgrades.

Wouldn't this also be the case if you override relocatability ?
Case:

- Install fuzzystrmatch, marked as relocatable
- Install ext2 depending on the former, which is them marked
non-relocatable
- Upgrade database -> fuzzystrmatch becomes relocatable again
- Change fuzzystrmatch schema BREAKING ext2

Allowing to relocate a dependency of other extensions using the
@extschema@ syntax is very dangerous.

I've seen that PostgreSQL itself doesn't even bother to replace
@extschema@ IF the extension using it doesn't mark itself as
non-relocatable. For consistency this patch should basically refuse
to expand @extschema:fuzzystrmatch@ if "fuzzystrmatch" extension
is relocatable.

Changing the current behaviour of PostgreSQL could be proposed but
I don't think it's to be done in this thread ?

So my suggestion is to start consistent (do not expand if referenced
extension is relocatable).

--strk;

#10Regina Obe
lr@pcorp.us
In reply to: Sandro Santilli (#9)
RE: Ability to reference other extensions by schema in extension scripts

On Thu, Dec 15, 2022 at 08:04:22AM -0500, Regina Obe wrote:

On Tue, Nov 22, 2022 at 11:24:19PM -0500, Regina Obe wrote:

If an extension is required by another extension and that required
extension schema is referenced in the extension scripts using the
@extschema:extensionname@ syntax, then ideally we should prevent
the required extension from being relocatable. This would prevent
a user from accidentally moving the required extension, thus
breaking the dependent extensions.

I didn't add that feature cause I wasn't sure if it was
overstepping the bounds of what should be done, or if we leave it
up to the user to just know better.

An alternative would be to forbid using @extschema:extensionname@ to
reference relocatable extensions. DBA can toggle relocatability of
an extension to allow it to be referenced.

That would be hard to do in a DbaaS setup and not many users know they
can fiddle with extension control files.
Plus those would get overwritten with upgrades.

Wouldn't this also be the case if you override relocatability ?
Case:

- Install fuzzystrmatch, marked as relocatable
- Install ext2 depending on the former, which is them marked
non-relocatable
- Upgrade database -> fuzzystrmatch becomes relocatable again
- Change fuzzystrmatch schema BREAKING ext2

Somewhat. It would be an issue if someone does

ALTER EXTENSION fuzzystrmatch UPDATE;

And

ALTER EXTENSION fuzzystrmatch SET SCHEMA a_different_schema;

Otherwise the relocatability of an already installed extension wouldn't
change even during upgrade. I haven't checked pg_upgrade, but I suspect it
wouldn't change there either.

It's my understanding that once an extension is installed, it's relocatable
status is recorded in the pg_extension table. So it doesn't matter at that
point what the control file says. However if someone does update the
extension, then yes it would look at the control file and make it updatable
again.

I just tested this fiddling with postgis extension and moving it and then
upgrading.

UPDATE pg_extension SET extrelocatable = true where extname = 'postgis';
ALTER EXTENSION postgis SET schema postgis;

ALTER EXTENSION postgis UPDATE;
e.g. if the above is already at latest version, get notice
NOTICE: version "3.3.2" of extension "postgis" is already installed
(and the extension is still relocatable)

-- if the extension can be upgraded
ALTER EXTENSION postgis UPDATE;

-- no longer relocatable (because postgis control file has relocatable =
false)

But honestly I don't expect this to be a huge issue, more of just an extra
safety block.
Not a bullet-proof safety block though.

Allowing to relocate a dependency of other extensions using the
@extschema@ syntax is very dangerous.

I've seen that PostgreSQL itself doesn't even bother to replace

@extschema@

IF the extension using it doesn't mark itself as non-relocatable. For

consistency

this patch should basically refuse to expand @extschema:fuzzystrmatch@ if
"fuzzystrmatch" extension is relocatable.

Changing the current behaviour of PostgreSQL could be proposed but I don't
think it's to be done in this thread ?

So my suggestion is to start consistent (do not expand if referenced

extension

is relocatable).

--strk;

I don't agree. That would make this patch of not much use.
For example lets revisit my postgis_tiger_geocoder which is a good bulk of
the reason why I want this.

I use indexes that use postgis_tiger_geocoder functions that call
fuzzystrmatch which causes pg_restore to break on reload and other issues,
because I'm not explicitly referencing the function schema. With your
proposal now I got to demand the postgresql project to make fuzzystrmatch
not relocatable so I can use this feature. It is so rare for people to go
around moving the locations of their extensions once set, that I honestly
don't think
the ALTER EXTENSION .. UPDATE hole is a huge deal.

I'd be more annoyed having to beg an extension provider to mark their
extension as not relocatable so that I could explicitly reference the
location of their extensions.

And even then - think about it. I ask extension provider to make their
extension schema relocatable. They do, but some people are using a version
before they marked it as schema relocatable. So now if I change my code,
users can't install my extension, cause they are using a version before it
was schema relocatable and I'm using the new syntax.

What would be more bullet-proof is having an extra column in pg_extension or
adding an extra array element to pg_extension.extcondition[] that allows you
to say "Hey, don't allow this to be relocatable cause other extensions
depend on it that have explicitly referenced the schema."

Thanks,
Regina

In reply to: Regina Obe (#10)
Re: Ability to reference other extensions by schema in extension scripts

On Mon, Jan 16, 2023 at 11:57:30PM -0500, Regina Obe wrote:

What would be more bullet-proof is having an extra column in pg_extension or
adding an extra array element to pg_extension.extcondition[] that allows you
to say "Hey, don't allow this to be relocatable cause other extensions
depend on it that have explicitly referenced the schema."

I've given this some more thoughts and I think a good
compromise could be to add the safety net in ALTER EXTESION SET SCHEMA
so that it does not only check "extrelocatable" but also the presence
of any extension effectively depending on it, in which case the
operation could be prevented with a more useful message than
"extension does not support SET SCHEMA" (what is currently output).

Example query to determine those cases:

SELECT e.extname, array_agg(v.name)
FROM pg_extension e, pg_available_extension_versions v
WHERE e.extname = ANY( v.requires )
AND e.extrelocatable
AND v.installed group by e.extname;

extname | array_agg
---------------+--------------------------
fuzzystrmatch | {postgis_tiger_geocoder}

--strk;

#12Regina Obe
lr@pcorp.us
In reply to: Sandro Santilli (#11)
RE: Ability to reference other extensions by schema in extension scripts

On Mon, Jan 16, 2023 at 11:57:30PM -0500, Regina Obe wrote:

What would be more bullet-proof is having an extra column in
pg_extension or adding an extra array element to
pg_extension.extcondition[] that allows you to say "Hey, don't allow
this to be relocatable cause other extensions depend on it that have

explicitly

referenced the schema."

I've given this some more thoughts and I think a good compromise could be

to

add the safety net in ALTER EXTESION SET SCHEMA so that it does not only
check "extrelocatable" but also the presence of any extension effectively
depending on it, in which case the operation could be prevented with a

more

useful message than "extension does not support SET SCHEMA" (what is
currently output).

Example query to determine those cases:

SELECT e.extname, array_agg(v.name)
FROM pg_extension e, pg_available_extension_versions v
WHERE e.extname = ANY( v.requires )
AND e.extrelocatable
AND v.installed group by e.extname;

extname | array_agg
---------------+--------------------------
fuzzystrmatch | {postgis_tiger_geocoder}

--strk;

The only problem with the above is then it bars an extension from being
relocated even if no extensions reference their schema. Note you wouldn't
be able to tell if an extension references a schema without analyzing the
install script. Which is why I was thinking another property would be
better, cause that could be checked during the install/upgrade of the
dependent extensions.

I personally would be okay with this and is easier to code I think and
doesn't require structural changes, but not sure others would be as it's
taking away permissions they had before when it wasn't necessary to do so.

Thanks,
Regina

#13Regina Obe
lr@pcorp.us
In reply to: Regina Obe (#1)
1 attachment(s)
RE: Ability to reference other extensions by schema in extension scripts

Here is first version of my patch using the
@extschema:extensionname@ syntax you proposed.

This patch includes:
1) Changes to replace references of @extschema:extensionname@ with
the schema of the required extension
2) Documentation for the feature
3) Tests for the feature.

Attached is a revised version of the original patch. It is revised to
prevent

ALTER EXTENSION .. SET SCHEMA if there is a dependent extension that
references the extension in their scripts using @extschema:extensionname@
It also adds additional tests to verify that new feature.

In going thru the code base, I was tempted to add a new dependency type
instead of using the existing DEPENDENCY_AUTO. I think this would be
cleaner, but I felt that was overstepping the area a bit, since it requires
making changes to dependency.h and dependency.c

My main concern with using DEPENDENCY_AUTO is because it was designed for
cases where an object can be dropped without need for CASCADE. In this
case, we don't want a dependent extension to be dropped if it's required is
dropped. However since there will already exist
a DEPENDENCY_NORMAL between the 2 extensions, I figure we are protected
against that issue already.

The issue I ran into is there doesn't seem to be an easy way of checking if
a pg_depend record is already in place, so I ended up dropping it first with
deleteDependencyRecordsForSpecific so I wouldn't need to check and then
reading it.

The reason for that is during CREATE EXTENSION it would need to create the
dependency.
It would also need to do so with ALTER EXTENSION .. UPDATE, since extension
could later on add it in their upgrade scripts and so there end up being
dupes after many ALTER EXTENSION UPDATE calls.

pg_depends getAutoExtensionsOfObject seemed suited to that check, as is
done in

alter.c ExecAlterObjectDependsStmt
/* Avoid duplicates */
currexts = getAutoExtensionsOfObject(address.classId,

address.objectId);
if (!list_member_oid(currexts, refAddr.objectId))
recordDependencyOn(&address, &refAddr,
DEPENDENCY_AUTO_EXTENSION);

but it is hard-coded to only check DEPENDENCY_AUTO_EXTENSION

Why isn't there a variant getAutoExtensionsOfObject take a DEPENDENCY type
as an option so it would be more useful or is there functionality for that I
missed?

Thanks,
Regina

Attachments:

0002-Allow-use-of-extschema-reqextname-to-reference.patchapplication/octet-stream; name=0002-Allow-use-of-extschema-reqextname-to-reference.patchDownload
From 92019e9015d7c6a536a580b92569d67fee2127e7 Mon Sep 17 00:00:00 2001
From: Regina Obe <lr@pcorp.us>
Date: Sun, 5 Feb 2023 01:35:29 -0500
Subject: [PATCH 2/2] 1. Changes to extension execute logic to allow
 referencing required extension schemas by variable 2. Extension tests both
 Makefile and meson.build 3. Documentation of the new feature 4. Add a
 DEPENDENCY_AUTO dependency whenever currently created extension references a
 required schema by variable name in its scripts 5. Prevent an extension from
 being relocated if there is a DEPENDENCY_AUTO depend to it

---
 doc/src/sgml/extend.sgml                      | 17 +++++
 src/backend/commands/extension.c              | 71 ++++++++++++++++++-
 src/test/modules/test_extensions/Makefile     |  9 ++-
 .../expected/test_extensions.out              | 66 +++++++++++++++++
 src/test/modules/test_extensions/meson.build  |  7 ++
 .../test_extensions/sql/test_extensions.sql   | 25 +++++++
 .../test_ext_req_schema1--1.0.sql             |  6 ++
 .../test_ext_req_schema1.control              |  3 +
 .../test_ext_req_schema2--1.0--2.0.sql        |  7 ++
 .../test_ext_req_schema2--1.0.sql             |  9 +++
 .../test_ext_req_schema2.control              |  4 ++
 .../test_ext_req_schema3--1.0.sql             | 13 ++++
 .../test_ext_req_schema3.control              |  4 ++
 13 files changed, 238 insertions(+), 3 deletions(-)
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3.control

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index b70cbe83ae..6c93bddb36 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -908,6 +908,23 @@ RETURNS anycompatible AS ...
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+       An extension might depend on other extensions.
+       It is useful to schema qualify calls to dependent extension to minimize reliance on search_path.
+       This is critical for cases such as functions used in indexes, materialized views, or check constraints.
+       To reference a required extension's schema, you must first have <literal>requires</literal>
+       variable specifying the list of extensions your extension requires.
+       In your extension sql scripts,
+       you can reference a required extension's schema with syntax
+       <literal>@extschema:reqextname@</literal> where <literal>reqextname</literal>
+       is the name of an extension in your <literal>requires</literal> list.
+       All occurrences of this string will be
+       replaced by the schema the required extension is installed in before the script is
+       executed.
+      </para>
+     </listitem>
+
      <listitem>
       <para>
        If the extension does not support relocation at all, set
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index b1509cc505..0fc87c96d3 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1030,6 +1030,70 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 											CStringGetTextDatum(qSchemaName));
 		}
 
+		/*
+		 * If this extension requires other extensions
+		 * Check each required extension to see if it's schema
+		 * is referenced by @extschema:reqextname@ syntax
+		 */
+		if (control->requires)
+		{
+			ObjectAddresses *addrs;
+			addrs = new_object_addresses();
+			ObjectAddress depender;
+			ObjectAddressSet(depender, ExtensionRelationId, CurrentExtensionObject);
+			foreach(lc, control->requires)
+			{
+				char	   *curreq = (char *) lfirst(lc);
+				Oid			reqext;
+				Oid			reqschema;
+				reqext = get_required_extension(curreq,
+												control->name,
+												schemaName,
+												false,
+												NIL,
+												false);
+				reqschema = get_extension_schema(reqext);
+				char *reqname;
+				reqname = get_namespace_name(reqschema);
+				StringInfoData rToken;
+				initStringInfo(&rToken);
+				appendStringInfo(&rToken, "%s%s%s", "@extschema:", curreq, "@");
+				if (strstr(c_sql,rToken.data) != NULL){
+					/*
+					* If the required extension's schema is referenced by variable name, 
+					* add a dependency that the current extension 
+					* references the schema of the required extension 
+					**/
+					
+					ObjectAddress referenced;
+					ObjectAddressSet(referenced, ExtensionRelationId, reqext);
+
+					/*
+					* Deleting the dependency if it exists, because it will be added again
+					* and no easy check to determine if the dependency already exists
+					*/
+					deleteDependencyRecordsForSpecific(depender.classId, depender.objectId,
+										   DEPENDENCY_AUTO,
+										   referenced.classId, referenced.objectId);
+
+					add_exact_object_address(&referenced, addrs);
+					/*
+					* Replace each occurence of @extschema:reqextname@
+					* with the required extension's schema
+					*/ 
+					t_sql = DirectFunctionCall3Coll(replace_text,
+										C_COLLATION_OID,
+										t_sql,
+										CStringGetTextDatum(rToken.data),
+										CStringGetTextDatum(quote_identifier(reqname)));
+				}
+			}
+			/* Record all required schemas that have their schema referenced in script by this one
+			 * with a DEPENDENCY_AUTO in pg_depend 
+			 */ 
+			record_object_address_dependencies(&depender, addrs, DEPENDENCY_AUTO);
+			free_object_addresses(addrs); 
+		}
 		/*
 		 * If module_pathname was set in the control file, substitute its
 		 * value for occurrences of MODULE_PATHNAME.
@@ -2816,12 +2880,17 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 		ObjectAddress dep;
 		Oid			dep_oldNspOid;
 
+		/* If a dependent object scripts 
+		 * reference the variable schema name of this extension
+		 * do not allow relocation */
+		if (pg_depend->deptype == DEPENDENCY_AUTO && pg_depend->classid == ExtensionRelationId )
+			elog(ERROR, "Extension can not be relocated because dependent extension references it's location");
 		/*
 		 * Ignore non-membership dependencies.  (Currently, the only other
 		 * case we could see here is a normal dependency from another
 		 * extension.)
 		 */
-		if (pg_depend->deptype != DEPENDENCY_EXTENSION)
+		if (pg_depend->deptype != DEPENDENCY_EXTENSION )
 			continue;
 
 		dep.classId = pg_depend->classid;
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
index c3139ab0fc..c073df963c 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -6,14 +6,19 @@ PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
 EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
             test_ext7 test_ext8 test_ext_cine test_ext_cor \
             test_ext_cyclic1 test_ext_cyclic2 \
-            test_ext_evttrig
+            test_ext_evttrig \
+            test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3
+
 DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
        test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \
        test_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \
        test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \
        test_ext_cor--1.0.sql \
        test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
-       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql
+       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql \
+       test_ext_req_schema1--1.0.sql \
+       test_ext_req_schema2--1.0.sql test_ext_req_schema2--1.0--2.0.sql \
+       test_ext_req_schema3--1.0.sql
 
 REGRESS = test_extensions test_extdepend
 
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index 821fed38d1..fc1f69c098 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -312,3 +312,69 @@ Objects in extension "test_ext_cine"
  table ext_cine_tab3
 (9 rows)
 
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+NOTICE:  installing required extension "test_ext_req_schema2"
+SELECT dep_req();
+ dep_req 
+---------
+ 1032w
+(1 row)
+
+SELECT dep_req2();
+ dep_req2 
+----------
+ 1032w
+(1 row)
+
+SELECT dep_req3();
+ dep_req3 
+----------
+ 2032w
+(1 row)
+
+SELECT count(*) 
+  FROM pg_depend AS d  
+WHERE d.deptype='a' AND d.refclassid::regclass::text = 'pg_extension' AND d.classid::regclass::text = 'pg_extension';
+ count 
+-------
+     3
+(1 row)
+
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+ERROR:  Extension can not be relocated because dependent extension references it's location
+SELECT dep_req();
+ dep_req 
+---------
+ 1update
+(1 row)
+
+SELECT count(*) 
+  FROM pg_depend AS d  
+WHERE d.deptype='a' AND d.refclassid::regclass::text = 'pg_extension' AND d.classid::regclass::text = 'pg_extension';
+ count 
+-------
+     3
+(1 row)
+
+DROP EXTENSION test_ext_req_schema3;
+SELECT count(*) 
+  FROM pg_depend AS d  
+WHERE d.deptype='a' AND d.refclassid::regclass::text = 'pg_extension' AND d.classid::regclass::text = 'pg_extension';
+ count 
+-------
+     1
+(1 row)
+
+DROP EXTENSION test_ext_req_schema2;
+SELECT count(*) 
+  FROM pg_depend AS d  
+WHERE d.deptype='a' AND d.refclassid::regclass::text = 'pg_extension' AND d.classid::regclass::text = 'pg_extension';
+ count 
+-------
+     0
+(1 row)
+
diff --git a/src/test/modules/test_extensions/meson.build b/src/test/modules/test_extensions/meson.build
index 45597ddc23..7519f6e28e 100644
--- a/src/test/modules/test_extensions/meson.build
+++ b/src/test/modules/test_extensions/meson.build
@@ -31,6 +31,13 @@ install_data(
   'test_ext_evttrig--1.0--2.0.sql',
   'test_ext_evttrig--1.0.sql',
   'test_ext_evttrig.control',
+  'test_ext_req_schema1--1.0.sql',
+  'test_ext_req_schema1.control',
+  'test_ext_req_schema2--1.0.sql',
+  'test_ext_req_schema2.control',
+  'test_ext_req_schema2--1.0--2.0.sql',
+  'test_ext_req_schema3.control',
+  'test_ext_req_schema3--1.0.sql',
   kwargs: contrib_data_args,
 )
 
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
index 41b6cddf0b..41f8e6739d 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -209,3 +209,28 @@ CREATE EXTENSION test_ext_cine;
 ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
 
 \dx+ test_ext_cine
+
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+SELECT dep_req();
+SELECT dep_req2();
+SELECT dep_req3();
+SELECT count(*) 
+  FROM pg_depend AS d  
+WHERE d.deptype='a' AND d.refclassid::regclass::text = 'pg_extension' AND d.classid::regclass::text = 'pg_extension';
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+SELECT dep_req();
+SELECT count(*) 
+  FROM pg_depend AS d  
+WHERE d.deptype='a' AND d.refclassid::regclass::text = 'pg_extension' AND d.classid::regclass::text = 'pg_extension';
+DROP EXTENSION test_ext_req_schema3;
+SELECT count(*) 
+  FROM pg_depend AS d  
+WHERE d.deptype='a' AND d.refclassid::regclass::text = 'pg_extension' AND d.classid::regclass::text = 'pg_extension';
+DROP EXTENSION test_ext_req_schema2;
+SELECT count(*) 
+  FROM pg_depend AS d  
+WHERE d.deptype='a' AND d.refclassid::regclass::text = 'pg_extension' AND d.classid::regclass::text = 'pg_extension';
\ No newline at end of file
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
new file mode 100644
index 0000000000..462fb52145
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
@@ -0,0 +1,6 @@
+/* src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema1" to load this file. \quit
+
+CREATE DOMAIN req AS text
+  CONSTRAINT starts_with_1 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '1');
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1.control b/src/test/modules/test_extensions/test_ext_req_schema1.control
new file mode 100644
index 0000000000..9ea4558a90
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1.control
@@ -0,0 +1,3 @@
+comment = 'Create required extension to be referenced'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
new file mode 100644
index 0000000000..73a44a25e5
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
@@ -0,0 +1,7 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+
+CREATE OR REPLACE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1update''::@extschema:test_ext_req_schema1@.req';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
new file mode 100644
index 0000000000..9fe25d48a1
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN dreq AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2.control b/src/test/modules/test_extensions/test_ext_req_schema2.control
new file mode 100644
index 0000000000..d2ba5add97
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1'
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
new file mode 100644
index 0000000000..bd05669097
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN req2 AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req2() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
+
+CREATE FUNCTION dep_req3() RETURNS @extschema:test_ext_req_schema2@.dreq
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''2032w''::@extschema:test_ext_req_schema2@.dreq';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3.control b/src/test/modules/test_extensions/test_ext_req_schema3.control
new file mode 100644
index 0000000000..b052fad785
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of 2 required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1,test_ext_req_schema2'
-- 
2.21.0.windows.1

In reply to: Regina Obe (#13)
Re: Ability to reference other extensions by schema in extension scripts

On Mon, Feb 06, 2023 at 05:19:39AM -0500, Regina Obe wrote:

Attached is a revised version of the original patch. It is revised to
prevent

ALTER EXTENSION .. SET SCHEMA if there is a dependent extension that
references the extension in their scripts using @extschema:extensionname@
It also adds additional tests to verify that new feature.

In going thru the code base, I was tempted to add a new dependency type
instead of using the existing DEPENDENCY_AUTO. I think this would be
cleaner, but I felt that was overstepping the area a bit, since it requires
making changes to dependency.h and dependency.c

My main concern with using DEPENDENCY_AUTO is because it was designed for
cases where an object can be dropped without need for CASCADE. In this
case, we don't want a dependent extension to be dropped if it's required is
dropped. However since there will already exist
a DEPENDENCY_NORMAL between the 2 extensions, I figure we are protected
against that issue already.

I was thinking: how about using the "refobjsubid" to encode the
"level" of dependency on an extension ? Right now "refobjsubid" is
always 0 when the referenced object is an extension.
Could we consider subid=1 to mean the dependency is not only
on the extension but ALSO on it's schema location ?

Also: should we really allow extensions to rely on other extension
w/out fully-qualifying calls to their functions ? Or should it be
discouraged and thus forbidden ? If we wanted to forbid it we then
would not need to encode any additional dependency but rather always
forbid `ALTER EXTENSION .. SET SCHEMA` whenever the extension is
a dependency of any other extension.

On the code in the patch itself, I tried with this simple use case:

- ext1, relocatable, exposes an ext1log(text) function

- ext2, relocatable, exposes an ext2log(text) function
calling @extschema:ext1@.ext1log()

What is not good:

- Drop of ext1 automatically cascades to drop of ext2 without even a notice:

test=# create extension ext2 cascade;
NOTICE: installing required extension "ext1"
CREATE EXTENSION
test=# drop extension ext1;
DROP EXTENSION -- no WARNING, no NOTICE, ext2 is gone

What is good:

- ext1 cannot be relocated while ext2 is loaded:

test=# create extension ext2 cascade;
NOTICE: installing required extension "ext1"
CREATE EXTENSION
test=# alter extension ext1 set schema n1;
ERROR: Extension can not be relocated because dependent extension references it's location
test=# drop extension ext2;
DROP EXTENSION
test=# alter extension ext1 set schema n1;
ALTER EXTENSION

--strk;

Libre GIS consultant/developer
https://strk.kbt.io/services.html

#15Regina Obe
lr@pcorp.us
In reply to: Sandro Santilli (#14)
RE: Ability to reference other extensions by schema in extension scripts

On Mon, Feb 06, 2023 at 05:19:39AM -0500, Regina Obe wrote:

Attached is a revised version of the original patch. It is revised to
prevent

ALTER EXTENSION .. SET SCHEMA if there is a dependent extension that
references the extension in their scripts using
@extschema:extensionname@ It also adds additional tests to verify that

new feature.

In going thru the code base, I was tempted to add a new dependency
type instead of using the existing DEPENDENCY_AUTO. I think this
would be cleaner, but I felt that was overstepping the area a bit,
since it requires making changes to dependency.h and dependency.c

My main concern with using DEPENDENCY_AUTO is because it was designed
for cases where an object can be dropped without need for CASCADE. In
this case, we don't want a dependent extension to be dropped if it's
required is dropped. However since there will already exist a
DEPENDENCY_NORMAL between the 2 extensions, I figure we are protected
against that issue already.

I was thinking: how about using the "refobjsubid" to encode the "level" of
dependency on an extension ? Right now "refobjsubid" is always 0 when the
referenced object is an extension.
Could we consider subid=1 to mean the dependency is not only on the
extension but ALSO on it's schema location ?

I like that idea. It's only been ever used for tables I think, but I don't
see why it wouldn't apply in this case as the concept is kinda the same.
Only concern if other parts rely on this being 0.

The other question, should this just update the existing DEPENDENCY_NORMAL
extension or add a new DEPENDENCY_NORMAL between the extensions with
subid=1?

Also: should we really allow extensions to rely on other extension w/out

fully-

qualifying calls to their functions ? Or should it be discouraged and thus
forbidden ? If we wanted to forbid it we then would not need to encode any
additional dependency but rather always forbid `ALTER EXTENSION .. SET
SCHEMA` whenever the extension is a dependency of any other extension.

On the code in the patch itself, I tried with this simple use case:

- ext1, relocatable, exposes an ext1log(text) function

- ext2, relocatable, exposes an ext2log(text) function
calling @extschema:ext1@.ext1log()

This would be an okay solution to me too if everyone is okay with it.

What is not good:

- Drop of ext1 automatically cascades to drop of ext2 without even a
notice:

test=# create extension ext2 cascade;
NOTICE: installing required extension "ext1"
CREATE EXTENSION
test=# drop extension ext1;
DROP EXTENSION -- no WARNING, no NOTICE, ext2 is gone

Oops. I don't know why I thought the normal dependency would protect
against this. I should have tested that. So DEPENDENCY_AUTO is not an
option to use and creating a new type of dependency seems like over stepping
the bounds of this patch.

What is good:

- ext1 cannot be relocated while ext2 is loaded:

test=# create extension ext2 cascade;
NOTICE: installing required extension "ext1"
CREATE EXTENSION
test=# alter extension ext1 set schema n1;
ERROR: Extension can not be relocated because dependent
extension references it's location
test=# drop extension ext2;
DROP EXTENSION
test=# alter extension ext1 set schema n1;
ALTER EXTENSION

--strk;

Libre GIS consultant/developer
https://strk.kbt.io/services.html

So in conclusion we have 3 possible paths to go with this

1) Just don't allow any extensions referenced by other extensions to be
relocatable.
It will show a message something like
"SET SCHEMA not allowed because other extensions depend on it"
Given that if you don't specify relocatable in you .control file, the assume
is relocatable = false , this isn't too far off from standard protocol.

2) Use objsubid=1 to denote that another extension explicitly references the
schema of another extension so setting schema of other extension is not
okay. So instead of introducing another dependency, we'd update the
DEPENDENCY_NORMAL one between the two schemas with objsubid=1 instead of 0.

This has 2 approaches:

a) Update the existing DEPENDENCY_NORMAL between the two extensions setting
the objsubid=1

or
b) Create a new DEPEDENCY_NORMAL between the two extensions with objsubid=1

I'm not sure if either has implications in backup / restore . I suspect b
would be safer since I suspect objsubid might be checked and this
dependency only needs checking during SET SCHEMA time.

3) Create a whole new DEPENDENCY type, perhaps calling it something like
DEPENDENCY_EXTENSION_SCHEMA

4) Just don't allow @extschema:<reqextension>@ syntax to be used unless the
<reqextension> is marked as relocatable=false. This one I don't like
because it doesn't solve my fundamental issue of

postgis_tiger_geocoder relying on fuzzystrmatch, which is marked as
relocatable.

The main issue I was trying to solve is my extension references
fuzzystrmatch functions in a function used for functional indexes, and this
fails restore of table indexes because I can't schema qualify the
fuzzystrmatch extension in the backing function.

If no one has any opinion, I'll go with option 1 which is the one that strk
had actually proposed before and seems least programmatically invasive, but
perhaps more annoying user facing.

My preferred would be #2

Thanks,
Regina

#16Regina Obe
lr@pcorp.us
In reply to: Regina Obe (#1)
1 attachment(s)
RE: Ability to reference other extensions by schema in extension scripts

So in conclusion we have 3 possible paths to go with this

1) Just don't allow any extensions referenced by other extensions to be
relocatable.
It will show a message something like
"SET SCHEMA not allowed because other extensions depend on it"
Given that if you don't specify relocatable in you .control file, the

assume is

relocatable = false , this isn't too far off from standard protocol.

2) Use objsubid=1 to denote that another extension explicitly references

the

schema of another extension so setting schema of other extension is not

okay.

So instead of introducing another dependency, we'd update the
DEPENDENCY_NORMAL one between the two schemas with objsubid=1
instead of 0.

This has 2 approaches:

a) Update the existing DEPENDENCY_NORMAL between the two extensions
setting the objsubid=1

or
b) Create a new DEPEDENCY_NORMAL between the two extensions with
objsubid=1

I'm not sure if either has implications in backup / restore . I suspect b

would

be safer since I suspect objsubid might be checked and this dependency

only

needs checking during SET SCHEMA time.

3) Create a whole new DEPENDENCY type, perhaps calling it something like
DEPENDENCY_EXTENSION_SCHEMA

4) Just don't allow @extschema:<reqextension>@ syntax to be used unless
the <reqextension> is marked as relocatable=false. This one I don't like
because it doesn't solve my fundamental issue of

postgis_tiger_geocoder relying on fuzzystrmatch, which is marked as
relocatable.

The main issue I was trying to solve is my extension references

fuzzystrmatch

functions in a function used for functional indexes, and this fails

restore of

table indexes because I can't schema qualify the fuzzystrmatch extension

in

the backing function.

If no one has any opinion, I'll go with option 1 which is the one that

strk had

actually proposed before and seems least programmatically invasive, but
perhaps more annoying user facing.

My preferred would be #2

Thanks,
Regina

Attached is my revision 3 patch, which follows the proposed #1.
Don't allow schema relocation of an extension if another extension requires
it.

Attachments:

0003-Allow-use-of-extschema-reqextname-to-reference.patchapplication/octet-stream; name=0003-Allow-use-of-extschema-reqextname-to-reference.patchDownload
From d25ae7c6795922c82921fb9abfd29719fcc8bdc0 Mon Sep 17 00:00:00 2001
From: Regina Obe <lr@pcorp.us>
Date: Sun, 5 Feb 2023 01:35:29 -0500
Subject: [PATCH 3/3] Allow @extschema:<extname>@ in extension scripts

Changes to extension execute logic to allow referencing required
extension schemas by variable

This feature is needed in cases such as
when another extension packages a function
which references an object from one of required extensions
and this function is used to back
indexes, constraints, and materialized views

Without this feature, because pg_restore removes the paths,
these indexes, constraints, materialized views are not restored
because when the function is called, the function call fails
because the dependent item is not schema qualified.

 - Extension tests both Makefile and meson.build
 - Documentation of the new feature
 - Prevent an extension from being relocated if
   another extension requires it

 Discussion: https://postgr.es/m/000801d94959%2463a9f520%242afddf60%24%40pcorp.us
---
 doc/src/sgml/extend.sgml                      | 17 ++++++
 src/backend/commands/extension.c              | 55 +++++++++++++++++++
 src/test/modules/test_extensions/Makefile     |  9 ++-
 .../expected/test_extensions.out              | 40 ++++++++++++++
 src/test/modules/test_extensions/meson.build  |  7 +++
 .../test_extensions/sql/test_extensions.sql   | 14 +++++
 .../test_ext_req_schema1--1.0.sql             |  6 ++
 .../test_ext_req_schema1.control              |  3 +
 .../test_ext_req_schema2--1.0--2.0.sql        |  7 +++
 .../test_ext_req_schema2--1.0.sql             |  9 +++
 .../test_ext_req_schema2.control              |  4 ++
 .../test_ext_req_schema3--1.0.sql             | 13 +++++
 .../test_ext_req_schema3.control              |  4 ++
 13 files changed, 186 insertions(+), 2 deletions(-)
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3.control

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index b70cbe83ae..6c93bddb36 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -908,6 +908,23 @@ RETURNS anycompatible AS ...
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+       An extension might depend on other extensions.
+       It is useful to schema qualify calls to dependent extension to minimize reliance on search_path.
+       This is critical for cases such as functions used in indexes, materialized views, or check constraints.
+       To reference a required extension's schema, you must first have <literal>requires</literal>
+       variable specifying the list of extensions your extension requires.
+       In your extension sql scripts,
+       you can reference a required extension's schema with syntax
+       <literal>@extschema:reqextname@</literal> where <literal>reqextname</literal>
+       is the name of an extension in your <literal>requires</literal> list.
+       All occurrences of this string will be
+       replaced by the schema the required extension is installed in before the script is
+       executed.
+      </para>
+     </listitem>
+
      <listitem>
       <para>
        If the extension does not support relocation at all, set
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index b1509cc505..57a21ba41e 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -854,6 +854,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 						 const char *schemaName, Oid schemaOid)
 {
 	bool		switch_to_superuser = false;
+	char	   *origSchemaName = (char *) schemaName;
 	char	   *filename;
 	Oid			save_userid = 0;
 	int			save_sec_context = 0;
@@ -1030,6 +1031,46 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 											CStringGetTextDatum(qSchemaName));
 		}
 
+		/*
+		 * If this extension requires other extensions
+		 * Check each required extension to see if it's schema
+		 * is referenced by @extschema:reqextname@ syntax
+		 */
+		if (control->requires)
+		{
+			foreach(lc, control->requires)
+			{
+				char	   *curreq;
+				curreq = (char *) lfirst(lc);
+				Oid			reqext;
+				Oid			reqschema;
+				reqext = get_required_extension(curreq,
+												control->name,
+												origSchemaName,
+												false,
+												NIL,
+												false);
+				reqschema = get_extension_schema(reqext);
+				char *reqname;
+				reqname = get_namespace_name(reqschema);
+				StringInfoData rToken;
+				initStringInfo(&rToken);
+				appendStringInfo(&rToken, "%s%s%s", "@extschema:", curreq, "@");
+				if (strstr(c_sql,rToken.data) != NULL){
+					/*
+					* If the required extension's schema is referenced
+					* by variable name,
+					* Replace each occurence of @extschema:reqextname@
+					* with the required extension's schema
+					*/
+					t_sql = DirectFunctionCall3Coll(replace_text,
+										C_COLLATION_OID,
+										t_sql,
+										CStringGetTextDatum(rToken.data),
+										CStringGetTextDatum(quote_identifier(reqname)));
+				}
+			}
+		}
 		/*
 		 * If module_pathname was set in the control file, substitute its
 		 * value for occurrences of MODULE_PATHNAME.
@@ -2816,6 +2857,20 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 		ObjectAddress dep;
 		Oid			dep_oldNspOid;
 
+		/* If an extension requires this extension
+		 * do not allow relocation */
+		if (pg_depend->deptype == DEPENDENCY_NORMAL && pg_depend->classid == ExtensionRelationId){
+			dep.classId = pg_depend->classid;
+			dep.objectId = pg_depend->objid;
+			dep.objectSubId = pg_depend->objsubid;
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot SET SCHEMA of extension %s because other extensions require it",
+							NameStr(extForm->extname)),
+					 errdetail("%s requires extension %s",
+							   getObjectDescription(&dep, false), NameStr(extForm->extname))));
+
+		}
 		/*
 		 * Ignore non-membership dependencies.  (Currently, the only other
 		 * case we could see here is a normal dependency from another
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
index c3139ab0fc..c073df963c 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -6,14 +6,19 @@ PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
 EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
             test_ext7 test_ext8 test_ext_cine test_ext_cor \
             test_ext_cyclic1 test_ext_cyclic2 \
-            test_ext_evttrig
+            test_ext_evttrig \
+            test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3
+
 DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
        test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \
        test_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \
        test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \
        test_ext_cor--1.0.sql \
        test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
-       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql
+       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql \
+       test_ext_req_schema1--1.0.sql \
+       test_ext_req_schema2--1.0.sql test_ext_req_schema2--1.0--2.0.sql \
+       test_ext_req_schema3--1.0.sql
 
 REGRESS = test_extensions test_extdepend
 
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index 821fed38d1..21dfd05982 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -312,3 +312,43 @@ Objects in extension "test_ext_cine"
  table ext_cine_tab3
 (9 rows)
 
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+NOTICE:  installing required extension "test_ext_req_schema2"
+SELECT dep_req();
+ dep_req 
+---------
+ 1032w
+(1 row)
+
+SELECT dep_req2();
+ dep_req2 
+----------
+ 1032w
+(1 row)
+
+SELECT dep_req3();
+ dep_req3 
+----------
+ 2032w
+(1 row)
+
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+ERROR:  cannot SET SCHEMA of extension test_ext_req_schema1 because other extensions require it
+DETAIL:  extension test_ext_req_schema3 requires extension test_ext_req_schema1
+SELECT dep_req();
+ dep_req 
+---------
+ 1update
+(1 row)
+
+DROP EXTENSION test_ext_req_schema1;
+ERROR:  cannot drop extension test_ext_req_schema1 because other objects depend on it
+DETAIL:  extension test_ext_req_schema2 depends on extension test_ext_req_schema1
+extension test_ext_req_schema3 depends on extension test_ext_req_schema1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP EXTENSION test_ext_req_schema3;
+DROP EXTENSION test_ext_req_schema2;
diff --git a/src/test/modules/test_extensions/meson.build b/src/test/modules/test_extensions/meson.build
index 45597ddc23..7519f6e28e 100644
--- a/src/test/modules/test_extensions/meson.build
+++ b/src/test/modules/test_extensions/meson.build
@@ -31,6 +31,13 @@ install_data(
   'test_ext_evttrig--1.0--2.0.sql',
   'test_ext_evttrig--1.0.sql',
   'test_ext_evttrig.control',
+  'test_ext_req_schema1--1.0.sql',
+  'test_ext_req_schema1.control',
+  'test_ext_req_schema2--1.0.sql',
+  'test_ext_req_schema2.control',
+  'test_ext_req_schema2--1.0--2.0.sql',
+  'test_ext_req_schema3.control',
+  'test_ext_req_schema3--1.0.sql',
   kwargs: contrib_data_args,
 )
 
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
index 41b6cddf0b..81ecd13736 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -209,3 +209,17 @@ CREATE EXTENSION test_ext_cine;
 ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
 
 \dx+ test_ext_cine
+
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+SELECT dep_req();
+SELECT dep_req2();
+SELECT dep_req3();
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+SELECT dep_req();
+DROP EXTENSION test_ext_req_schema1;
+DROP EXTENSION test_ext_req_schema3;
+DROP EXTENSION test_ext_req_schema2;
\ No newline at end of file
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
new file mode 100644
index 0000000000..462fb52145
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
@@ -0,0 +1,6 @@
+/* src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema1" to load this file. \quit
+
+CREATE DOMAIN req AS text
+  CONSTRAINT starts_with_1 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '1');
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1.control b/src/test/modules/test_extensions/test_ext_req_schema1.control
new file mode 100644
index 0000000000..9ea4558a90
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1.control
@@ -0,0 +1,3 @@
+comment = 'Create required extension to be referenced'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
new file mode 100644
index 0000000000..73a44a25e5
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
@@ -0,0 +1,7 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+
+CREATE OR REPLACE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1update''::@extschema:test_ext_req_schema1@.req';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
new file mode 100644
index 0000000000..9fe25d48a1
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN dreq AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2.control b/src/test/modules/test_extensions/test_ext_req_schema2.control
new file mode 100644
index 0000000000..d2ba5add97
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1'
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
new file mode 100644
index 0000000000..bd05669097
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN req2 AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req2() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
+
+CREATE FUNCTION dep_req3() RETURNS @extschema:test_ext_req_schema2@.dreq
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''2032w''::@extschema:test_ext_req_schema2@.dreq';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3.control b/src/test/modules/test_extensions/test_ext_req_schema3.control
new file mode 100644
index 0000000000..b052fad785
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of 2 required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1,test_ext_req_schema2'
-- 
2.21.0.windows.1

In reply to: Regina Obe (#15)
Re: Ability to reference other extensions by schema in extension scripts

On Sat, Feb 25, 2023 at 03:40:24PM -0500, Regina Obe wrote:

On Mon, Feb 06, 2023 at 05:19:39AM -0500, Regina Obe wrote:

I was thinking: how about using the "refobjsubid" to encode the "level" of
dependency on an extension ? Right now "refobjsubid" is always 0 when the
referenced object is an extension.
Could we consider subid=1 to mean the dependency is not only on the
extension but ALSO on it's schema location ?

I like that idea. It's only been ever used for tables I think, but I don't
see why it wouldn't apply in this case as the concept is kinda the same.
Only concern if other parts rely on this being 0.

This has to be verified, yes. But it feels to me like "must be 0" was
mostly to _allow_ for future extensions like the proposed one.

The other question, should this just update the existing DEPENDENCY_NORMAL
extension or add a new DEPENDENCY_NORMAL between the extensions with
subid=1?

I'd use the existing record.

--strk;

Libre GIS consultant/developer
https://strk.kbt.io/services.html

In reply to: Regina Obe (#16)
Re: Ability to reference other extensions by schema in extension scripts

On Sun, Feb 26, 2023 at 01:39:24AM -0500, Regina Obe wrote:

1) Just don't allow any extensions referenced by other
extensions to be relocatable.

Attached is my revision 3 patch, which follows the proposed #1.
Don't allow schema relocation of an extension if another extension
requires it.

I've built a version of PostgreSQL with this patch applied and I
confirm it works as expected.

The "ext1" is relocatable and creates a function ext1log():

=# create extension ext1 schema n1;
CREATE EXTENSION

The "ext2" is relocatable and creates a function ext2log() relying
on the ext1log() function from "ext1" extension, referencing
it via @extschema:ext1@:

=# create extension ext2 schema n2;
CREATE EXTENSION
=# select n2.ext2log('hello'); -- things work here
ext1: ext2: hello

By creating "ext2", "ext1" becomes effectively non-relocatable:

=# alter extension ext1 set schema n2;
ERROR: cannot SET SCHEMA of extension ext1 because other extensions
require it
DETAIL: extension ext2 requires extension ext1

Drop "ext2" makes "ext1" relocatable again:

=# drop extension ext2;
DROP EXTENSION
=# alter extension ext1 set schema n2;
ALTER EXTENSION

Upon re-creating "ext2" the referenced ext1 schema will be
the correct one:

=# create extension ext2 schema n1;
CREATE EXTENSION
=# select n1.ext2log('hello');
ext1: ext2: hello

The code itself builds w/out warnings with:

mkdir build
cd build
../configure
make 2> ERR # ERR is empty

The testsuite reports all successes:

make check
[...]
=======================
All 213 tests passed.
=======================

Since I didn't see the tests for extension in there, I've also
explicitly run that portion:

make -C src/test/modules/test_extensions/ check
[...]
test test_extensions ... ok 32 ms
test test_extdepend ... ok 12 ms
[...]
=====================
All 2 tests passed.
=====================

As mentioned already the downside of this patch is that it would
not be possibile to change the schema of an otherwise relocatable
extension once other extension depend on it, but I can't think of
any good reason to allow that, as it would mean dependent code
would need to always dynamically determine the install location
of the objects in that extension, which sounds dangerous, security
wise.

--strk;

Libre GIS consultant/developer
https://strk.kbt.io/services.html

#19Regina Obe
lr@pcorp.us
In reply to: Sandro Santilli (#18)
1 attachment(s)
RE: Ability to reference other extensions by schema in extension scripts

On Sun, Feb 26, 2023 at 01:39:24AM -0500, Regina Obe wrote:

1) Just don't allow any extensions referenced by other
extensions to be relocatable.

Attached is my revision 3 patch, which follows the proposed #1.
Don't allow schema relocation of an extension if another extension
requires it.

I've built a version of PostgreSQL with this patch applied and I confirm

it

works as expected.

The "ext1" is relocatable and creates a function ext1log():

=# create extension ext1 schema n1;
CREATE EXTENSION

The "ext2" is relocatable and creates a function ext2log() relying on the
ext1log() function from "ext1" extension, referencing it via
@extschema:ext1@:

=# create extension ext2 schema n2;
CREATE EXTENSION
=# select n2.ext2log('hello'); -- things work here
ext1: ext2: hello

By creating "ext2", "ext1" becomes effectively non-relocatable:

=# alter extension ext1 set schema n2;
ERROR: cannot SET SCHEMA of extension ext1 because other extensions
require it
DETAIL: extension ext2 requires extension ext1

Drop "ext2" makes "ext1" relocatable again:

=# drop extension ext2;
DROP EXTENSION
=# alter extension ext1 set schema n2;
ALTER EXTENSION

Upon re-creating "ext2" the referenced ext1 schema will be the correct

one:

=# create extension ext2 schema n1;
CREATE EXTENSION
=# select n1.ext2log('hello');
ext1: ext2: hello

The code itself builds w/out warnings with:

mkdir build
cd build
../configure
make 2> ERR # ERR is empty

The testsuite reports all successes:

make check
[...]
=======================
All 213 tests passed.
=======================

Since I didn't see the tests for extension in there, I've also explicitly

run that

portion:

make -C src/test/modules/test_extensions/ check
[...]
test test_extensions ... ok 32 ms
test test_extdepend ... ok 12 ms
[...]
=====================
All 2 tests passed.
=====================

As mentioned already the downside of this patch is that it would not be
possibile to change the schema of an otherwise relocatable extension once
other extension depend on it, but I can't think of any good reason to

allow

that, as it would mean dependent code would need to always dynamically
determine the install location of the objects in that extension, which

sounds

dangerous, security wise.

--strk;

Libre GIS consultant/developer
https://strk.kbt.io/services.html

Oops I had forgotten to submit the updated patch strk was testing against in
my fork.
He had asked me to clean up the warnings off list and the description.

Attached is the revised.
Thanks strk for the patient help and guidance.

Thanks,
Regina

Attachments:

0004-Allow-use-of-extschema-reqextname-to-reference.patchapplication/octet-stream; name=0004-Allow-use-of-extschema-reqextname-to-reference.patchDownload
From 5279686f1338f31d974f7bb749a201e0d0b280fc Mon Sep 17 00:00:00 2001
From: Regina Obe <lr@pcorp.us>
Date: Sun, 5 Feb 2023 01:35:29 -0500
Subject: [PATCH 4/4] Allow @extschema:<extname>@ in extension scripts

This feature allows an extension author to reference
extension schemas of extensions in the requires variable
of the control file using variable syntax
@extschema:<extname>@  where <extname> is the
name of any of the listed required extensions.

This feature is needed particularly in cases
when another extension packages a function
which references an object from one of its
required extensions and this function is used to back
indexes, constraints, and materialized views.

Without this feature, because pg_restore removes the paths,
these indexes, constraints, materialized views are not restored
because when the function is called, the function call fails
because items it depends on cannot be schema qualified.

This feature will allow extension authors to schema qualify
references to required extension objects to prevent this issue.
Aside from this issue, schema qualifying all these references will
improve security by preventing a rogue version of a function from being
used.

 - Extension tests both Makefile and meson.build
 - Documentation of the new feature
 - Prevent an extension from being relocated if
   another extension requires it

 Discussion: https://postgr.es/m/000801d94959%2463a9f520%242afddf60%24%40pcorp.us
---
 doc/src/sgml/extend.sgml                      | 17 +++++++
 src/backend/commands/extension.c              | 49 +++++++++++++++++++
 src/test/modules/test_extensions/Makefile     |  9 +++-
 .../expected/test_extensions.out              | 40 +++++++++++++++
 src/test/modules/test_extensions/meson.build  |  7 +++
 .../test_extensions/sql/test_extensions.sql   | 14 ++++++
 .../test_ext_req_schema1--1.0.sql             |  6 +++
 .../test_ext_req_schema1.control              |  3 ++
 .../test_ext_req_schema2--1.0--2.0.sql        |  7 +++
 .../test_ext_req_schema2--1.0.sql             |  9 ++++
 .../test_ext_req_schema2.control              |  4 ++
 .../test_ext_req_schema3--1.0.sql             | 13 +++++
 .../test_ext_req_schema3.control              |  4 ++
 13 files changed, 180 insertions(+), 2 deletions(-)
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3.control

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index b70cbe83ae..6c93bddb36 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -908,6 +908,23 @@ RETURNS anycompatible AS ...
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+       An extension might depend on other extensions.
+       It is useful to schema qualify calls to dependent extension to minimize reliance on search_path.
+       This is critical for cases such as functions used in indexes, materialized views, or check constraints.
+       To reference a required extension's schema, you must first have <literal>requires</literal>
+       variable specifying the list of extensions your extension requires.
+       In your extension sql scripts,
+       you can reference a required extension's schema with syntax
+       <literal>@extschema:reqextname@</literal> where <literal>reqextname</literal>
+       is the name of an extension in your <literal>requires</literal> list.
+       All occurrences of this string will be
+       replaced by the schema the required extension is installed in before the script is
+       executed.
+      </para>
+     </listitem>
+
      <listitem>
       <para>
        If the extension does not support relocation at all, set
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index b1509cc505..17ebb1a4fc 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1030,6 +1030,41 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 											CStringGetTextDatum(qSchemaName));
 		}
 
+		/*
+		* If this extension requires other extensions
+		* Check each required extension to see if its schema
+		* is referenced by @extschema:reqextname@ syntax
+		*/
+		foreach(lc, control->requires)
+		{
+			char		*curreq = (char *) lfirst(lc);
+			Oid			reqext;
+			Oid			reqschema;
+			StringInfoData rToken;
+			char		*reqname;
+			reqext = get_required_extension(curreq,
+											control->name,
+											(char *) schemaName,
+											false,
+											NIL,
+											false);
+			reqschema = get_extension_schema(reqext);
+			reqname = get_namespace_name(reqschema);
+			initStringInfo(&rToken);
+			appendStringInfo(&rToken, "%s%s%s", "@extschema:", curreq, "@");
+
+			/*
+			* If the required extension's schema is referenced
+			* by variable name,
+			* Replace each occurence of @extschema:<reqextname>@
+			* with the required extension's schema
+			*/
+			t_sql = DirectFunctionCall3Coll(replace_text,
+								C_COLLATION_OID,
+								t_sql,
+								CStringGetTextDatum(rToken.data),
+								CStringGetTextDatum(quote_identifier(reqname)));
+		}
 		/*
 		 * If module_pathname was set in the control file, substitute its
 		 * value for occurrences of MODULE_PATHNAME.
@@ -2816,6 +2851,20 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 		ObjectAddress dep;
 		Oid			dep_oldNspOid;
 
+		/* If an extension requires this extension
+		 * do not allow relocation */
+		if (pg_depend->deptype == DEPENDENCY_NORMAL && pg_depend->classid == ExtensionRelationId){
+			dep.classId = pg_depend->classid;
+			dep.objectId = pg_depend->objid;
+			dep.objectSubId = pg_depend->objsubid;
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot SET SCHEMA of extension %s because other extensions require it",
+							NameStr(extForm->extname)),
+					 errdetail("%s requires extension %s",
+							   getObjectDescription(&dep, false), NameStr(extForm->extname))));
+
+		}
 		/*
 		 * Ignore non-membership dependencies.  (Currently, the only other
 		 * case we could see here is a normal dependency from another
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
index c3139ab0fc..c073df963c 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -6,14 +6,19 @@ PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
 EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
             test_ext7 test_ext8 test_ext_cine test_ext_cor \
             test_ext_cyclic1 test_ext_cyclic2 \
-            test_ext_evttrig
+            test_ext_evttrig \
+            test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3
+
 DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
        test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \
        test_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \
        test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \
        test_ext_cor--1.0.sql \
        test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
-       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql
+       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql \
+       test_ext_req_schema1--1.0.sql \
+       test_ext_req_schema2--1.0.sql test_ext_req_schema2--1.0--2.0.sql \
+       test_ext_req_schema3--1.0.sql
 
 REGRESS = test_extensions test_extdepend
 
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index 821fed38d1..21dfd05982 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -312,3 +312,43 @@ Objects in extension "test_ext_cine"
  table ext_cine_tab3
 (9 rows)
 
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+NOTICE:  installing required extension "test_ext_req_schema2"
+SELECT dep_req();
+ dep_req 
+---------
+ 1032w
+(1 row)
+
+SELECT dep_req2();
+ dep_req2 
+----------
+ 1032w
+(1 row)
+
+SELECT dep_req3();
+ dep_req3 
+----------
+ 2032w
+(1 row)
+
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+ERROR:  cannot SET SCHEMA of extension test_ext_req_schema1 because other extensions require it
+DETAIL:  extension test_ext_req_schema3 requires extension test_ext_req_schema1
+SELECT dep_req();
+ dep_req 
+---------
+ 1update
+(1 row)
+
+DROP EXTENSION test_ext_req_schema1;
+ERROR:  cannot drop extension test_ext_req_schema1 because other objects depend on it
+DETAIL:  extension test_ext_req_schema2 depends on extension test_ext_req_schema1
+extension test_ext_req_schema3 depends on extension test_ext_req_schema1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP EXTENSION test_ext_req_schema3;
+DROP EXTENSION test_ext_req_schema2;
diff --git a/src/test/modules/test_extensions/meson.build b/src/test/modules/test_extensions/meson.build
index 45597ddc23..7519f6e28e 100644
--- a/src/test/modules/test_extensions/meson.build
+++ b/src/test/modules/test_extensions/meson.build
@@ -31,6 +31,13 @@ install_data(
   'test_ext_evttrig--1.0--2.0.sql',
   'test_ext_evttrig--1.0.sql',
   'test_ext_evttrig.control',
+  'test_ext_req_schema1--1.0.sql',
+  'test_ext_req_schema1.control',
+  'test_ext_req_schema2--1.0.sql',
+  'test_ext_req_schema2.control',
+  'test_ext_req_schema2--1.0--2.0.sql',
+  'test_ext_req_schema3.control',
+  'test_ext_req_schema3--1.0.sql',
   kwargs: contrib_data_args,
 )
 
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
index 41b6cddf0b..81ecd13736 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -209,3 +209,17 @@ CREATE EXTENSION test_ext_cine;
 ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
 
 \dx+ test_ext_cine
+
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+SELECT dep_req();
+SELECT dep_req2();
+SELECT dep_req3();
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+SELECT dep_req();
+DROP EXTENSION test_ext_req_schema1;
+DROP EXTENSION test_ext_req_schema3;
+DROP EXTENSION test_ext_req_schema2;
\ No newline at end of file
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
new file mode 100644
index 0000000000..462fb52145
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
@@ -0,0 +1,6 @@
+/* src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema1" to load this file. \quit
+
+CREATE DOMAIN req AS text
+  CONSTRAINT starts_with_1 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '1');
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1.control b/src/test/modules/test_extensions/test_ext_req_schema1.control
new file mode 100644
index 0000000000..9ea4558a90
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1.control
@@ -0,0 +1,3 @@
+comment = 'Create required extension to be referenced'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
new file mode 100644
index 0000000000..73a44a25e5
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
@@ -0,0 +1,7 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+
+CREATE OR REPLACE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1update''::@extschema:test_ext_req_schema1@.req';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
new file mode 100644
index 0000000000..9fe25d48a1
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN dreq AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2.control b/src/test/modules/test_extensions/test_ext_req_schema2.control
new file mode 100644
index 0000000000..d2ba5add97
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1'
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
new file mode 100644
index 0000000000..bd05669097
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN req2 AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req2() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
+
+CREATE FUNCTION dep_req3() RETURNS @extschema:test_ext_req_schema2@.dreq
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''2032w''::@extschema:test_ext_req_schema2@.dreq';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3.control b/src/test/modules/test_extensions/test_ext_req_schema3.control
new file mode 100644
index 0000000000..b052fad785
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of 2 required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1,test_ext_req_schema2'
-- 
2.21.0.windows.1

In reply to: Regina Obe (#19)
Re: Ability to reference other extensions by schema in extension scripts

The following review has been posted through the commitfest application:
make installcheck-world: tested, failed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed

I've applied the patch attached to message /messages/by-id/000401d94bc8$48dff700$da9fe500$@pcorp.us (md5sum a7d45a32c054919d94cd4a26d7d07c20) onto current tip of the master branch being 128dd9f9eca0b633b51ffcd5b0f798fbc48ec4c0

The review written in /messages/by-id/20230228224608.ak7br5shev4wic5a@c19 all still applies.

The `make installcheck-world` test fails for me but the failures seem unrelated to the patch (many occurrences of "+ERROR: function pg_input_error_info(unknown, unknown) does not exist" in the regression.diff).

Documentation exists for the new feature

The new status of this patch is: Ready for Committer

#21Gregory Stark (as CFM)
stark.cfm@gmail.com
In reply to: Sandro Santilli (#20)
Re: Ability to reference other extensions by schema in extension scripts

It looks like this patch needs a quick rebase, there's a conflict in
the meson.build.

I'll leave the state since presumably this would be easy to resolve
but it would be more likely to get attention if it's actually building
cleanly.

http://cfbot.cputube.org/patch_42_4023.log

On Tue, 28 Feb 2023 at 18:44, Sandro Santilli <strk@kbt.io> wrote:

The following review has been posted through the commitfest application:
make installcheck-world: tested, failed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed

I've applied the patch attached to message /messages/by-id/000401d94bc8$48dff700$da9fe500$@pcorp.us (md5sum a7d45a32c054919d94cd4a26d7d07c20) onto current tip of the master branch being 128dd9f9eca0b633b51ffcd5b0f798fbc48ec4c0

The review written in /messages/by-id/20230228224608.ak7br5shev4wic5a@c19 all still applies.

The `make installcheck-world` test fails for me but the failures seem unrelated to the patch (many occurrences of "+ERROR: function pg_input_error_info(unknown, unknown) does not exist" in the regression.diff).

Documentation exists for the new feature

The new status of this patch is: Ready for Committer

--
Gregory Stark
As Commitfest Manager

#22Regina Obe
lr@pcorp.us
In reply to: Gregory Stark (as CFM) (#21)
RE: Ability to reference other extensions by schema in extension scripts

It looks like this patch needs a quick rebase, there's a conflict in the
meson.build.

I'll leave the state since presumably this would be easy to resolve but it would
be more likely to get attention if it's actually building cleanly.

http://cfbot.cputube.org/patch_42_4023.log

On Tue, 28 Feb 2023 at 18:44, Sandro Santilli <strk@kbt.io> wrote:

The following review has been posted through the commitfest application:
make installcheck-world: tested, failed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed

I've applied the patch attached to message
https://www.postgresql.org/message-

id/000401d94bc8%2448dff700%24da9fe5

00%24%40pcorp.us (md5sum a7d45a32c054919d94cd4a26d7d07c20) onto
current tip of the master branch being
128dd9f9eca0b633b51ffcd5b0f798fbc48ec4c0

The review written in https://www.postgresql.org/message-

id/20230228224608.ak7br5shev4wic5a%40c19 all still applies.

The `make installcheck-world` test fails for me but the failures seem

unrelated to the patch (many occurrences of "+ERROR: function
pg_input_error_info(unknown, unknown) does not exist" in the
regression.diff).

Documentation exists for the new feature

The new status of this patch is: Ready for Committer

--
Gregory Stark
As Commitfest Manager

Just sent a note about the wildcard one. Was this conflicting with the wildcard one or some other.
I can rebase if it was conflicting with another one, if it was the wildcard one, then maybe we should commit this one and we'll rebase the wildcard one.

We would like to submit the wildcard one too, but I think Tom had some reservations on that one.

Thanks,
Regina

#23Regina Obe
lr@pcorp.us
In reply to: Gregory Stark (as CFM) (#21)
1 attachment(s)
RE: Ability to reference other extensions by schema in extension scripts

It looks like this patch needs a quick rebase, there's a conflict in the
meson.build.

I'll leave the state since presumably this would be easy to resolve but it would
be more likely to get attention if it's actually building cleanly.

http://cfbot.cputube.org/patch_42_4023.log
--
Gregory Stark
As Commitfest Manager

Attach is the patch rebased against master.

Attachments:

0005-Allow-use-of-extschema-reqextname-to-reference.patchapplication/octet-stream; name=0005-Allow-use-of-extschema-reqextname-to-reference.patchDownload
From dce6168357e9ed410f94deb66b552129884195ba Mon Sep 17 00:00:00 2001
From: Regina Obe <lr@pcorp.us>
Date: Sun, 5 Feb 2023 01:35:29 -0500
Subject: [PATCH 5/5] Allow @extschema:<extname>@ in extension scripts

This feature allows an extension author to reference
extension schemas of extensions in the requires variable
of the control file using variable syntax
@extschema:<extname>@  where <extname> is the
name of any of the listed required extensions.

This feature is needed particularly in cases
when another extension packages a function
which references an object from one of its
required extensions and this function is used to back
indexes, constraints, and materialized views.

Without this feature, because pg_restore removes the paths,
these indexes, constraints, materialized views are not restored
because when the function is called, the function call fails
because items it depends on cannot be schema qualified.

This feature will allow extension authors to schema qualify
references to required extension objects to prevent this issue.
Aside from this issue, schema qualifying all these references will
improve security by preventing a rogue version of a function from being
used.

 - Extension tests both Makefile and meson.build
 - Documentation of the new feature
 - Prevent an extension from being relocated if
   another extension requires it

 Discussion: https://postgr.es/m/000801d94959%2463a9f520%242afddf60%24%40pcorp.us
---
 doc/src/sgml/extend.sgml                      | 17 +++++++
 src/backend/commands/extension.c              | 49 +++++++++++++++++++
 src/test/modules/test_extensions/Makefile     |  9 +++-
 .../expected/test_extensions.out              | 40 +++++++++++++++
 src/test/modules/test_extensions/meson.build  |  7 +++
 .../test_extensions/sql/test_extensions.sql   | 14 ++++++
 .../test_ext_req_schema1--1.0.sql             |  6 +++
 .../test_ext_req_schema1.control              |  3 ++
 .../test_ext_req_schema2--1.0--2.0.sql        |  7 +++
 .../test_ext_req_schema2--1.0.sql             |  9 ++++
 .../test_ext_req_schema2.control              |  4 ++
 .../test_ext_req_schema3--1.0.sql             | 13 +++++
 .../test_ext_req_schema3.control              |  4 ++
 13 files changed, 180 insertions(+), 2 deletions(-)
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3.control

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index b70cbe83ae..6c93bddb36 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -908,6 +908,23 @@ RETURNS anycompatible AS ...
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+       An extension might depend on other extensions.
+       It is useful to schema qualify calls to dependent extension to minimize reliance on search_path.
+       This is critical for cases such as functions used in indexes, materialized views, or check constraints.
+       To reference a required extension's schema, you must first have <literal>requires</literal>
+       variable specifying the list of extensions your extension requires.
+       In your extension sql scripts,
+       you can reference a required extension's schema with syntax
+       <literal>@extschema:reqextname@</literal> where <literal>reqextname</literal>
+       is the name of an extension in your <literal>requires</literal> list.
+       All occurrences of this string will be
+       replaced by the schema the required extension is installed in before the script is
+       executed.
+      </para>
+     </listitem>
+
      <listitem>
       <para>
        If the extension does not support relocation at all, set
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index b1509cc505..17ebb1a4fc 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1030,6 +1030,41 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 											CStringGetTextDatum(qSchemaName));
 		}
 
+		/*
+		* If this extension requires other extensions
+		* Check each required extension to see if its schema
+		* is referenced by @extschema:reqextname@ syntax
+		*/
+		foreach(lc, control->requires)
+		{
+			char		*curreq = (char *) lfirst(lc);
+			Oid			reqext;
+			Oid			reqschema;
+			StringInfoData rToken;
+			char		*reqname;
+			reqext = get_required_extension(curreq,
+											control->name,
+											(char *) schemaName,
+											false,
+											NIL,
+											false);
+			reqschema = get_extension_schema(reqext);
+			reqname = get_namespace_name(reqschema);
+			initStringInfo(&rToken);
+			appendStringInfo(&rToken, "%s%s%s", "@extschema:", curreq, "@");
+
+			/*
+			* If the required extension's schema is referenced
+			* by variable name,
+			* Replace each occurence of @extschema:<reqextname>@
+			* with the required extension's schema
+			*/
+			t_sql = DirectFunctionCall3Coll(replace_text,
+								C_COLLATION_OID,
+								t_sql,
+								CStringGetTextDatum(rToken.data),
+								CStringGetTextDatum(quote_identifier(reqname)));
+		}
 		/*
 		 * If module_pathname was set in the control file, substitute its
 		 * value for occurrences of MODULE_PATHNAME.
@@ -2816,6 +2851,20 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 		ObjectAddress dep;
 		Oid			dep_oldNspOid;
 
+		/* If an extension requires this extension
+		 * do not allow relocation */
+		if (pg_depend->deptype == DEPENDENCY_NORMAL && pg_depend->classid == ExtensionRelationId){
+			dep.classId = pg_depend->classid;
+			dep.objectId = pg_depend->objid;
+			dep.objectSubId = pg_depend->objsubid;
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot SET SCHEMA of extension %s because other extensions require it",
+							NameStr(extForm->extname)),
+					 errdetail("%s requires extension %s",
+							   getObjectDescription(&dep, false), NameStr(extForm->extname))));
+
+		}
 		/*
 		 * Ignore non-membership dependencies.  (Currently, the only other
 		 * case we could see here is a normal dependency from another
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
index c3139ab0fc..c073df963c 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -6,14 +6,19 @@ PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
 EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
             test_ext7 test_ext8 test_ext_cine test_ext_cor \
             test_ext_cyclic1 test_ext_cyclic2 \
-            test_ext_evttrig
+            test_ext_evttrig \
+            test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3
+
 DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
        test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \
        test_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \
        test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \
        test_ext_cor--1.0.sql \
        test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
-       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql
+       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql \
+       test_ext_req_schema1--1.0.sql \
+       test_ext_req_schema2--1.0.sql test_ext_req_schema2--1.0--2.0.sql \
+       test_ext_req_schema3--1.0.sql
 
 REGRESS = test_extensions test_extdepend
 
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index 821fed38d1..21dfd05982 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -312,3 +312,43 @@ Objects in extension "test_ext_cine"
  table ext_cine_tab3
 (9 rows)
 
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+NOTICE:  installing required extension "test_ext_req_schema2"
+SELECT dep_req();
+ dep_req 
+---------
+ 1032w
+(1 row)
+
+SELECT dep_req2();
+ dep_req2 
+----------
+ 1032w
+(1 row)
+
+SELECT dep_req3();
+ dep_req3 
+----------
+ 2032w
+(1 row)
+
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+ERROR:  cannot SET SCHEMA of extension test_ext_req_schema1 because other extensions require it
+DETAIL:  extension test_ext_req_schema3 requires extension test_ext_req_schema1
+SELECT dep_req();
+ dep_req 
+---------
+ 1update
+(1 row)
+
+DROP EXTENSION test_ext_req_schema1;
+ERROR:  cannot drop extension test_ext_req_schema1 because other objects depend on it
+DETAIL:  extension test_ext_req_schema2 depends on extension test_ext_req_schema1
+extension test_ext_req_schema3 depends on extension test_ext_req_schema1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP EXTENSION test_ext_req_schema3;
+DROP EXTENSION test_ext_req_schema2;
diff --git a/src/test/modules/test_extensions/meson.build b/src/test/modules/test_extensions/meson.build
index c3af3e1721..7f47a6fc23 100644
--- a/src/test/modules/test_extensions/meson.build
+++ b/src/test/modules/test_extensions/meson.build
@@ -30,6 +30,13 @@ test_install_data += files(
   'test_ext_evttrig--1.0--2.0.sql',
   'test_ext_evttrig--1.0.sql',
   'test_ext_evttrig.control',
+  'test_ext_req_schema1--1.0.sql',
+  'test_ext_req_schema1.control',
+  'test_ext_req_schema2--1.0.sql',
+  'test_ext_req_schema2.control',
+  'test_ext_req_schema2--1.0--2.0.sql',
+  'test_ext_req_schema3.control',
+  'test_ext_req_schema3--1.0.sql',
 )
 
 tests += {
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
index 41b6cddf0b..81ecd13736 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -209,3 +209,17 @@ CREATE EXTENSION test_ext_cine;
 ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
 
 \dx+ test_ext_cine
+
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+SELECT dep_req();
+SELECT dep_req2();
+SELECT dep_req3();
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+SELECT dep_req();
+DROP EXTENSION test_ext_req_schema1;
+DROP EXTENSION test_ext_req_schema3;
+DROP EXTENSION test_ext_req_schema2;
\ No newline at end of file
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
new file mode 100644
index 0000000000..462fb52145
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
@@ -0,0 +1,6 @@
+/* src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema1" to load this file. \quit
+
+CREATE DOMAIN req AS text
+  CONSTRAINT starts_with_1 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '1');
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1.control b/src/test/modules/test_extensions/test_ext_req_schema1.control
new file mode 100644
index 0000000000..9ea4558a90
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1.control
@@ -0,0 +1,3 @@
+comment = 'Create required extension to be referenced'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
new file mode 100644
index 0000000000..73a44a25e5
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
@@ -0,0 +1,7 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+
+CREATE OR REPLACE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1update''::@extschema:test_ext_req_schema1@.req';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
new file mode 100644
index 0000000000..9fe25d48a1
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN dreq AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2.control b/src/test/modules/test_extensions/test_ext_req_schema2.control
new file mode 100644
index 0000000000..d2ba5add97
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1'
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
new file mode 100644
index 0000000000..bd05669097
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN req2 AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req2() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
+
+CREATE FUNCTION dep_req3() RETURNS @extschema:test_ext_req_schema2@.dreq
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''2032w''::@extschema:test_ext_req_schema2@.dreq';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3.control b/src/test/modules/test_extensions/test_ext_req_schema3.control
new file mode 100644
index 0000000000..b052fad785
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of 2 required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1,test_ext_req_schema2'
-- 
2.21.0.windows.1

#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: Regina Obe (#23)
Re: Ability to reference other extensions by schema in extension scripts

"Regina Obe" <lr@pcorp.us> writes:

[ 0005-Allow-use-of-extschema-reqextname-to-reference.patch ]

I took a look at this. I'm on board with the feature design,
but not so much with this undocumented restriction you added
to ALTER EXTENSION SET SCHEMA:

+		/* If an extension requires this extension
+		 * do not allow relocation */
+		if (pg_depend->deptype == DEPENDENCY_NORMAL && pg_depend->classid == ExtensionRelationId){
+			dep.classId = pg_depend->classid;
+			dep.objectId = pg_depend->objid;
+			dep.objectSubId = pg_depend->objsubid;
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot SET SCHEMA of extension %s because other extensions require it",
+							NameStr(extForm->extname)),
+					 errdetail("%s requires extension %s",
+							   getObjectDescription(&dep, false), NameStr(extForm->extname))));

That seems quite disastrous for usability, and it's making an assumption
unsupported by any evidence: that it will be a majority use-case for
dependent extensions to have used @extschema:myextension@ in a way that
would be broken by ALTER EXTENSION SET SCHEMA.

I think we should just drop this. It might be worth putting in some
documentation notes about the hazard, instead.

If you want to work harder, perhaps a reasonable way to deal with
the issue would be to allow dependent extensions to declare that
they don't want your extension relocated. But I do not think it's
okay to make that the default behavior, much less the only behavior.
And really, since we've gotten along without it so far, I'm not
sure that it's necessary to have it.

Another thing that's bothering me a bit is the use of
get_required_extension in execute_extension_script. That does way
more than you really need, and passing a bunch of bogus parameter
values to it makes me uncomfortable. The callers already have
the required extensions' OIDs at hand; it'd be better to add that list
to execute_extension_script's API instead of redoing the lookups.

regards, tom lane

#25Regina Obe
lr@pcorp.us
In reply to: Tom Lane (#24)
RE: Ability to reference other extensions by schema in extension scripts

-----Original Message-----
From: Tom Lane [mailto:tgl@sss.pgh.pa.us]
Sent: Friday, March 10, 2023 3:07 PM
To: Regina Obe <lr@pcorp.us>
Cc: 'Gregory Stark (as CFM)' <stark.cfm@gmail.com>; 'Sandro Santilli'
<strk@kbt.io>; pgsql-hackers@lists.postgresql.org; 'Regina Obe'
<r@pcorp.us>
Subject: Re: Ability to reference other extensions by schema in extension
scripts

"Regina Obe" <lr@pcorp.us> writes:

[ 0005-Allow-use-of-extschema-reqextname-to-reference.patch ]

I took a look at this. I'm on board with the feature design, but not so

much

with this undocumented restriction you added to ALTER EXTENSION SET
SCHEMA:

+		/* If an extension requires this extension
+		 * do not allow relocation */
+		if (pg_depend->deptype == DEPENDENCY_NORMAL &&
pg_depend->classid == ExtensionRelationId){
+			dep.classId = pg_depend->classid;
+			dep.objectId = pg_depend->objid;
+			dep.objectSubId = pg_depend->objsubid;
+			ereport(ERROR,
+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot SET SCHEMA of
extension %s because other extensions require it",
+							NameStr(extForm-

extname)),

+ errdetail("%s requires extension

%s",

+
getObjectDescription(&dep, false),
+NameStr(extForm->extname))));

That seems quite disastrous for usability, and it's making an assumption
unsupported by any evidence: that it will be a majority use-case for
dependent extensions to have used @extschema:myextension@ in a way that
would be broken by ALTER EXTENSION SET SCHEMA.

I think we should just drop this. It might be worth putting in some
documentation notes about the hazard, instead.

If you want to work harder, perhaps a reasonable way to deal with the

issue

would be to allow dependent extensions to declare that they don't want

your

extension relocated. But I do not think it's okay to make that the

default

behavior, much less the only behavior.
And really, since we've gotten along without it so far, I'm not sure that

it's

necessary to have it.

Another thing that's bothering me a bit is the use of

get_required_extension

in execute_extension_script. That does way more than you really need, and
passing a bunch of bogus parameter values to it makes me uncomfortable.
The callers already have the required extensions' OIDs at hand; it'd be

better

Show quoted text

to add that list to execute_extension_script's API instead of redoing the
lookups.

regards, tom lane

#26Regina Obe
lr@pcorp.us
In reply to: Tom Lane (#24)
RE: Ability to reference other extensions by schema in extension scripts

"Regina Obe" <lr@pcorp.us> writes:

[ 0005-Allow-use-of-extschema-reqextname-to-reference.patch ]

I took a look at this. I'm on board with the feature design, but not so

much

with this undocumented restriction you added to ALTER EXTENSION SET
SCHEMA:

+		/* If an extension requires this extension
+		 * do not allow relocation */
+		if (pg_depend->deptype == DEPENDENCY_NORMAL &&
pg_depend->classid == ExtensionRelationId){
+			dep.classId = pg_depend->classid;
+			dep.objectId = pg_depend->objid;
+			dep.objectSubId = pg_depend->objsubid;
+			ereport(ERROR,
+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot SET SCHEMA of
extension %s because other extensions require it",
+							NameStr(extForm-

extname)),

+ errdetail("%s requires extension

%s",

+
getObjectDescription(&dep, false),
+NameStr(extForm->extname))));

That seems quite disastrous for usability, and it's making an assumption
unsupported by any evidence: that it will be a majority use-case for
dependent extensions to have used @extschema:myextension@ in a way that
would be broken by ALTER EXTENSION SET SCHEMA.

I think we should just drop this. It might be worth putting in some
documentation notes about the hazard, instead.

That was my thought originally too and also given the rarity of people
changing schemas
I wasn't that bothered with not forcing this. Sandro was a bit more
bothered by not forcing it and given the default for extensions is not
relocatable, we didn't see that much of an issue with it.

If you want to work harder, perhaps a reasonable way to deal with the

issue

would be to allow dependent extensions to declare that they don't want

your

extension relocated. But I do not think it's okay to make that the

default

behavior, much less the only behavior.

I had done that in one iteration of the patch.
We discussed this here
/messages/by-id/000001d949ad$1159adc0$340d0940$%25
40pcorp.us

and here
/messages/by-id/20230223183906.6rhtybwdpe37sri7@c19

- the main issue I ran into is I have to introduce another dependency type
or go with Sandro's idea of using refsubobjid for this purpose. I think
defining a new dependency type is less likely to cause unforeseen
complications elsewhere, but did require me to expand the scope (to make
changes to pg_depend). Which I am fine with doing, but didn't want to over
extend my reach too much.

One of my revisions tried to use DEPENDENCY_AUTO which did not work (as
Sandro discovered) and I had some other annoyances with lack of helper
functions
/messages/by-id/000401d93a14$8647f540$92d7dfc0$%25
40pcorp.us

key point:
"Why isn't there a variant getAutoExtensionsOfObject take a DEPENDENCY type
as an option so it would be more useful or is there functionality for that I
missed?"

And really, since we've gotten along without it so far, I'm not sure that

it's

necessary to have it.

Another thing that's bothering me a bit is the use of

get_required_extension

in execute_extension_script. That does way more than you really need, and
passing a bunch of bogus parameter values to it makes me uncomfortable.
The callers already have the required extensions' OIDs at hand; it'd be

better

to add that list to execute_extension_script's API instead of redoing the
lookups.

regards, tom lane

So you are proposing I change the execute_extension_scripts input args to
take more args?

#27Tom Lane
tgl@sss.pgh.pa.us
In reply to: Regina Obe (#26)
Re: Ability to reference other extensions by schema in extension scripts

"Regina Obe" <lr@pcorp.us> writes:

If you want to work harder, perhaps a reasonable way to deal with the issue
would be to allow dependent extensions to declare that they don't want your
extension relocated. But I do not think it's okay to make that the default
behavior, much less the only behavior.

- the main issue I ran into is I have to introduce another dependency type
or go with Sandro's idea of using refsubobjid for this purpose.

No, pg_depend is not the thing to use for this. I was thinking of a new
field in the extension's control file, right beside where it says it's
dependent on such-and-such extensions in the first place. Say like

requires = 'extfoo, extbar'
no_relocate = 'extfoo'

So you are proposing I change the execute_extension_scripts input args to
take more args?

Why not? It's local to that file, so you won't break anything.

regards, tom lane

#28Regina Obe
lr@pcorp.us
In reply to: Tom Lane (#27)
RE: Ability to reference other extensions by schema in extension scripts

No, pg_depend is not the thing to use for this. I was thinking of a new

field in

the extension's control file, right beside where it says it's dependent on

such-

and-such extensions in the first place. Say like

requires = 'extfoo, extbar'
no_relocate = 'extfoo'

So when no_relocate is specified, where would that live?

Would I mark the extfoo as not relocatable on CREATE / ALTER of said
extension?
Or add an extra field to pg_extension

I had tried to do that originally, e.g. instead of even bothering with such
an extra arg, just mark it as not relocatable if the extension's script
contains references to the required extension's schema.

But then what if extfoo is upgraded?

ALTER EXTENSION extfoo UPDATE;

Wipes out the not relocatable of extfoo set.
So in order to prevent that, I have to

a) check the control files of all extensions that depend on foo to see if
they made such a request.
or
b) "Seeing if the extension is marked as not relocatable, prevent ALTER
EXTENSION from marking it as relocatable"
problem with b is what if the extension author changed their mind and wanted
it to be relocatable? Given the default is (not relocatable), it's possible
the author didn't know this and later decided to put in an explicit
relocate=false.
c) define a new column in pg_extension to hold this bit of info. I was
hoping I could reuse pg_extension.extconfig, but it seems that's hardwired
to be only used for backup.

Am I missing something or is this really as complicated as I think it is?

If we go with b) I'm not sure why I need to bother defining a no_relocate,
as it's obvious looking at the extension install/upgrade script that it
should not be relocatable.

So you are proposing I change the execute_extension_scripts input args
to take more args?

Why not? It's local to that file, so you won't break anything.

Okay, I wasn't absolutely sure if it was. If it is then I'll change.

regards, tom lane

Thanks,
Regina

#29Tom Lane
tgl@sss.pgh.pa.us
In reply to: Regina Obe (#28)
Re: Ability to reference other extensions by schema in extension scripts

"Regina Obe" <lr@pcorp.us> writes:

requires = 'extfoo, extbar'
no_relocate = 'extfoo'

So when no_relocate is specified, where would that live?

In the control file.

Would I mark the extfoo as not relocatable on CREATE / ALTER of said
extension?
Or add an extra field to pg_extension

We don't record dependent extensions in pg_extension now, so that
doesn't seem like it would fit well. I was envisioning that
ALTER EXTENSION SET SCHEMA would do something along the lines of

(1) scrape the list of dependent extensions out of pg_depend
(2) open and parse each of their control files
(3) fail if any of their control files mentions the target one in
no_relocate.

Admittedly, this'd be a bit slow, but I doubt that ALTER EXTENSION
SET SCHEMA is a performance bottleneck for anybody.

I had tried to do that originally, e.g. instead of even bothering with such
an extra arg, just mark it as not relocatable if the extension's script
contains references to the required extension's schema.

I don't think that's a great approach, because those references might
appear in places that can track a rename (ie, in an object name that's
resolved to a stored OID). Short of fully parsing the script file you
aren't going to get a reliable answer. I'm content to lay that problem
off on the extension authors.

But then what if extfoo is upgraded?

We already have mechanisms for version-dependent control files, so
I don't see where there's a problem.

regards, tom lane

#30Regina Obe
lr@pcorp.us
In reply to: Tom Lane (#29)
RE: Ability to reference other extensions by schema in extension scripts

"Regina Obe" <lr@pcorp.us> writes:

requires = 'extfoo, extbar'
no_relocate = 'extfoo'

So when no_relocate is specified, where would that live?

In the control file.

Would I mark the extfoo as not relocatable on CREATE / ALTER of said
extension?
Or add an extra field to pg_extension

We don't record dependent extensions in pg_extension now, so that doesn't
seem like it would fit well. I was envisioning that ALTER EXTENSION SET
SCHEMA would do something along the lines of

(1) scrape the list of dependent extensions out of pg_depend
(2) open and parse each of their control files
(3) fail if any of their control files mentions the target one in
no_relocate.

Admittedly, this'd be a bit slow, but I doubt that ALTER EXTENSION SET
SCHEMA is a performance bottleneck for anybody.

Okay I'll move ahead with this approach.

Thanks,
Regina

#31Regina Obe
lr@pcorp.us
In reply to: Tom Lane (#29)
1 attachment(s)
RE: Ability to reference other extensions by schema in extension scripts

Subject: Re: Ability to reference other extensions by schema in extension
scripts

"Regina Obe" <lr@pcorp.us> writes:

requires = 'extfoo, extbar'
no_relocate = 'extfoo'

So when no_relocate is specified, where would that live?

In the control file.

Would I mark the extfoo as not relocatable on CREATE / ALTER of said
extension?
Or add an extra field to pg_extension

We don't record dependent extensions in pg_extension now, so that doesn't
seem like it would fit well. I was envisioning that ALTER EXTENSION SET
SCHEMA would do something along the lines of

(1) scrape the list of dependent extensions out of pg_depend
(2) open and parse each of their control files
(3) fail if any of their control files mentions the target one in
no_relocate.

Admittedly, this'd be a bit slow, but I doubt that ALTER EXTENSION SET
SCHEMA is a performance bottleneck for anybody.

I had tried to do that originally, e.g. instead of even bothering with
such an extra arg, just mark it as not relocatable if the extension's
script contains references to the required extension's schema.

I don't think that's a great approach, because those references might

appear

in places that can track a rename (ie, in an object name that's resolved

to a

stored OID). Short of fully parsing the script file you aren't going to

get a

reliable answer. I'm content to lay that problem off on the extension

authors.

But then what if extfoo is upgraded?

We already have mechanisms for version-dependent control files, so I don't
see where there's a problem.

regards, tom lane

Attached is a revised patch with these changes in place.

Attachments:

0006-Allow-use-of-extschema-reqextname-to-reference.patchapplication/octet-stream; name=0006-Allow-use-of-extschema-reqextname-to-reference.patchDownload
From 32430fcec8794a0c10305bde45637d13828f0559 Mon Sep 17 00:00:00 2001
From: Regina Obe <lr@pcorp.us>
Date: Sun, 5 Feb 2023 01:35:29 -0500
Subject: [PATCH 6/6] Allow @extschema:<extname>@ in extension scripts

This feature allows an extension author to reference
extension schemas of extensions in the requires variable
of the control file using variable syntax
@extschema:<extname>@  where <extname> is the
name of any of the listed required extensions.

It also defines another optional config parameter
in the control file called no_relocate
which consists of a subset of the requires extensions
that should have ALTER EXTENSION SET SCHEMA prevented.

This feature is needed particularly in cases
when another extension packages a function
which references an object from one of its
required extensions and this function is used to back
indexes, constraints, and materialized views.

Without this feature, because pg_restore removes the paths,
these indexes, constraints, materialized views are not restored
because when the function is called, the function call fails
because items it depends on cannot be schema qualified.

This feature will allow extension authors to schema qualify
references to required extension objects to prevent this issue.
Aside from this issue, schema qualifying all these references will
improve security by preventing a rogue version of a function from being
used.

 - Extension tests both Makefile and meson.build
 - Documentation of the new feature
 - Define a new extension control parameter no_relocate
   Which is a comma separated list of required extensions
   that should be prevented from ALTER .. SET SCHEMA
 - Prevent an extension from being relocated if
   another extension requires it and has it
   listed in it's no_relocate list
 - Change execute_extension_script to take as input
   required extension oids,
   since calling functions already build this list

 Discussion: https://postgr.es/m/000801d94959%2463a9f520%242afddf60%24%40pcorp.us
---
 doc/src/sgml/extend.sgml                      | 35 +++++++
 src/backend/commands/extension.c              | 98 ++++++++++++++++++-
 src/test/modules/test_extensions/Makefile     |  9 +-
 .../expected/test_extensions.out              | 41 ++++++++
 src/test/modules/test_extensions/meson.build  |  7 ++
 .../test_extensions/sql/test_extensions.sql   | 15 +++
 .../test_ext_req_schema1--1.0.sql             |  6 ++
 .../test_ext_req_schema1.control              |  3 +
 .../test_ext_req_schema2--1.0--2.0.sql        |  7 ++
 .../test_ext_req_schema2--1.0.sql             |  9 ++
 .../test_ext_req_schema2.control              |  4 +
 .../test_ext_req_schema3--1.0.sql             | 13 +++
 .../test_ext_req_schema3.control              |  5 +
 13 files changed, 245 insertions(+), 7 deletions(-)
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3.control

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index b70cbe83ae..da54c7feca 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -739,6 +739,22 @@ RETURNS anycompatible AS ...
       </listitem>
      </varlistentry>
 
+     <varlistentry id="extend-extensions-files-no-relocate">
+      <term><varname>no_relocate</varname> (<type>string</type>)</term>
+      <listitem>
+       <para>
+        A list of names of extensions that this extension depends on,
+        and that should be barred from SET SCHEMA.
+        This is needed if an extension script references
+        a required extensions schema by variable name
+        and the object references are in places that can't track renames.
+        for example <literal>no_relocate = 'foo, bar'</literal>.
+        This setting is checked at ALTER EXTENSION .. SET SCHEMA 
+        time of a required extension.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="extend-extensions-files-superuser">
       <term><varname>superuser</varname> (<type>boolean</type>)</term>
       <listitem>
@@ -908,6 +924,25 @@ RETURNS anycompatible AS ...
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+       An extension might depend on other extensions.
+       It is useful to schema qualify calls to dependent extension to minimize reliance on search_path.
+       This is critical for cases such as functions used in indexes, materialized views, or check constraints.
+       To reference a required extension's schema, you must first have <literal>requires</literal>
+       variable specifying the list of extensions your extension requires.
+       In your extension sql scripts,
+       you can reference a required extension's schema with syntax
+       <literal>@extschema:reqextname@</literal> where <literal>reqextname</literal>
+       is the name of an extension in your <literal>requires</literal> list.
+       All occurrences of this string will be
+       replaced by the schema the required extension is installed in before the script is
+       executed.
+      </para>
+      <para>For extensions that you reference by this syntax, make sure to add them to the
+       <literal>no_relocate</literal> list as well.</para>
+     </listitem>
+
      <listitem>
       <para>
        If the extension does not support relocation at all, set
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 02ff4a9a7f..71683c547c 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -90,6 +90,8 @@ typedef struct ExtensionControlFile
 	bool		trusted;		/* allow becoming superuser on the fly? */
 	int			encoding;		/* encoding of the script file, or -1 */
 	List	   *requires;		/* names of prerequisite extensions */
+	List	   *no_relocate;	/* names of extensions that should be marked as not relocatable
+									these should be a subset of the requires */
 } ExtensionControlFile;
 
 /*
@@ -606,6 +608,21 @@ parse_extension_control_file(ExtensionControlFile *control,
 								item->name)));
 			}
 		}
+		else if (strcmp(item->name, "no_relocate") == 0)
+		{
+			/* Need a modifiable copy of string */
+			char	   *rawnames = pstrdup(item->value);
+
+			/* Parse string into list of identifiers */
+			if (!SplitIdentifierString(rawnames, ',', &control->no_relocate))
+			{
+				/* syntax error in name list */
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"%s\" must be a list of extension names",
+								item->name)));
+			}
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
@@ -851,7 +868,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 						 const char *from_version,
 						 const char *version,
 						 List *requiredSchemas,
-						 const char *schemaName, Oid schemaOid)
+						 const char *schemaName, Oid schemaOid, List *requiredExtensions)
 {
 	bool		switch_to_superuser = false;
 	char	   *filename;
@@ -1030,6 +1047,34 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 											CStringGetTextDatum(qSchemaName));
 		}
 
+		/*
+		* If this extension requires other extensions
+		* Check each required extension to see if its schema
+		* is referenced by @extschema:reqextname@ syntax
+		*/
+		foreach(lc, requiredExtensions)
+		{
+			Oid				reqext = lfirst_oid(lc);
+			Oid				reqschema;
+			StringInfoData	rToken;
+			char			*reqname;
+			reqschema = get_extension_schema(reqext);
+			reqname = get_namespace_name(reqschema);
+			initStringInfo(&rToken);
+			appendStringInfo(&rToken, "%s%s%s", "@extschema:", get_extension_name(reqext), "@");
+
+			/*
+			* If the required extension's schema is referenced
+			* by variable name,
+			* Replace each occurence of @extschema:<reqextname>@
+			* with the required extension's schema
+			*/
+			t_sql = DirectFunctionCall3Coll(replace_text,
+								C_COLLATION_OID,
+								t_sql,
+								CStringGetTextDatum(rToken.data),
+								CStringGetTextDatum(quote_identifier(reqname)));
+		}
 		/*
 		 * If module_pathname was set in the control file, substitute its
 		 * value for occurrences of MODULE_PATHNAME.
@@ -1613,7 +1658,7 @@ CreateExtensionInternal(char *extensionName,
 	execute_extension_script(extensionOid, control,
 							 NULL, versionName,
 							 requiredSchemas,
-							 schemaName, schemaOid);
+							 schemaName, schemaOid, requiredExtensions);
 
 	/*
 	 * If additional update scripts have to be executed, apply the updates as
@@ -2094,8 +2139,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 	{
 		ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
 		ExtensionControlFile *control;
-		Datum		values[8];
-		bool		nulls[8];
+		Datum		values[9];
+		bool		nulls[9];
 		ListCell   *lc2;
 
 		if (!evi->installable)
@@ -2120,6 +2165,7 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 		values[3] = BoolGetDatum(control->trusted);
 		/* relocatable */
 		values[4] = BoolGetDatum(control->relocatable);
+
 		/* schema */
 		if (control->schema == NULL)
 			nulls[5] = true;
@@ -2131,12 +2177,19 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 			nulls[6] = true;
 		else
 			values[6] = convert_requires_to_datum(control->requires);
+
 		/* comment */
 		if (control->comment == NULL)
 			nulls[7] = true;
 		else
 			values[7] = CStringGetTextDatum(control->comment);
 
+		/* no_relocate */
+		if (control->no_relocate == NIL)
+			nulls[8] = true;
+		else
+			values[8] = convert_requires_to_datum(control->no_relocate);
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 
 		/*
@@ -2177,6 +2230,14 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 					nulls[6] = false;
 				}
 				/* comment stays the same */
+				/* no_relocate */
+				if (control->no_relocate == NIL)
+					nulls[8] = true;
+				else
+				{
+					values[8] = convert_requires_to_datum(control->no_relocate);
+					nulls[8] = false;
+				}
 
 				tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 			}
@@ -2816,6 +2877,33 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 		ObjectAddress dep;
 		Oid			dep_oldNspOid;
 
+		/* If an extension requires this extension
+		 * and the extension has this extension marked as no_relocate,
+		   then prevent set schema */
+		if (pg_depend->deptype == DEPENDENCY_NORMAL && pg_depend->classid == ExtensionRelationId){
+			ExtensionControlFile *dcontrol;
+			dep.classId = pg_depend->classid;
+			dep.objectId = pg_depend->objid;
+			dep.objectSubId = pg_depend->objsubid;
+			/** Check if dependent extension has a no_relocate request for this extension **/
+			dcontrol = read_extension_control_file(get_extension_name(dep.objectId));
+			if (dcontrol->no_relocate) {
+				ListCell   *lc;
+				foreach(lc, dcontrol->no_relocate)
+				{
+					char	*curreq = (char *) lfirst(lc);
+					if (strcmp(curreq, NameStr(extForm->extname)) == 0 ){
+						/** This dependent extension, has a no_relocate hold for this extension **/
+						ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							errmsg("cannot SET SCHEMA of extension %s because other extensions prevent it",
+									NameStr(extForm->extname)),
+							errdetail("%s prevents relocation of extension %s",
+									getObjectDescription(&dep, false), NameStr(extForm->extname))));
+					}
+				}
+			}
+		}
 		/*
 		 * Ignore non-membership dependencies.  (Currently, the only other
 		 * case we could see here is a normal dependency from another
@@ -3172,7 +3260,7 @@ ApplyExtensionUpdates(Oid extensionOid,
 		execute_extension_script(extensionOid, control,
 								 oldVersionName, versionName,
 								 requiredSchemas,
-								 schemaName, schemaOid);
+								 schemaName, schemaOid, requiredExtensions);
 
 		/*
 		 * Update prior-version name and loop around.  Since
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
index c3139ab0fc..c073df963c 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -6,14 +6,19 @@ PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
 EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
             test_ext7 test_ext8 test_ext_cine test_ext_cor \
             test_ext_cyclic1 test_ext_cyclic2 \
-            test_ext_evttrig
+            test_ext_evttrig \
+            test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3
+
 DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
        test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \
        test_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \
        test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \
        test_ext_cor--1.0.sql \
        test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
-       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql
+       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql \
+       test_ext_req_schema1--1.0.sql \
+       test_ext_req_schema2--1.0.sql test_ext_req_schema2--1.0--2.0.sql \
+       test_ext_req_schema3--1.0.sql
 
 REGRESS = test_extensions test_extdepend
 
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index 821fed38d1..7582d2d2aa 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -312,3 +312,44 @@ Objects in extension "test_ext_cine"
  table ext_cine_tab3
 (9 rows)
 
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+NOTICE:  installing required extension "test_ext_req_schema2"
+SELECT dep_req();
+ dep_req 
+---------
+ 1032w
+(1 row)
+
+SELECT dep_req2();
+ dep_req2 
+----------
+ 1032w
+(1 row)
+
+SELECT dep_req3();
+ dep_req3 
+----------
+ 2032w
+(1 row)
+
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+ERROR:  cannot SET SCHEMA of extension test_ext_req_schema1 because other extensions prevent it
+DETAIL:  extension test_ext_req_schema3 prevents relocation of extension test_ext_req_schema1
+SELECT dep_req();
+ dep_req 
+---------
+ 1update
+(1 row)
+
+ALTER EXTENSION test_ext_req_schema2 SET SCHEMA test_s_dep;
+DROP EXTENSION test_ext_req_schema1;
+ERROR:  cannot drop extension test_ext_req_schema1 because other objects depend on it
+DETAIL:  extension test_ext_req_schema2 depends on extension test_ext_req_schema1
+extension test_ext_req_schema3 depends on extension test_ext_req_schema1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP EXTENSION test_ext_req_schema3;
+DROP EXTENSION test_ext_req_schema2;
diff --git a/src/test/modules/test_extensions/meson.build b/src/test/modules/test_extensions/meson.build
index c3af3e1721..7f47a6fc23 100644
--- a/src/test/modules/test_extensions/meson.build
+++ b/src/test/modules/test_extensions/meson.build
@@ -30,6 +30,13 @@ test_install_data += files(
   'test_ext_evttrig--1.0--2.0.sql',
   'test_ext_evttrig--1.0.sql',
   'test_ext_evttrig.control',
+  'test_ext_req_schema1--1.0.sql',
+  'test_ext_req_schema1.control',
+  'test_ext_req_schema2--1.0.sql',
+  'test_ext_req_schema2.control',
+  'test_ext_req_schema2--1.0--2.0.sql',
+  'test_ext_req_schema3.control',
+  'test_ext_req_schema3--1.0.sql',
 )
 
 tests += {
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
index 41b6cddf0b..0f554c856b 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -209,3 +209,18 @@ CREATE EXTENSION test_ext_cine;
 ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
 
 \dx+ test_ext_cine
+
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+SELECT dep_req();
+SELECT dep_req2();
+SELECT dep_req3();
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+SELECT dep_req();
+ALTER EXTENSION test_ext_req_schema2 SET SCHEMA test_s_dep;
+DROP EXTENSION test_ext_req_schema1;
+DROP EXTENSION test_ext_req_schema3;
+DROP EXTENSION test_ext_req_schema2;
\ No newline at end of file
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
new file mode 100644
index 0000000000..462fb52145
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
@@ -0,0 +1,6 @@
+/* src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema1" to load this file. \quit
+
+CREATE DOMAIN req AS text
+  CONSTRAINT starts_with_1 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '1');
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1.control b/src/test/modules/test_extensions/test_ext_req_schema1.control
new file mode 100644
index 0000000000..9ea4558a90
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1.control
@@ -0,0 +1,3 @@
+comment = 'Create required extension to be referenced'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
new file mode 100644
index 0000000000..73a44a25e5
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
@@ -0,0 +1,7 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+
+CREATE OR REPLACE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1update''::@extschema:test_ext_req_schema1@.req';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
new file mode 100644
index 0000000000..b17fb82535
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN dreq AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2.control b/src/test/modules/test_extensions/test_ext_req_schema2.control
new file mode 100644
index 0000000000..d2ba5add97
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1'
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
new file mode 100644
index 0000000000..bd05669097
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN req2 AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req2() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
+
+CREATE FUNCTION dep_req3() RETURNS @extschema:test_ext_req_schema2@.dreq
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''2032w''::@extschema:test_ext_req_schema2@.dreq';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3.control b/src/test/modules/test_extensions/test_ext_req_schema3.control
new file mode 100644
index 0000000000..66c8de5e05
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3.control
@@ -0,0 +1,5 @@
+comment = 'Test schema referencing of 2 required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1,test_ext_req_schema2'
+no_relocate = 'test_ext_req_schema1'
-- 
2.21.0.windows.1

In reply to: Regina Obe (#31)
Re: Ability to reference other extensions by schema in extension scripts

On Sat, Mar 11, 2023 at 03:18:18AM -0500, Regina Obe wrote:

Attached is a revised patch with these changes in place.

I've given a try to this patch. It builds and regresses fine.

My own tests also worked fine. As long as ext1 was found
in the ext2's no_relocate list it could not be relocated,
and proper error message is given to user trying it.

Nitpicking, there are a few things that are weird to me:

1) I don't get any error/warning if I put an arbitrary
string into no_relocate (there's no check to verify the
no_relocate is a subset of the requires).

2) An extension can still reference extensions it depends on
without putting them in no_relocate. This may be intentional,
as some substitutions may not require blocking relocation, but
felt inconsistent with the normal @extschema@ which is never
replaced unless an extension is marked as non-relocatable.

--strk;

Libre GIS consultant/developer
https://strk.kbt.io/services.html

#33Regina Obe
lr@pcorp.us
In reply to: 'Sandro Santilli' (#32)
RE: Ability to reference other extensions by schema in extension scripts

I've given a try to this patch. It builds and regresses fine.

My own tests also worked fine. As long as ext1 was found in the ext2's
no_relocate list it could not be relocated, and proper error message is

given

to user trying it.

Nitpicking, there are a few things that are weird to me:

1) I don't get any error/warning if I put an arbitrary string into

no_relocate

(there's no check to verify the no_relocate is a subset of the requires).

I thought about that and decided it wasn't worth checking for. If an
extension author puts in an extension not in requires it's on them as the
docs say it should be in requires.

It will just pretend that extension is not listed in no_relocate.

2) An extension can still reference extensions it depends on without

putting

them in no_relocate. This may be intentional, as some substitutions may

not

require blocking relocation, but felt inconsistent with the normal
@extschema@ which is never replaced unless an extension is marked as non-
relocatable.

--strk;

Libre GIS consultant/developer
https://strk.kbt.io/services.html

Yes this is intentional. As Tom mentioned, if for example an extension
author decides
To schema qualify @extschema:foo@ in their table definition, and they marked
as requiring foo
since such a reference
is captured by a schema move, there is no need for them to prevent relocate
of the foo extension (assuming foo was relocatable to begin with)

#34Regina Obe
lr@pcorp.us
In reply to: 'Sandro Santilli' (#32)
1 attachment(s)
RE: Ability to reference other extensions by schema in extension scripts

On Sat, Mar 11, 2023 at 03:18:18AM -0500, Regina Obe wrote:

Attached is a revised patch with these changes in place.

I've given a try to this patch. It builds and regresses fine.

My own tests also worked fine. As long as ext1 was found in the ext2's
no_relocate list it could not be relocated, and proper error message is

given

to user trying it.

Nitpicking, there are a few things that are weird to me:

1) I don't get any error/warning if I put an arbitrary string into

no_relocate

(there's no check to verify the no_relocate is a subset of the requires).

2) An extension can still reference extensions it depends on without

putting

them in no_relocate. This may be intentional, as some substitutions may

not

require blocking relocation, but felt inconsistent with the normal
@extschema@ which is never replaced unless an extension is marked as non-
relocatable.

--strk;

Libre GIS consultant/developer
https://strk.kbt.io/services.html

Attached is a slightly revised patch to fix the extra whitespace in the
extend.gml
document that Sandro noted to me.

Thanks,
Regina

Attachments:

0007-Allow-use-of-extschema-reqextname-to-reference.patchapplication/octet-stream; name=0007-Allow-use-of-extschema-reqextname-to-reference.patchDownload
From d7b3ee51af0fbdc7e58fc43473114cd633e09c74 Mon Sep 17 00:00:00 2001
From: Regina Obe <lr@pcorp.us>
Date: Sun, 5 Feb 2023 01:35:29 -0500
Subject: [PATCH 7/7] Allow @extschema:<extname>@ in extension scripts

This feature allows an extension author to reference
extension schemas of extensions in the requires variable
of the control file using variable syntax
@extschema:<extname>@  where <extname> is the
name of any of the listed required extensions.

It also defines another optional config parameter
in the control file called no_relocate
which consists of a subset of the requires extensions
that should have ALTER EXTENSION SET SCHEMA prevented.

This feature is needed particularly in cases
when another extension packages a function
which references an object from one of its
required extensions and this function is used to back
indexes, constraints, and materialized views.

Without this feature, because pg_restore removes the paths,
these indexes, constraints, materialized views are not restored
because when the function is called, the function call fails
because items it depends on cannot be schema qualified.

This feature will allow extension authors to schema qualify
references to required extension objects to prevent this issue.
Aside from this issue, schema qualifying all these references will
improve security by preventing a rogue version of a function from being
used.

 - Extension tests both Makefile and meson.build
 - Documentation of the new feature
 - Define a new extension control parameter no_relocate
   Which is a comma separated list of required extensions
   that should be prevented from ALTER .. SET SCHEMA
 - Prevent an extension from being relocated if
   another extension requires it and has it
   listed in it's no_relocate list
 - Change execute_extension_script to take as input
   required extension oids,
   since calling functions already build this list

 Discussion: https://postgr.es/m/000801d94959%2463a9f520%242afddf60%24%40pcorp.us
---
 doc/src/sgml/extend.sgml                      | 35 +++++++
 src/backend/commands/extension.c              | 98 ++++++++++++++++++-
 src/test/modules/test_extensions/Makefile     |  9 +-
 .../expected/test_extensions.out              | 41 ++++++++
 src/test/modules/test_extensions/meson.build  |  7 ++
 .../test_extensions/sql/test_extensions.sql   | 15 +++
 .../test_ext_req_schema1--1.0.sql             |  6 ++
 .../test_ext_req_schema1.control              |  3 +
 .../test_ext_req_schema2--1.0--2.0.sql        |  7 ++
 .../test_ext_req_schema2--1.0.sql             |  9 ++
 .../test_ext_req_schema2.control              |  4 +
 .../test_ext_req_schema3--1.0.sql             | 13 +++
 .../test_ext_req_schema3.control              |  5 +
 13 files changed, 245 insertions(+), 7 deletions(-)
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema1.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema2.control
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
 create mode 100644 src/test/modules/test_extensions/test_ext_req_schema3.control

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index b70cbe83ae..554876672b 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -739,6 +739,22 @@ RETURNS anycompatible AS ...
       </listitem>
      </varlistentry>
 
+     <varlistentry id="extend-extensions-files-no-relocate">
+      <term><varname>no_relocate</varname> (<type>string</type>)</term>
+      <listitem>
+       <para>
+        A list of names of extensions that this extension depends on,
+        and that should be barred from SET SCHEMA.
+        This is needed if an extension script references
+        a required extensions schema by variable name
+        and the object references are in places that can't track renames.
+        for example <literal>no_relocate = 'foo, bar'</literal>.
+        This setting is checked at ALTER EXTENSION .. SET SCHEMA
+        time of a required extension.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="extend-extensions-files-superuser">
       <term><varname>superuser</varname> (<type>boolean</type>)</term>
       <listitem>
@@ -908,6 +924,25 @@ RETURNS anycompatible AS ...
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+       An extension might depend on other extensions.
+       It is useful to schema qualify calls to dependent extension to minimize reliance on search_path.
+       This is critical for cases such as functions used in indexes, materialized views, or check constraints.
+       To reference a required extension's schema, you must first have <literal>requires</literal>
+       variable specifying the list of extensions your extension requires.
+       In your extension sql scripts,
+       you can reference a required extension's schema with syntax
+       <literal>@extschema:reqextname@</literal> where <literal>reqextname</literal>
+       is the name of an extension in your <literal>requires</literal> list.
+       All occurrences of this string will be
+       replaced by the schema the required extension is installed in before the script is
+       executed.
+      </para>
+      <para>For extensions that you reference by this syntax, make sure to add them to the
+       <literal>no_relocate</literal> list as well.</para>
+     </listitem>
+
      <listitem>
       <para>
        If the extension does not support relocation at all, set
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 02ff4a9a7f..71683c547c 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -90,6 +90,8 @@ typedef struct ExtensionControlFile
 	bool		trusted;		/* allow becoming superuser on the fly? */
 	int			encoding;		/* encoding of the script file, or -1 */
 	List	   *requires;		/* names of prerequisite extensions */
+	List	   *no_relocate;	/* names of extensions that should be marked as not relocatable
+									these should be a subset of the requires */
 } ExtensionControlFile;
 
 /*
@@ -606,6 +608,21 @@ parse_extension_control_file(ExtensionControlFile *control,
 								item->name)));
 			}
 		}
+		else if (strcmp(item->name, "no_relocate") == 0)
+		{
+			/* Need a modifiable copy of string */
+			char	   *rawnames = pstrdup(item->value);
+
+			/* Parse string into list of identifiers */
+			if (!SplitIdentifierString(rawnames, ',', &control->no_relocate))
+			{
+				/* syntax error in name list */
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"%s\" must be a list of extension names",
+								item->name)));
+			}
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
@@ -851,7 +868,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 						 const char *from_version,
 						 const char *version,
 						 List *requiredSchemas,
-						 const char *schemaName, Oid schemaOid)
+						 const char *schemaName, Oid schemaOid, List *requiredExtensions)
 {
 	bool		switch_to_superuser = false;
 	char	   *filename;
@@ -1030,6 +1047,34 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 											CStringGetTextDatum(qSchemaName));
 		}
 
+		/*
+		* If this extension requires other extensions
+		* Check each required extension to see if its schema
+		* is referenced by @extschema:reqextname@ syntax
+		*/
+		foreach(lc, requiredExtensions)
+		{
+			Oid				reqext = lfirst_oid(lc);
+			Oid				reqschema;
+			StringInfoData	rToken;
+			char			*reqname;
+			reqschema = get_extension_schema(reqext);
+			reqname = get_namespace_name(reqschema);
+			initStringInfo(&rToken);
+			appendStringInfo(&rToken, "%s%s%s", "@extschema:", get_extension_name(reqext), "@");
+
+			/*
+			* If the required extension's schema is referenced
+			* by variable name,
+			* Replace each occurence of @extschema:<reqextname>@
+			* with the required extension's schema
+			*/
+			t_sql = DirectFunctionCall3Coll(replace_text,
+								C_COLLATION_OID,
+								t_sql,
+								CStringGetTextDatum(rToken.data),
+								CStringGetTextDatum(quote_identifier(reqname)));
+		}
 		/*
 		 * If module_pathname was set in the control file, substitute its
 		 * value for occurrences of MODULE_PATHNAME.
@@ -1613,7 +1658,7 @@ CreateExtensionInternal(char *extensionName,
 	execute_extension_script(extensionOid, control,
 							 NULL, versionName,
 							 requiredSchemas,
-							 schemaName, schemaOid);
+							 schemaName, schemaOid, requiredExtensions);
 
 	/*
 	 * If additional update scripts have to be executed, apply the updates as
@@ -2094,8 +2139,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 	{
 		ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
 		ExtensionControlFile *control;
-		Datum		values[8];
-		bool		nulls[8];
+		Datum		values[9];
+		bool		nulls[9];
 		ListCell   *lc2;
 
 		if (!evi->installable)
@@ -2120,6 +2165,7 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 		values[3] = BoolGetDatum(control->trusted);
 		/* relocatable */
 		values[4] = BoolGetDatum(control->relocatable);
+
 		/* schema */
 		if (control->schema == NULL)
 			nulls[5] = true;
@@ -2131,12 +2177,19 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 			nulls[6] = true;
 		else
 			values[6] = convert_requires_to_datum(control->requires);
+
 		/* comment */
 		if (control->comment == NULL)
 			nulls[7] = true;
 		else
 			values[7] = CStringGetTextDatum(control->comment);
 
+		/* no_relocate */
+		if (control->no_relocate == NIL)
+			nulls[8] = true;
+		else
+			values[8] = convert_requires_to_datum(control->no_relocate);
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 
 		/*
@@ -2177,6 +2230,14 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 					nulls[6] = false;
 				}
 				/* comment stays the same */
+				/* no_relocate */
+				if (control->no_relocate == NIL)
+					nulls[8] = true;
+				else
+				{
+					values[8] = convert_requires_to_datum(control->no_relocate);
+					nulls[8] = false;
+				}
 
 				tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 			}
@@ -2816,6 +2877,33 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 		ObjectAddress dep;
 		Oid			dep_oldNspOid;
 
+		/* If an extension requires this extension
+		 * and the extension has this extension marked as no_relocate,
+		   then prevent set schema */
+		if (pg_depend->deptype == DEPENDENCY_NORMAL && pg_depend->classid == ExtensionRelationId){
+			ExtensionControlFile *dcontrol;
+			dep.classId = pg_depend->classid;
+			dep.objectId = pg_depend->objid;
+			dep.objectSubId = pg_depend->objsubid;
+			/** Check if dependent extension has a no_relocate request for this extension **/
+			dcontrol = read_extension_control_file(get_extension_name(dep.objectId));
+			if (dcontrol->no_relocate) {
+				ListCell   *lc;
+				foreach(lc, dcontrol->no_relocate)
+				{
+					char	*curreq = (char *) lfirst(lc);
+					if (strcmp(curreq, NameStr(extForm->extname)) == 0 ){
+						/** This dependent extension, has a no_relocate hold for this extension **/
+						ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							errmsg("cannot SET SCHEMA of extension %s because other extensions prevent it",
+									NameStr(extForm->extname)),
+							errdetail("%s prevents relocation of extension %s",
+									getObjectDescription(&dep, false), NameStr(extForm->extname))));
+					}
+				}
+			}
+		}
 		/*
 		 * Ignore non-membership dependencies.  (Currently, the only other
 		 * case we could see here is a normal dependency from another
@@ -3172,7 +3260,7 @@ ApplyExtensionUpdates(Oid extensionOid,
 		execute_extension_script(extensionOid, control,
 								 oldVersionName, versionName,
 								 requiredSchemas,
-								 schemaName, schemaOid);
+								 schemaName, schemaOid, requiredExtensions);
 
 		/*
 		 * Update prior-version name and loop around.  Since
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
index c3139ab0fc..c073df963c 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -6,14 +6,19 @@ PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
 EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
             test_ext7 test_ext8 test_ext_cine test_ext_cor \
             test_ext_cyclic1 test_ext_cyclic2 \
-            test_ext_evttrig
+            test_ext_evttrig \
+            test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3
+
 DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
        test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \
        test_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \
        test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \
        test_ext_cor--1.0.sql \
        test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
-       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql
+       test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql \
+       test_ext_req_schema1--1.0.sql \
+       test_ext_req_schema2--1.0.sql test_ext_req_schema2--1.0--2.0.sql \
+       test_ext_req_schema3--1.0.sql
 
 REGRESS = test_extensions test_extdepend
 
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index 821fed38d1..7582d2d2aa 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -312,3 +312,44 @@ Objects in extension "test_ext_cine"
  table ext_cine_tab3
 (9 rows)
 
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+NOTICE:  installing required extension "test_ext_req_schema2"
+SELECT dep_req();
+ dep_req 
+---------
+ 1032w
+(1 row)
+
+SELECT dep_req2();
+ dep_req2 
+----------
+ 1032w
+(1 row)
+
+SELECT dep_req3();
+ dep_req3 
+----------
+ 2032w
+(1 row)
+
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+ERROR:  cannot SET SCHEMA of extension test_ext_req_schema1 because other extensions prevent it
+DETAIL:  extension test_ext_req_schema3 prevents relocation of extension test_ext_req_schema1
+SELECT dep_req();
+ dep_req 
+---------
+ 1update
+(1 row)
+
+ALTER EXTENSION test_ext_req_schema2 SET SCHEMA test_s_dep;
+DROP EXTENSION test_ext_req_schema1;
+ERROR:  cannot drop extension test_ext_req_schema1 because other objects depend on it
+DETAIL:  extension test_ext_req_schema2 depends on extension test_ext_req_schema1
+extension test_ext_req_schema3 depends on extension test_ext_req_schema1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP EXTENSION test_ext_req_schema3;
+DROP EXTENSION test_ext_req_schema2;
diff --git a/src/test/modules/test_extensions/meson.build b/src/test/modules/test_extensions/meson.build
index c3af3e1721..7f47a6fc23 100644
--- a/src/test/modules/test_extensions/meson.build
+++ b/src/test/modules/test_extensions/meson.build
@@ -30,6 +30,13 @@ test_install_data += files(
   'test_ext_evttrig--1.0--2.0.sql',
   'test_ext_evttrig--1.0.sql',
   'test_ext_evttrig.control',
+  'test_ext_req_schema1--1.0.sql',
+  'test_ext_req_schema1.control',
+  'test_ext_req_schema2--1.0.sql',
+  'test_ext_req_schema2.control',
+  'test_ext_req_schema2--1.0--2.0.sql',
+  'test_ext_req_schema3.control',
+  'test_ext_req_schema3--1.0.sql',
 )
 
 tests += {
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
index 41b6cddf0b..0f554c856b 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -209,3 +209,18 @@ CREATE EXTENSION test_ext_cine;
 ALTER EXTENSION test_ext_cine UPDATE TO '1.1';
 
 \dx+ test_ext_cine
+
+CREATE SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema1 SCHEMA test_s_dep;
+CREATE EXTENSION test_ext_req_schema3 CASCADE;
+SELECT dep_req();
+SELECT dep_req2();
+SELECT dep_req3();
+ALTER EXTENSION test_ext_req_schema2 UPDATE TO '2.0';
+CREATE SCHEMA test_s_dep2;
+ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2;
+SELECT dep_req();
+ALTER EXTENSION test_ext_req_schema2 SET SCHEMA test_s_dep;
+DROP EXTENSION test_ext_req_schema1;
+DROP EXTENSION test_ext_req_schema3;
+DROP EXTENSION test_ext_req_schema2;
\ No newline at end of file
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
new file mode 100644
index 0000000000..462fb52145
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql
@@ -0,0 +1,6 @@
+/* src/test/modules/test_extensions/test_ext_req_schema1--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema1" to load this file. \quit
+
+CREATE DOMAIN req AS text
+  CONSTRAINT starts_with_1 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '1');
diff --git a/src/test/modules/test_extensions/test_ext_req_schema1.control b/src/test/modules/test_extensions/test_ext_req_schema1.control
new file mode 100644
index 0000000000..9ea4558a90
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema1.control
@@ -0,0 +1,3 @@
+comment = 'Create required extension to be referenced'
+default_version = '1.0'
+relocatable = true
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
new file mode 100644
index 0000000000..73a44a25e5
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0--2.0.sql
@@ -0,0 +1,7 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+
+CREATE OR REPLACE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1update''::@extschema:test_ext_req_schema1@.req';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
new file mode 100644
index 0000000000..b17fb82535
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN dreq AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema2.control b/src/test/modules/test_extensions/test_ext_req_schema2.control
new file mode 100644
index 0000000000..d2ba5add97
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema2.control
@@ -0,0 +1,4 @@
+comment = 'Test schema referencing of required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1'
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
new file mode 100644
index 0000000000..bd05669097
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/test_extensions/test_ext_req_schema2--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_req_schema2" to load this file. \quit
+CREATE DOMAIN req2 AS text
+  CONSTRAINT starts_with_2 check(pg_catalog.left(value,1) OPERATOR(pg_catalog.=) '2');
+
+CREATE FUNCTION dep_req2() RETURNS @extschema:test_ext_req_schema1@.req
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''1032w''::@extschema:test_ext_req_schema1@.req';
+
+CREATE FUNCTION dep_req3() RETURNS @extschema:test_ext_req_schema2@.dreq
+LANGUAGE SQL IMMUTABLE PARALLEL SAFE
+AS 'SELECT ''2032w''::@extschema:test_ext_req_schema2@.dreq';
diff --git a/src/test/modules/test_extensions/test_ext_req_schema3.control b/src/test/modules/test_extensions/test_ext_req_schema3.control
new file mode 100644
index 0000000000..66c8de5e05
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_req_schema3.control
@@ -0,0 +1,5 @@
+comment = 'Test schema referencing of 2 required extensions'
+default_version = '1.0'
+relocatable = true
+requires = 'test_ext_req_schema1,test_ext_req_schema2'
+no_relocate = 'test_ext_req_schema1'
-- 
2.21.0.windows.1

In reply to: Regina Obe (#34)
Re: Ability to reference other extensions by schema in extension scripts

On Mon, Mar 13, 2023 at 05:57:57PM -0400, Regina Obe wrote:

Attached is a slightly revised patch to fix the extra whitespace in the
extend.gml document that Sandro noted to me.

Thanks Regina.
I've tested attached patch (md5 0b652a8271fc7e71ed5f712ac162a0ef)
against current master (hash 4ef1be5a0b676a9f030cc2e4837f4b5650ecb069).
The patch applies cleanly, builds cleanly, regresses cleanly.

I've also run my quick test and I'm satisfied with it:

test=# create extension ext2 cascade;
NOTICE: installing required extension "ext1"
CREATE EXTENSION

test=# select ext2log('h');
ext1: ext2: h

test=# alter extension ext1 set schema n1;
ERROR: cannot SET SCHEMA of extension ext1 because other extensions prevent it
DETAIL: extension ext2 prevents relocation of extension ext1

test=# drop extension ext2;
DROP EXTENSION

test=# alter extension ext1 set schema n1;
ALTER EXTENSION

test=# create extension ext2;
CREATE EXTENSION

test=# select ext2log('h');
ext1: ext2: h

--strk;

#36Tom Lane
tgl@sss.pgh.pa.us
In reply to: Sandro Santilli (#35)
Re: Ability to reference other extensions by schema in extension scripts

Sandro Santilli <strk@kbt.io> writes:

On Mon, Mar 13, 2023 at 05:57:57PM -0400, Regina Obe wrote:

Attached is a slightly revised patch to fix the extra whitespace in the
extend.gml document that Sandro noted to me.

Thanks Regina.
I've tested attached patch (md5 0b652a8271fc7e71ed5f712ac162a0ef)
against current master (hash 4ef1be5a0b676a9f030cc2e4837f4b5650ecb069).
The patch applies cleanly, builds cleanly, regresses cleanly.

Pushed with some mostly-cosmetic adjustments (in particular I tried
to make the docs and tests neater).

I did not commit the changes in get_available_versions_for_extension
to add no_relocate as an output column. Those were dead code because
you hadn't done anything to connect them up to an actual output parameter
of pg_available_extension_versions(). While I'm not necessarily averse
to making the no_relocate values visible somehow, I'm not convinced that
pg_available_extension_versions should be the place to do it. ISTM what's
relevant is the no_relocate values of *installed* extensions, not those of
potentially-installable extensions. If we had a view over pg_extension
then that might be a place to add this, but we don't. On the whole it
didn't seem important enough to pursue, so I just left it out.

regards, tom lane

#37Regina Obe
lr@pcorp.us
In reply to: Tom Lane (#36)
RE: Ability to reference other extensions by schema in extension scripts

Pushed with some mostly-cosmetic adjustments (in particular I tried to

make

the docs and tests neater).

I did not commit the changes in get_available_versions_for_extension
to add no_relocate as an output column. Those were dead code because you
hadn't done anything to connect them up to an actual output parameter of
pg_available_extension_versions(). While I'm not necessarily averse to
making the no_relocate values visible somehow, I'm not convinced that
pg_available_extension_versions should be the place to do it. ISTM what's
relevant is the no_relocate values of *installed* extensions, not those of
potentially-installable extensions. If we had a view over pg_extension

then

that might be a place to add this, but we don't. On the whole it didn't

seem

important enough to pursue, so I just left it out.

regards, tom lane

Thanks. Agree with get_available_versions_for_extension, not necessary.

Thanks,
Regina

#38Tom Lane
tgl@sss.pgh.pa.us
In reply to: Regina Obe (#37)
Re: Ability to reference other extensions by schema in extension scripts

"Regina Obe" <lr@pcorp.us> writes:

making the no_relocate values visible somehow, I'm not convinced that
pg_available_extension_versions should be the place to do it. ISTM what's
relevant is the no_relocate values of *installed* extensions, not those of
potentially-installable extensions. If we had a view over pg_extension then
that might be a place to add this, but we don't. On the whole it didn't seem
important enough to pursue, so I just left it out.

Thanks. Agree with get_available_versions_for_extension, not necessary.

If we did feel like doing something about this, on reflection I think
the thing to do would be to add no_relocate as an actual column in
pg_extension, probably of type "oid[]". Then we could modify the
SET SCHEMA code to check that instead of parsing the extension control
files. That'd be a little cleaner, but I can't say that I'm hugely
excited about it.

regards, tom lane