]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Preserve extension dependencies on indexes during partition merge/split
authorAlexander Korotkov <akorotkov@postgresql.org>
Wed, 22 Apr 2026 11:32:57 +0000 (14:32 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Wed, 22 Apr 2026 11:34:20 +0000 (14:34 +0300)
When using ALTER TABLE ... MERGE PARTITIONS or ALTER TABLE ... SPLIT
PARTITION, extension dependencies on partition indexes were being lost.
This happened because the new partition indexes are created fresh from
the parent partitioned table's indexes, while the old partition indexes
(with their extension dependencies) are dropped.

Fix this by collecting extension dependencies from source partition
indexes before detaching them, then applying those dependencies to the
corresponding new partition indexes after they're created.  The mapping
between old and new indexes is done via their common parent partitioned
index.

For MERGE operations, all source partition indexes sharing a parent
partitioned index must have the same extension dependencies; if they
differ, an error naming both conflicting partition indexes is raised.
The check is implemented by collecting one entry per partition index,
sorting by parent index OID, and comparing adjacent entries in a single
pass.  This is order-independent: the same set of partitions produces
the same decision regardless of the order they are listed in the MERGE
command, and subset mismatches are caught in both directions.

For SPLIT operations, the new partition indexes simply inherit all
extension dependencies from the source partition's index.

The regression tests exercising this feature live under
src/test/modules/test_extensions, where the test_ext3 and test_ext5
extensions are available; core regression tests cannot assume any
particular extension is installed.

Author: Matheus Alcantara <matheusssilv97@gmail.com>
Co-authored-by: Alexander Korotkov <aekorotkov@gmail.com>
Reported-by: Kirill Reshke <reshkekirill@gmail.com>
Reviewed-by: Dmitry Koval <d.koval@postgrespro.ru>
Discussion: https://www.postgresql.org/message-id/CALdSSPjXtzGM7Uk4fWRwRMXcCczge5uNirPQcYCHKPAWPkp9iQ%40mail.gmail.com

doc/src/sgml/ref/alter_table.sgml
src/backend/commands/tablecmds.c
src/test/modules/test_extensions/expected/test_extdepend.out
src/test/modules/test_extensions/sql/test_extdepend.sql
src/tools/pgindent/typedefs.list

index 54f38cb964ab53cba5b1bd3a997bd4e9fdcefc2a..1f9a456fd336ab810ca15e67aca7eb0ae454f303 100644 (file)
@@ -1255,6 +1255,15 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       (see <xref linkend="ddl-depend"/>).
      </para>
 
+     <para>
+      Extension dependencies on partition indexes (created via
+      <link linkend="sql-alterindex"><command>ALTER INDEX ... DEPENDS ON
+      EXTENSION</command></link>) are preserved during merge operations.
+      All source partition indexes must have the same extension dependencies;
+      if they differ, an error is raised. This ensures that extension
+      dependencies are not silently lost during merge.
+     </para>
+
      <note>
       <para>
        Merging partitions acquires an <literal>ACCESS EXCLUSIVE</literal> lock on
@@ -1342,6 +1351,14 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       would fail (see <xref linkend="ddl-depend"/>).
      </para>
 
+     <para>
+      Extension dependencies on partition indexes (created via
+      <link linkend="sql-alterindex"><command>ALTER INDEX ... DEPENDS ON
+      EXTENSION</command></link>) are preserved during split operations.
+      The new partitions' indexes will inherit the extension dependencies
+      from the source partition's indexes.
+     </para>
+
      <note>
       <para>
        Split partition acquires an <literal>ACCESS EXCLUSIVE</literal> lock on
index 1068d7c7b6aabd9a89097082ec6d16a090278cc6..32db5a899dc88f6d5b2fe4f10cd37aefb4c8e7ca 100644 (file)
@@ -40,6 +40,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
+#include "catalog/pg_extension_d.h"
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_largeobject.h"
@@ -60,6 +61,7 @@
 #include "commands/comment.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
+#include "commands/extension.h"
 #include "commands/repack.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -365,6 +367,27 @@ typedef enum addFkConstraintSides
        addFkBothSides,
 } addFkConstraintSides;
 
