Repair ALTER EXTENSION ... SET SCHEMA.
authorTom Lane <[email protected]>
Thu, 9 May 2024 16:19:43 +0000 (12:19 -0400)
committerTom Lane <[email protected]>
Thu, 9 May 2024 16:19:52 +0000 (12:19 -0400)
It turns out that we broke this in commit e5bc9454e, because
the code was assuming that no dependent types would appear
among the extension's direct dependencies, and now they do.

This isn't terribly hard to fix: just skip dependent types,
expecting that we will recurse to them when we process the parent
object (which should also be among the direct dependencies).
But a little bit of refactoring is needed so that we can avoid
duplicating logic about what is a dependent type.

Although there is some testing of ALTER EXTENSION SET SCHEMA,
it failed to cover interesting cases, so add more tests.

Discussion: https://postgr.es/m/930191.1715205151@sss.pgh.pa.us

src/backend/commands/alter.c
src/backend/commands/extension.c
src/backend/commands/tablecmds.c
src/backend/commands/typecmds.c
src/include/commands/typecmds.h
src/test/modules/test_extensions/Makefile
src/test/modules/test_extensions/expected/test_extensions.out
src/test/modules/test_extensions/meson.build
src/test/modules/test_extensions/sql/test_extensions.sql
src/test/modules/test_extensions/test_ext_set_schema--1.0.sql [new file with mode: 0644]
src/test/modules/test_extensions/test_ext_set_schema.control [new file with mode: 0644]

index 12802b9d3fdfeee9df08806ead8d34cbdc672f53..4f99ebb4470a6d648cbe3719e7d0d2acddc82604 100644 (file)
@@ -598,16 +598,16 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 /*
  * Change an object's namespace given its classOid and object Oid.
  *
- * Objects that don't have a namespace should be ignored.
+ * Objects that don't have a namespace should be ignored, as should
+ * dependent types such as array types.
  *
  * This function is currently used only by ALTER EXTENSION SET SCHEMA,
- * so it only needs to cover object types that can be members of an
- * extension, and it doesn't have to deal with certain special cases
- * such as not wanting to process array types --- those should never
- * be direct members of an extension anyway.
+ * so it only needs to cover object kinds that can be members of an
+ * extension, and it can silently ignore dependent types --- we assume
+ * those will be moved when their parent object is moved.
  *
  * Returns the OID of the object's previous namespace, or InvalidOid if
- * object doesn't have a schema.
+ * object doesn't have a schema or was ignored due to being a dependent type.
  */
 Oid
 AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
@@ -631,7 +631,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
            }
 
        case TypeRelationId:
-           oldNspOid = AlterTypeNamespace_oid(objid, nspOid, objsMoved);
+           oldNspOid = AlterTypeNamespace_oid(objid, nspOid, true, objsMoved);
            break;
 
        case ProcedureRelationId:
