From 713e553e3213d53f4ba25dcb7a8c41994b7cab9d Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Wed, 22 Apr 2026 14:32:57 +0300 Subject: [PATCH] Preserve extension dependencies on indexes during partition merge/split 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 Co-authored-by: Alexander Korotkov Reported-by: Kirill Reshke Reviewed-by: Dmitry Koval Discussion: https://www.postgresql.org/message-id/CALdSSPjXtzGM7Uk4fWRwRMXcCczge5uNirPQcYCHKPAWPkp9iQ%40mail.gmail.com --- doc/src/sgml/ref/alter_table.sgml | 17 ++ src/backend/commands/tablecmds.c | 283 ++++++++++++++++++ .../expected/test_extdepend.out | 120 ++++++++ .../test_extensions/sql/test_extdepend.sql | 104 +++++++ src/tools/pgindent/typedefs.list | 1 + 5 files changed, 525 insertions(+) diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 54f38cb964a..1f9a456fd33 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -1255,6 +1255,15 @@ WITH ( MODULUS numeric_literal, REM (see ). + + Extension dependencies on partition indexes (created via + ALTER INDEX ... DEPENDS ON + EXTENSION) 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. + + Merging partitions acquires an ACCESS EXCLUSIVE lock on @@ -1342,6 +1351,14 @@ WITH ( MODULUS numeric_literal, REM would fail (see ). + + Extension dependencies on partition indexes (created via + ALTER INDEX ... DEPENDS ON + EXTENSION) are preserved during split operations. + The new partitions' indexes will inherit the extension dependencies + from the source partition's indexes. + + Split partition acquires an ACCESS EXCLUSIVE lock on diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1068d7c7b6a..32db5a899dc 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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 MERGE PARTITIONS INTO */ @@ -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; diff --git a/src/test/modules/test_extensions/expected/test_extdepend.out b/src/test/modules/test_extensions/expected/test_extdepend.out index 0b62015d18c..ede5dc64c04 100644 --- a/src/test/modules/test_extensions/expected/test_extdepend.out +++ b/src/test/modules/test_extensions/expected/test_extdepend.out @@ -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; diff --git a/src/test/modules/test_extensions/sql/test_extdepend.sql b/src/test/modules/test_extensions/sql/test_extdepend.sql index 63240a1af5d..ad734af1e71 100644 --- a/src/test/modules/test_extensions/sql/test_extdepend.sql +++ b/src/test/modules/test_extensions/sql/test_extdepend.sql @@ -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; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 49dfb662abc..9f1dd55213d 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2200,6 +2200,7 @@ PartitionDirectoryEntry PartitionDispatch PartitionElem PartitionHashBound +PartitionIndexExtDepEntry PartitionKey PartitionListValue PartitionMap -- 2.47.3