+/*
+ * Hold extension dependencies of one partition index, during
+ * MERGE/SPLIT PARTITION processing.
+ *
+ * collectPartitionIndexExtDeps() builds a list of these entries sorted by
+ * parentIndexOid with exactly one entry per parent partitioned index; the
+ * list is then consumed by applyPartitionIndexExtDeps() to re-record the
+ * same dependencies on the newly created partition's indexes.
+ *
+ * extensionOids is kept sorted ascending so that equality checks between
+ * entries from different partitions can be done in a single pass.
+ * indexOid is carried only so that conflict errors can cite specific
+ * partition index names.
+ */
+typedef struct PartitionIndexExtDepEntry
+{
+       Oid                     parentIndexOid; /* OID of the parent partitioned index */
+       Oid                     indexOid;               /* OID of a representative partition index */
+       List       *extensionOids;      /* OIDs of dependent extensions, sorted asc */
+} PartitionIndexExtDepEntry;
+
 /*
  * Partition tables are expected to be dropped when the parent partitioned
  * table gets dropped. Hence for partitioning we use AUTO dependency.
@@ -760,6 +783,9 @@ static void ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation
 static void ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab,
                                                                 Relation rel, PartitionCmd *cmd,
                                                                 AlterTableUtilityContext *context);
+static List *collectPartitionIndexExtDeps(List *partitionOids);
+static void applyPartitionIndexExtDeps(Oid newPartOid, List *extDepState);
+static void freePartitionIndexExtDeps(List *extDepState);
 
 /* ----------------------------------------------------------------
  *             DefineRelation
@@ -23011,6 +23037,224 @@ detachPartitionTable(Relation parent_rel, Relation child_rel, Oid defaultPartOid
        PopActiveSnapshot();
 }
 
+/*
+ * equal_oid_lists: return true if two OID lists, each sorted in ascending
+ * order, contain the same OIDs in the same order.
+ */
+static bool
+equal_oid_lists(const List *a, const List *b)
+{
+       ListCell   *la,
+                          *lb;
+
+       if (list_length(a) != list_length(b))
+               return false;
+
+       forboth(la, a, lb, b)
+       {
+               if (lfirst_oid(la) != lfirst_oid(lb))
+                       return false;
+       }
+       return true;
+}
+
+/*
+ * Comparator for list_sort() on a list of PartitionIndexExtDepEntry *.
+ * Orders by parentIndexOid, then by indexOid as a tiebreaker so conflict
+ * reports for different parent indexes are deterministic.
+ */
+static int
+cmp_partition_index_ext_dep(const ListCell *a, const ListCell *b)
+{
+       const PartitionIndexExtDepEntry *ea = lfirst(a);
+       const PartitionIndexExtDepEntry *eb = lfirst(b);
+
+       if (ea->parentIndexOid != eb->parentIndexOid)
+               return pg_cmp_u32(ea->parentIndexOid, eb->parentIndexOid);
+       return pg_cmp_u32(ea->indexOid, eb->indexOid);
+}
+
+/*
+ * collectPartitionIndexExtDeps: collect extension dependencies from indexes
+ * on the given partitions.
+ *
+ * For each partition index that has a parent partitioned index, we collect
+ * extension dependencies. All source partition indexes sharing the same
+ * parent partitioned index must depend on exactly the same set of
+ * extensions; otherwise an error is raised so that we neither silently drop
+ * nor silently add dependencies on the merged partition's index.
+ *
+ * Indexes that don't have a parent partitioned index (i.e., indexes created
+ * directly on a partition without a corresponding parent index) are skipped.
+ *
+ * The returned list is sorted by parentIndexOid with exactly one entry per
+ * parent partitioned index, so applyPartitionIndexExtDeps() can scan it
+ * linearly.
+ */
+static List *
+collectPartitionIndexExtDeps(List *partitionOids)
+{
+       List       *collected = NIL;
+       List       *result = NIL;
+       PartitionIndexExtDepEntry *prev = NULL;
+
+       /*
+        * Phase 1: collect one entry per (partition index -> parent index) pair,
+        * with its extension dependency OIDs sorted ascending.
+        */
+       foreach_oid(partOid, partitionOids)
+       {
+               Relation        partRel;
+               List       *indexList;
+
+               /*
+                * Use NoLock since the caller already holds AccessExclusiveLock on
+                * these partitions.
+                */
+               partRel = table_open(partOid, NoLock);
+               indexList = RelationGetIndexList(partRel);
+
+               foreach_oid(indexOid, indexList)
+               {
+                       Oid                     parentIndexOid;
+                       PartitionIndexExtDepEntry *entry;
+
+                       if (!get_rel_relispartition(indexOid))
+                               continue;
+
+                       parentIndexOid = get_partition_parent(indexOid, true);
+                       if (!OidIsValid(parentIndexOid))
+                               continue;
+
+                       entry = palloc(sizeof(PartitionIndexExtDepEntry));
+                       entry->parentIndexOid = parentIndexOid;
+                       entry->indexOid = indexOid;
+                       entry->extensionOids = getAutoExtensionsOfObject(RelationRelationId,
+                                                                                                                        indexOid);
+                       list_sort(entry->extensionOids, list_oid_cmp);
+
+                       collected = lappend(collected, entry);
+               }
+
+               list_free(indexList);
+               table_close(partRel, NoLock);
+       }
+
+       /*
+        * Phase 2: sort by parentIndexOid so entries sharing a parent index sit
+        * adjacent.
+        */
+       list_sort(collected, cmp_partition_index_ext_dep);
+
+       /*
+        * Phase 3: single linear pass verifying that adjacent entries sharing a
+        * parent index have identical extension dependencies, and keeping one
+        * representative entry per parent index.
+        */
+       foreach_ptr(PartitionIndexExtDepEntry, entry, collected)
+       {
+               if (prev != NULL && prev->parentIndexOid == entry->parentIndexOid)
+               {
+                       if (!equal_oid_lists(prev->extensionOids, entry->extensionOids))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                errmsg("cannot merge partitions with conflicting extension dependencies"),
+                                                errdetail("Partition indexes \"%s\" and \"%s\" depend on different extensions.",
+                                                                  get_rel_name(prev->indexOid),
+                                                                  get_rel_name(entry->indexOid))));
+
+                       /* Duplicate entry for the same parent index; discard. */
+                       list_free(entry->extensionOids);
+                       pfree(entry);
+                       continue;
+               }
+
+               result = lappend(result, entry);
+               prev = entry;
+       }
+
+       list_free(collected);
+
+       return result;
+}
+
+/*
+ * applyPartitionIndexExtDeps: apply collected extension dependencies to
+ * indexes on a new partition.
+ *
+ * For each index on the new partition, look up its parent index in the
+ * extDepState list. If found, record extension dependencies on the new index.
+ * extDepState is sorted by parentIndexOid, so the inner scan can bail out
+ * as soon as it passes the target OID.
+ */
+static void
+applyPartitionIndexExtDeps(Oid newPartOid, List *extDepState)
+{
+       Relation        partRel;
+       List       *indexList;
+
+       if (extDepState == NIL)
+               return;
+
+       /*
+        * Use NoLock since the caller already holds AccessExclusiveLock on the
+        * new partition.
+        */
+       partRel = table_open(newPartOid, NoLock);
+       indexList = RelationGetIndexList(partRel);
+
+       foreach_oid(indexOid, indexList)
+       {
+               Oid                     parentIdxOid;
+
+               if (!get_rel_relispartition(indexOid))
+                       continue;
+
+               parentIdxOid = get_partition_parent(indexOid, true);
+               if (!OidIsValid(parentIdxOid))
+                       continue;
+
+               foreach_ptr(PartitionIndexExtDepEntry, entry, extDepState)
+               {
+                       ObjectAddress indexAddr;
+
+                       if (entry->parentIndexOid > parentIdxOid)
+                               break;
+                       if (entry->parentIndexOid < parentIdxOid)
+                               continue;
+
+                       ObjectAddressSet(indexAddr, RelationRelationId, indexOid);
+
+                       foreach_oid(extOid, entry->extensionOids)
+                       {
+                               ObjectAddress extAddr;
+
+                               ObjectAddressSet(extAddr, ExtensionRelationId, extOid);
+                               recordDependencyOn(&indexAddr, &extAddr,
+                                                                  DEPENDENCY_AUTO_EXTENSION);
+                       }
+                       break;
+               }
+       }
+
+       list_free(indexList);
+       table_close(partRel, NoLock);
+}
+
+/*
+ * freePartitionIndexExtDeps: free memory allocated by collectPartitionIndexExtDeps.
+ */
+static void
+freePartitionIndexExtDeps(List *extDepState)
+{
+       foreach_ptr(PartitionIndexExtDepEntry, entry, extDepState)
+       {
+               list_free(entry->extensionOids);
+               pfree(entry);
+       }
+       list_free(extDepState);
+}
+
 /*
  * ALTER TABLE <name> MERGE PARTITIONS <partition-list> INTO <partition-name>
  */