index 77d8c9e1862f4fd9a649d38a1f570795af73b27c..1643c8c69a0d816390b265762fc1a123b4131b5a 100644 (file)
@@ -2940,7 +2940,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 
        /*
         * If not all the objects had the same old namespace (ignoring any
-        * that are not in namespaces), complain.
+        * that are not in namespaces or are dependent types), complain.
         */
        if (dep_oldNspOid != InvalidOid && dep_oldNspOid != oldNspOid)
            ereport(ERROR,
index 925978c35b93727f89b32f944a8a45791e86f759..de0d911b468a001af1adddff16d6b781cf4bc7b9 100644 (file)
@@ -18017,8 +18017,11 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
    /* Fix the table's row type too, if it has one */
    if (OidIsValid(rel->rd_rel->reltype))
-       AlterTypeNamespaceInternal(rel->rd_rel->reltype,
-                                  nspOid, false, false, objsMoved);
+       AlterTypeNamespaceInternal(rel->rd_rel->reltype, nspOid,
+                                  false,   /* isImplicitArray */
+                                  false,   /* ignoreDependent */
+                                  false,   /* errorOnTableType */
+                                  objsMoved);
 
    /* Fix other dependent stuff */
    AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
index 315b0feb8e47a2b19a0b0ce182a1c950cafc29a3..2a6550de907b5b3ea6525036d3e4855a26751d01 100644 (file)
@@ -4068,7 +4068,7 @@ AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype,
    typename = makeTypeNameFromNameList(names);
    typeOid = typenameTypeId(NULL, typename);
 
-   /* Don't allow ALTER DOMAIN on a type */
+   /* Don't allow ALTER DOMAIN on a non-domain type */
    if (objecttype == OBJECT_DOMAIN && get_typtype(typeOid) != TYPTYPE_DOMAIN)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -4079,7 +4079,7 @@ AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype,
    nspOid = LookupCreationNamespace(newschema);
 
    objsMoved = new_object_addresses();
-   oldNspOid = AlterTypeNamespace_oid(typeOid, nspOid, objsMoved);
+   oldNspOid = AlterTypeNamespace_oid(typeOid, nspOid, false, objsMoved);
    free_object_addresses(objsMoved);
 
    if (oldschema)
@@ -4090,8 +4090,21 @@ AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype,
    return myself;
 }
 
+/*
+ * ALTER TYPE SET SCHEMA, where the caller has already looked up the OIDs
+ * of the type and the target schema and checked the schema's privileges.
+ *
+ * If ignoreDependent is true, we silently ignore dependent types
+ * (array types and table rowtypes) rather than raising errors.
+ *
+ * This entry point is exported for use by AlterObjectNamespace_oid,
+ * which doesn't want errors when it passes OIDs of dependent types.
+ *
+ * Returns the type's old namespace OID, or InvalidOid if we did nothing.
+ */
 Oid
-AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, ObjectAddresses *objsMoved)
+AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, bool ignoreDependent,
+                      ObjectAddresses *objsMoved)
 {
    Oid         elemOid;
 
@@ -4102,15 +4115,23 @@ AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, ObjectAddresses *objsMoved)
    /* don't allow direct alteration of array types */
    elemOid = get_element_type(typeOid);
    if (OidIsValid(elemOid) && get_array_type(elemOid) == typeOid)
+   {
+       if (ignoreDependent)
+           return InvalidOid;
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                 errmsg("cannot alter array type %s",
                        format_type_be(typeOid)),
                 errhint("You can alter type %s, which will alter the array type as well.",
                         format_type_be(elemOid))));
+   }
 
    /* and do the work */
-   return AlterTypeNamespaceInternal(typeOid, nspOid, false, true, objsMoved);
+   return AlterTypeNamespaceInternal(typeOid, nspOid,
+                                     false,    /* isImplicitArray */
+                                     ignoreDependent,  /* ignoreDependent */
+                                     true, /* errorOnTableType */
+                                     objsMoved);
 }
 
 /*
@@ -4122,15 +4143,21 @@ AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, ObjectAddresses *objsMoved)
  * if any.  isImplicitArray should be true only when doing this internal
  * recursion (outside callers must never try to move an array type directly).
  *
+ * If ignoreDependent is true, we silently don't process table types.
+ *
  * If errorOnTableType is true, the function errors out if the type is
  * a table type.  ALTER TABLE has to be used to move a table to a new
- * namespace.
+ * namespace.  (This flag is ignored if ignoreDependent is true.)
+ *
+ * We also do nothing if the type is already listed in *objsMoved.
+ * After a successful move, we add the type to *objsMoved.
  *
- * Returns the type's old namespace OID.
+ * Returns the type's old namespace OID, or InvalidOid if we did nothing.
  */
 Oid
 AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
                           bool isImplicitArray,