@@ -23020,6 +23264,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 {
        Relation        newPartRel;
        List       *mergingPartitions = NIL;
+       List       *extDepState = NIL;
        Oid                     defaultPartOid;
        Oid                     existingRelid;
        Oid                     ownerId = InvalidOid;
@@ -23109,6 +23354,13 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
        defaultPartOid =
                get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
 
+       /*
+        * Collect extension dependencies from indexes on the merging partitions.
+        * We must do this before detaching them, so we can restore the
+        * dependencies on the new partition's indexes later.
+        */
+       extDepState = collectPartitionIndexExtDeps(mergingPartitions);
+
        /* Detach all merging partitions. */
        foreach_oid(mergingPartitionOid, mergingPartitions)
        {
@@ -23186,6 +23438,15 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
         */
        attachPartitionTable(NULL, rel, newPartRel, cmd->bound);
 
+       /*
+        * Apply extension dependencies to the new partition's indexes. This
+        * preserves any "DEPENDS ON EXTENSION" settings from the merged
+        * partitions.
+        */
+       applyPartitionIndexExtDeps(RelationGetRelid(newPartRel), extDepState);
+
+       freePartitionIndexExtDeps(extDepState);
+
        /* Keep the lock until commit. */
        table_close(newPartRel, NoLock);
 
@@ -23480,11 +23741,13 @@ ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
        bool            isSameName = false;
        char            tmpRelName[NAMEDATALEN];
        List       *newPartRels = NIL;
+       List       *extDepState = NIL;
        ObjectAddress object;
        Oid                     defaultPartOid;
        Oid                     save_userid;
        int                     save_sec_context;
        int                     save_nestlevel;
+       List       *splitPartList;
 
        defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
 
@@ -23517,6 +23780,16 @@ ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                        errmsg("relation \"%s\" already exists", sps->name->relname));
        }
 
+       /*
+        * Collect extension dependencies from indexes on the split partition. We
+        * must do this before detaching it, so we can restore the dependencies on
+        * the new partitions' indexes later.
+        */
+       splitPartList = list_make1_oid(splitRelOid);
+
+       extDepState = collectPartitionIndexExtDeps(splitPartList);
+       list_free(splitPartList);
+
        /* Detach the split partition. */
        detachPartitionTable(rel, splitRel, defaultPartOid);
 
@@ -23596,10 +23869,20 @@ ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
                 * needed.
                 */
                attachPartitionTable(NULL, rel, newPartRel, sps->bound);
+
+               /*
+                * Apply extension dependencies to the new partition's indexes. This
+                * preserves any "DEPENDS ON EXTENSION" settings from the split
+                * partition.
+                */
+               applyPartitionIndexExtDeps(RelationGetRelid(newPartRel), extDepState);
+
                /* Keep the lock until commit. */
                table_close(newPartRel, NoLock);
        }
 
+       freePartitionIndexExtDeps(extDepState);
+
        /* Drop the split partition. */
        object.classId = RelationRelationId;
        object.objectId = splitRelOid;
index 0b62015d18c80804da9e38d5232134e640601ebf..ede5dc64c04a7ad25fb5c59f4e11bd6da1c52029 100644 (file)
@@ -186,3 +186,123 @@ DROP MATERIALIZED VIEW d;
 DROP INDEX e;
 DROP SCHEMA test_ext CASCADE;
 NOTICE:  drop cascades to table a