+                          bool ignoreDependent,
                           bool errorOnTableType,
                           ObjectAddresses *objsMoved)
 {
@@ -4185,15 +4212,21 @@ AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
         get_rel_relkind(typform->typrelid) == RELKIND_COMPOSITE_TYPE);
 
    /* Enforce not-table-type if requested */
-   if (typform->typtype == TYPTYPE_COMPOSITE && !isCompositeType &&
-       errorOnTableType)
-       ereport(ERROR,
-               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                errmsg("%s is a table's row type",
-                       format_type_be(typeOid)),
-       /* translator: %s is an SQL ALTER command */
-                errhint("Use %s instead.",
-                        "ALTER TABLE")));
+   if (typform->typtype == TYPTYPE_COMPOSITE && !isCompositeType)
+   {
+       if (ignoreDependent)
+       {
+           table_close(rel, RowExclusiveLock);
+           return InvalidOid;
+       }
+       if (errorOnTableType)
+           ereport(ERROR,
+                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                    errmsg("%s is a table's row type",
+                           format_type_be(typeOid)),
+           /* translator: %s is an SQL ALTER command */
+                    errhint("Use %s instead.", "ALTER TABLE")));
+   }
 
    if (oldNspOid != nspOid)
    {
@@ -4260,7 +4293,11 @@ AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
 
    /* Recursively alter the associated array type, if any */
    if (OidIsValid(arrayOid))
-       AlterTypeNamespaceInternal(arrayOid, nspOid, true, true, objsMoved);
+       AlterTypeNamespaceInternal(arrayOid, nspOid,
+                                  true,    /* isImplicitArray */
+                                  false,   /* ignoreDependent */
+                                  true,    /* errorOnTableType */
+                                  objsMoved);
 
    return oldNspOid;
 }
index c378f9cd4f39b1c428b48407c7fd65b041233f54..e1b02927c4bab5352ff85ef5e19d7186b1d90676 100644 (file)
@@ -50,9 +50,11 @@ extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId);
 
 extern ObjectAddress AlterTypeNamespace(List *names, const char *newschema,
                                        ObjectType objecttype, Oid *oldschema);
-extern Oid AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, ObjectAddresses *objsMoved);
+extern Oid AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, bool ignoreDependent,
+                                  ObjectAddresses *objsMoved);
 extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
                                       bool isImplicitArray,
+                                      bool ignoreDependent,
                                       bool errorOnTableType,
                                       ObjectAddresses *objsMoved);
 
index 7d95d6b92450a608022f4f13f5ae6d2cb04ebe9b..05272e6a40b93f80260159b90681ec0bb1c0bab9 100644 (file)
@@ -8,6 +8,7 @@ EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
             test_ext_cyclic1 test_ext_cyclic2 \
             test_ext_extschema \
             test_ext_evttrig \
+            test_ext_set_schema \
             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 \
@@ -19,6 +20,7 @@ DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
        test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \
        test_ext_extschema--1.0.sql \
        test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql \
+       test_ext_set_schema--1.0.sql \
        test_ext_req_schema1--1.0.sql \
        test_ext_req_schema2--1.0.sql \
        test_ext_req_schema3--1.0.sql
index a7ab244e8750a4799412d4fc8f68f85d1afa1199..f357cc21aaab4695573bcde11a7e7643b1a94a3c 100644 (file)
@@ -464,6 +464,44 @@ CREATE EXTENSION test_ext_extschema SCHEMA has$dollar;
 ERROR:  invalid character in extension "test_ext_extschema" schema: must not contain any of ""$'\"
 CREATE EXTENSION test_ext_extschema SCHEMA "has space";
 --