+-- Fifth test: extension dependencies on partition indexes survive MERGE and
+-- SPLIT PARTITION operations, and mismatches between source partitions are
+-- reported.
+RESET search_path;
+CREATE EXTENSION test_ext3;
+CREATE EXTENSION test_ext5;
+CREATE TABLE part_extdep (i int, x int) PARTITION BY RANGE (i);
+CREATE TABLE part_extdep_1 PARTITION OF part_extdep FOR VALUES FROM (1) TO (2);
+CREATE TABLE part_extdep_2 PARTITION OF part_extdep FOR VALUES FROM (2) TO (3);
+CREATE TABLE part_extdep_3 PARTITION OF part_extdep FOR VALUES FROM (3) TO (4);
+CREATE TABLE part_extdep_4 PARTITION OF part_extdep FOR VALUES FROM (4) TO (5);
+CREATE TABLE part_extdep_5 PARTITION OF part_extdep FOR VALUES FROM (5) TO (6);
+CREATE INDEX part_extdep_i_idx ON part_extdep(i);
+CREATE INDEX part_extdep_x_idx ON part_extdep(x);
+-- Partitions 1, 2, 3 depend on the same two extensions.
+ALTER INDEX part_extdep_1_i_idx DEPENDS ON EXTENSION test_ext3;
+ALTER INDEX part_extdep_1_x_idx DEPENDS ON EXTENSION test_ext5;
+ALTER INDEX part_extdep_2_i_idx DEPENDS ON EXTENSION test_ext3;
+ALTER INDEX part_extdep_2_x_idx DEPENDS ON EXTENSION test_ext5;
+ALTER INDEX part_extdep_3_i_idx DEPENDS ON EXTENSION test_ext3;
+ALTER INDEX part_extdep_3_x_idx DEPENDS ON EXTENSION test_ext5;
+-- Partition 4 depends on a different extension on one index.
+ALTER INDEX part_extdep_4_i_idx DEPENDS ON EXTENSION test_ext5;
+-- Partition 5 has no dependency at all.
+-- Merge matching partitions: should succeed and preserve dependencies on the
+-- new partition's indexes (DROP EXTENSION must fail, naming the new index).
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_1, part_extdep_2)
+    INTO part_extdep_merged;
+DROP EXTENSION test_ext3;
+ERROR:  cannot drop index part_extdep_merged_i_idx because index part_extdep_i_idx requires it
+HINT:  You can drop index part_extdep_i_idx instead.
+SELECT c.relname, e.extname
+FROM pg_depend d
+JOIN pg_class c ON d.objid = c.oid
+JOIN pg_extension e ON d.refobjid = e.oid
+WHERE c.relname IN ('part_extdep_merged_i_idx', 'part_extdep_merged_x_idx')
+  AND e.extname IN ('test_ext3', 'test_ext5')
+  AND d.deptype = 'x'
+ORDER BY c.relname, e.extname;
+         relname          |  extname  
+--------------------------+-----------
+ part_extdep_merged_i_idx | test_ext3
+ part_extdep_merged_x_idx | test_ext5
+(2 rows)
+
+-- An index created directly on a partition has no parent in the partitioned
+-- index tree.  Such an index is dropped with its old partition during merge,
+-- and any extension dependency it carries goes away with it: the dep is not
+-- promoted to the merged partition.  Verify by attaching test_ext9 to such
+-- an orphan index, merging, and observing that test_ext9 becomes droppable.
+CREATE EXTENSION test_ext9;
+CREATE INDEX part_extdep_3_extra_idx ON part_extdep_3(x);
+ALTER INDEX part_extdep_3_extra_idx DEPENDS ON EXTENSION test_ext9;
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_merged, part_extdep_3)
+    INTO part_extdep_merged2;
+DROP EXTENSION test_ext9;
+-- Mismatched dependencies: partition 4's index depends on a different
+-- extension than partition_merged2's. Both orderings must fail, and the
+-- error must cite both partition indexes.
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_merged2, part_extdep_4)
+    INTO part_extdep_bad;
+ERROR:  cannot merge partitions with conflicting extension dependencies
+DETAIL:  Partition indexes "part_extdep_4_i_idx" and "part_extdep_merged2_i_idx" depend on different extensions.
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_merged2)
+    INTO part_extdep_bad;
+ERROR:  cannot merge partitions with conflicting extension dependencies
+DETAIL:  Partition indexes "part_extdep_4_i_idx" and "part_extdep_merged2_i_idx" depend on different extensions.
+-- Empty vs non-empty dependency set (the subset case the earlier linear
+-- check missed in one direction).
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_5)
+    INTO part_extdep_bad;
+ERROR:  cannot merge partitions with conflicting extension dependencies
+DETAIL:  Partition indexes "part_extdep_4_i_idx" and "part_extdep_5_i_idx" depend on different extensions.
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_5, part_extdep_4)
+    INTO part_extdep_bad;
+ERROR:  cannot merge partitions with conflicting extension dependencies
+DETAIL:  Partition indexes "part_extdep_4_i_idx" and "part_extdep_5_i_idx" depend on different extensions.
+-- Subset: partition 5's i_idx depends on a strict superset of partition 4's
+-- i_idx dependencies. Partition 4 = {test_ext5}, partition 5 will be
+-- {test_ext3, test_ext5}. Both orderings must fail; in particular the case
+-- where the first partition we walk has fewer extensions than the second
+-- must still be rejected.
+ALTER INDEX part_extdep_5_i_idx DEPENDS ON EXTENSION test_ext3;
+ALTER INDEX part_extdep_5_i_idx DEPENDS ON EXTENSION test_ext5;
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_5)
+    INTO part_extdep_bad;
+ERROR:  cannot merge partitions with conflicting extension dependencies
+DETAIL:  Partition indexes "part_extdep_4_i_idx" and "part_extdep_5_i_idx" depend on different extensions.
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_5, part_extdep_4)
+    INTO part_extdep_bad;
+ERROR:  cannot merge partitions with conflicting extension dependencies
+DETAIL:  Partition indexes "part_extdep_4_i_idx" and "part_extdep_5_i_idx" depend on different extensions.
+-- Reset partition 5 so it doesn't interfere with the SPLIT test below.
+ALTER INDEX part_extdep_5_i_idx NO DEPENDS ON EXTENSION test_ext3;
+ALTER INDEX part_extdep_5_i_idx NO DEPENDS ON EXTENSION test_ext5;
+-- Split: the single source partition's dependencies must appear on every
+-- new partition's matching index, identified by extension name.
+ALTER TABLE part_extdep SPLIT PARTITION part_extdep_merged2 INTO
+    (PARTITION part_extdep_s1 FOR VALUES FROM (1) TO (3),
+     PARTITION part_extdep_s2 FOR VALUES FROM (3) TO (4));
+SELECT c.relname, e.extname
+FROM pg_depend d
+JOIN pg_class c ON d.objid = c.oid
+JOIN pg_extension e ON d.refobjid = e.oid
+WHERE c.relname IN ('part_extdep_s1_i_idx', 'part_extdep_s1_x_idx',
+                    'part_extdep_s2_i_idx', 'part_extdep_s2_x_idx')
+  AND e.extname IN ('test_ext3', 'test_ext5')
+  AND d.deptype = 'x'
+ORDER BY c.relname, e.extname;
+       relname        |  extname  
+----------------------+-----------
+ part_extdep_s1_i_idx | test_ext3
+ part_extdep_s1_x_idx | test_ext5
+ part_extdep_s2_i_idx | test_ext3
+ part_extdep_s2_x_idx | test_ext5
+(4 rows)
+
+DROP TABLE part_extdep;
+DROP EXTENSION test_ext3;
+DROP EXTENSION test_ext5;
index 63240a1af5dfda5311d972d222710ebc10f92ed5..ad734af1e71eaac5c7374a70222d3b6c9494912a 100644 (file)
@@ -88,3 +88,107 @@ DROP FUNCTION b();
 DROP MATERIALIZED VIEW d;
 DROP INDEX e;
 DROP SCHEMA test_ext CASCADE;
+
+-- Fifth test: extension dependencies on partition indexes survive MERGE and
+-- SPLIT PARTITION operations, and mismatches between source partitions are
+-- reported.
+RESET search_path;
+CREATE EXTENSION test_ext3;
+CREATE EXTENSION test_ext5;
+
+CREATE TABLE part_extdep (i int, x int) PARTITION BY RANGE (i);
+CREATE TABLE part_extdep_1 PARTITION OF part_extdep FOR VALUES FROM (1) TO (2);
+CREATE TABLE part_extdep_2 PARTITION OF part_extdep FOR VALUES FROM (2) TO (3);
+CREATE TABLE part_extdep_3 PARTITION OF part_extdep FOR VALUES FROM (3) TO (4);
+CREATE TABLE part_extdep_4 PARTITION OF part_extdep FOR VALUES FROM (4) TO (5);
+CREATE TABLE part_extdep_5 PARTITION OF part_extdep FOR VALUES FROM (5) TO (6);
+CREATE INDEX part_extdep_i_idx ON part_extdep(i);
+CREATE INDEX part_extdep_x_idx ON part_extdep(x);
+
+-- Partitions 1, 2, 3 depend on the same two extensions.
+ALTER INDEX part_extdep_1_i_idx DEPENDS ON EXTENSION test_ext3;
+ALTER INDEX part_extdep_1_x_idx DEPENDS ON EXTENSION test_ext5;
+ALTER INDEX part_extdep_2_i_idx DEPENDS ON EXTENSION test_ext3;
+ALTER INDEX part_extdep_2_x_idx DEPENDS ON EXTENSION test_ext5;
+ALTER INDEX part_extdep_3_i_idx DEPENDS ON EXTENSION test_ext3;
+ALTER INDEX part_extdep_3_x_idx DEPENDS ON EXTENSION test_ext5;
+
+-- Partition 4 depends on a different extension on one index.
+ALTER INDEX part_extdep_4_i_idx DEPENDS ON EXTENSION test_ext5;
+
+-- Partition 5 has no dependency at all.
+
+-- Merge matching partitions: should succeed and preserve dependencies on the
+-- new partition's indexes (DROP EXTENSION must fail, naming the new index).
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_1, part_extdep_2)
+    INTO part_extdep_merged;
+DROP EXTENSION test_ext3;
+SELECT c.relname, e.extname
+FROM pg_depend d
+JOIN pg_class c ON d.objid = c.oid
+JOIN pg_extension e ON d.refobjid = e.oid
+WHERE c.relname IN ('part_extdep_merged_i_idx', 'part_extdep_merged_x_idx')
+  AND e.extname IN ('test_ext3', 'test_ext5')
+  AND d.deptype = 'x'
+ORDER BY c.relname, e.extname;
+
+-- An index created directly on a partition has no parent in the partitioned
+-- index tree.  Such an index is dropped with its old partition during merge,
+-- and any extension dependency it carries goes away with it: the dep is not
+-- promoted to the merged partition.  Verify by attaching test_ext9 to such
+-- an orphan index, merging, and observing that test_ext9 becomes droppable.
+CREATE EXTENSION test_ext9;
+CREATE INDEX part_extdep_3_extra_idx ON part_extdep_3(x);
+ALTER INDEX part_extdep_3_extra_idx DEPENDS ON EXTENSION test_ext9;
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_merged, part_extdep_3)
+    INTO part_extdep_merged2;
+DROP EXTENSION test_ext9;
+
+-- Mismatched dependencies: partition 4's index depends on a different
+-- extension than partition_merged2's. Both orderings must fail, and the
+-- error must cite both partition indexes.
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_merged2, part_extdep_4)
+    INTO part_extdep_bad;
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_merged2)
+    INTO part_extdep_bad;
+
+-- Empty vs non-empty dependency set (the subset case the earlier linear
+-- check missed in one direction).
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_5)
+    INTO part_extdep_bad;
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_5, part_extdep_4)
+    INTO part_extdep_bad;
+
+-- Subset: partition 5's i_idx depends on a strict superset of partition 4's
+-- i_idx dependencies. Partition 4 = {test_ext5}, partition 5 will be
+-- {test_ext3, test_ext5}. Both orderings must fail; in particular the case
+-- where the first partition we walk has fewer extensions than the second
+-- must still be rejected.
+ALTER INDEX part_extdep_5_i_idx DEPENDS ON EXTENSION test_ext3;
+ALTER INDEX part_extdep_5_i_idx DEPENDS ON EXTENSION test_ext5;
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_4, part_extdep_5)
+    INTO part_extdep_bad;
+ALTER TABLE part_extdep MERGE PARTITIONS (part_extdep_5, part_extdep_4)
+    INTO part_extdep_bad;
+-- Reset partition 5 so it doesn't interfere with the SPLIT test below.
+ALTER INDEX part_extdep_5_i_idx NO DEPENDS ON EXTENSION test_ext3;
+ALTER INDEX part_extdep_5_i_idx NO DEPENDS ON EXTENSION test_ext5;
+
+-- Split: the single source partition's dependencies must appear on every
+-- new partition's matching index, identified by extension name.
+ALTER TABLE part_extdep SPLIT PARTITION part_extdep_merged2 INTO
+    (PARTITION part_extdep_s1 FOR VALUES FROM (1) TO (3),
+     PARTITION part_extdep_s2 FOR VALUES FROM (3) TO (4));
+SELECT c.relname, e.extname
+FROM pg_depend d
+JOIN pg_class c ON d.objid = c.oid
+JOIN pg_extension e ON d.refobjid = e.oid
+WHERE c.relname IN ('part_extdep_s1_i_idx', 'part_extdep_s1_x_idx',
+                    'part_extdep_s2_i_idx', 'part_extdep_s2_x_idx')
+  AND e.extname IN ('test_ext3', 'test_ext5')
+  AND d.deptype = 'x'
+ORDER BY c.relname, e.extname;
+
+DROP TABLE part_extdep;
+DROP EXTENSION test_ext3;
+DROP EXTENSION test_ext5;
index 49dfb662abc40f8eb105e5466c55987d551eb254..9f1dd55213d07958303d57667b2fbf1e76576cee 100644 (file)
@@ -2200,6 +2200,7 @@ PartitionDirectoryEntry
 PartitionDispatch
 PartitionElem
 PartitionHashBound
+PartitionIndexExtDepEntry
 PartitionKey
 PartitionListValue
 PartitionMap