+-- Test basic SET SCHEMA handling.
+--
+CREATE SCHEMA s1;
+CREATE SCHEMA s2;
+CREATE EXTENSION test_ext_set_schema SCHEMA s1;
+ALTER EXTENSION test_ext_set_schema SET SCHEMA s2;
+\dx+ test_ext_set_schema
+      Objects in extension "test_ext_set_schema"
+                  Object description                   
+-------------------------------------------------------
+ cast from s2.ess_range_type to s2.ess_multirange_type
+ function s2.ess_func(integer)
+ function s2.ess_multirange_type()
+ function s2.ess_multirange_type(s2.ess_range_type)
+ function s2.ess_multirange_type(s2.ess_range_type[])
+ function s2.ess_range_type(text,text)
+ function s2.ess_range_type(text,text,text)
+ table s2.ess_table
+ type s2.ess_composite_type
+ type s2.ess_composite_type[]
+ type s2.ess_multirange_type
+ type s2.ess_multirange_type[]
+ type s2.ess_range_type
+ type s2.ess_range_type[]
+ type s2.ess_table
+ type s2.ess_table[]
+(16 rows)
+
+\sf s2.ess_func(int)
+CREATE OR REPLACE FUNCTION s2.ess_func(integer)
+ RETURNS text
+ LANGUAGE sql
+BEGIN ATOMIC
+ SELECT ess_table.f3
+    FROM s2.ess_table
+   WHERE (ess_table.f1 = $1);
+END
+--
 -- Test extension with objects outside the extension's schema.
 --
 CREATE SCHEMA test_func_dep1;
index 9b8d0a10168ee9649baee0dc5bb6eaffdd12854d..c5f3424da515979268fc3dd6019d2c08b0bd6f23 100644 (file)
@@ -40,6 +40,8 @@ test_install_data += files(
   'test_ext_req_schema2.control',
   'test_ext_req_schema3--1.0.sql',
   'test_ext_req_schema3.control',
+  'test_ext_set_schema--1.0.sql',
+  'test_ext_set_schema.control',
 )
 
 tests += {
index c5b64f47c6b7e49f2b27212c8c07e47effeabec9..642c82ff5d381ecdfa86742b89d01b41905d7e06 100644 (file)
@@ -232,6 +232,16 @@ CREATE SCHEMA "has space";
 CREATE EXTENSION test_ext_extschema SCHEMA has$dollar;
 CREATE EXTENSION test_ext_extschema SCHEMA "has space";
 
+--
+-- Test basic SET SCHEMA handling.
+--
+CREATE SCHEMA s1;
+CREATE SCHEMA s2;
+CREATE EXTENSION test_ext_set_schema SCHEMA s1;
+ALTER EXTENSION test_ext_set_schema SET SCHEMA s2;
+\dx+ test_ext_set_schema
+\sf s2.ess_func(int)
+
 --
 -- Test extension with objects outside the extension's schema.
 --
diff --git a/src/test/modules/test_extensions/test_ext_set_schema--1.0.sql b/src/test/modules/test_extensions/test_ext_set_schema--1.0.sql
new file mode 100644 (file)
index 0000000..66df583
--- /dev/null
@@ -0,0 +1,17 @@
+/* src/test/modules/test_extensions/test_ext_set_schema--1.0.sql */
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ext_set_schema" to load this file. \quit
+
+-- Create various object types that need extra handling by SET SCHEMA.
+
+CREATE TABLE ess_table (f1 int primary key, f2 int, f3 text,
+                        constraint ess_c check (f1 != f2));
+
+CREATE FUNCTION ess_func(int) RETURNS text
+BEGIN ATOMIC
+  SELECT f3 FROM ess_table WHERE f1 = $1;
+END;
+
+CREATE TYPE ess_range_type AS RANGE (subtype = text);
+
+CREATE TYPE ess_composite_type AS (f1 int, f2 ess_range_type);
diff --git a/src/test/modules/test_extensions/test_ext_set_schema.control b/src/test/modules/test_extensions/test_ext_set_schema.control
new file mode 100644 (file)
index 0000000..a9a2367
--- /dev/null
@@ -0,0 +1,3 @@
+comment = 'Test ALTER EXTENSION SET SCHEMA'
+default_version = '1.0'
+relocatable = true