From d89992ca128580953c8893d93ff573bb60b323a0 Mon Sep 17 00:00:00 2001 From: Matheus Alcantara Date: Wed, 11 Mar 2026 11:39:56 -0300 Subject: [PATCH v3] 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 (created via ALTER INDEX ... DEPENDS ON EXTENSION) 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, only extension dependencies that exist on ALL source partition indexes are preserved on the merged partition's index. For example: MERGE(idx1(ext_a, ext_b), idx2(ext_a)) -> idx3(ext_a) MERGE(idx1(ext_a), idx2()) -> idx3() -- no common deps For SPLIT operations, the new partition indexes simply inherit all extension dependencies from the source partition's index. Also adds list_intersection_oid() to support the intersection logic. Author: Matheus Alcantara 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 | 20 ++ src/backend/commands/tablecmds.c | 251 ++++++++++++++++++ src/backend/nodes/list.c | 26 ++ src/include/nodes/pg_list.h | 1 + src/test/regress/expected/partition_merge.out | 105 ++++++++ src/test/regress/expected/partition_split.out | 41 +++ src/test/regress/sql/partition_merge.sql | 92 +++++++ src/test/regress/sql/partition_split.sql | 39 +++ src/tools/pgindent/typedefs.list | 1 + 9 files changed, 576 insertions(+) diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 453395c5c73..5dde3ce57f1 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -1255,6 +1255,18 @@ WITH ( MODULUS numeric_literal, REM (see ). + + Extension dependencies on partition indexes (created via + ALTER INDEX ... DEPENDS ON + EXTENSION) are preserved during merge operations. + When merging partitions, only extension dependencies that exist on + all source partition indexes are preserved on + the merged partition's corresponding index. For example, if two + partitions are merged and only one partition's index depends on an + extension, that dependency will not be carried over to the merged + partition's index. + + Merging partitions acquires an ACCESS EXCLUSIVE lock on @@ -1342,6 +1354,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 eec09ba1ded..14adb089df5 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" @@ -365,6 +366,19 @@ typedef enum addFkConstraintSides addFkBothSides, } addFkConstraintSides; +/* + * Hold extension dependencies for a partitioned index. Used by + * collectPartitionIndexExtDeps and applyPartitionIndexExtDeps. + * + * Extension dependencies are created on the new partition based + * on the indexes that share the same parent index oid. + */ +typedef struct PartitionIndexExtDepEntry +{ + Oid parentIndexOid; /* OID of the parent partitioned index */ + List *extensionOids; /* List of extension OIDs this index depends */ +} PartitionIndexExtDepEntry; + /* * Partition tables are expected to be dropped when the parent partitioned * table gets dropped. Hence for partitioning we use AUTO dependency. @@ -760,6 +774,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 @@ -23000,6 +23017,201 @@ detachPartitionTable(Relation parent_rel, Relation child_rel, Oid defaultPartOid PopActiveSnapshot(); } +/* + * collectPartitionIndexExtDeps: collect extension dependencies from indexes + * on the given partitions. + * + * For each partition index that has a parent partitioned index, we collect + * extension dependencies. Dependencies are stored grouped by parent index OID. + * + * When multiple partitions have indexes with the same parent, only the + * intersection of their extension dependencies is kept. This ensures that + * after merge, the new partition's index only has dependencies that ALL + * source partition indexes had. For example: + * + * MERGE(INDEX1(EXT_A, EXT_B), INDEX2(EXT_A)) -> INDEX3(EXT_A) + * MERGE(INDEX1(EXT_A), INDEX2()) -> INDEX3() -- no common deps + * + * Indexes that don't have a parent partitioned index (i.e., indexes created + * directly on a partition without a corresponding parent index) are skipped. + * + * Returns a list of PartitionIndexExtDepEntry structs. + */ +static List * +collectPartitionIndexExtDeps(List *partitionOids) +{ + List *result = NIL; + + 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; + List *extDeps; + PartitionIndexExtDepEntry *entry = NULL; + + /* Get the parent index if this is a partition index */ + if (!get_rel_relispartition(indexOid)) + continue; + + parentIndexOid = get_partition_parent(indexOid, true); + if (!OidIsValid(parentIndexOid)) + continue; + + /* Get extension dependencies for this index. */ + extDeps = getAutoExtensionsOfObject(RelationRelationId, indexOid); + + /* Look for existing partition entry for this parent index */ + foreach_ptr(PartitionIndexExtDepEntry, e, result) + { + if (e->parentIndexOid == parentIndexOid) + { + entry = e; + break; + } + } + + if (extDeps == NIL) + { + /* + * This index has no extension dependencies. Since we only + * preserve dependencies that exist on ALL source partition + * indexes, finding one index without deps means the result + * for this parent index must be empty. + */ + if (entry != NULL) + { + list_free(entry->extensionOids); + entry->extensionOids = NIL; + } + /* No need to create an entry for no deps if none exists */ + } + else if (entry != NULL) + { + /* + * We already have an entry for this parent index from a + * previous partition. Compute the intersection to keep only + * deps that exist on both. + */ + List *old = entry->extensionOids; + + entry->extensionOids = list_intersection_oid(old, extDeps); + list_free(old); + list_free(extDeps); + } + else + { + /* + * First partition index we've seen for this parent. Create a + * new entry with all its dependencies - subsequent partitions + * will narrow this down via intersection. + */ + entry = palloc(sizeof(PartitionIndexExtDepEntry)); + entry->parentIndexOid = parentIndexOid; + entry->extensionOids = extDeps; + result = lappend(result, entry); + } + } + + list_free(indexList); + table_close(partRel, NoLock); + } + + 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. + */ +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; + ListCell *lc; + + /* Skip if this is not a partition index */ + if (!get_rel_relispartition(indexOid)) + continue; + + parentIdxOid = get_partition_parent(indexOid, true); + if (!OidIsValid(parentIdxOid)) + continue; + + /* Look for extension dependencies to apply */ + foreach(lc, extDepState) + { + PartitionIndexExtDepEntry *entry = lfirst(lc); + + if (entry->parentIndexOid == parentIdxOid) + { + ObjectAddress indexAddr; + + 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) +{ + ListCell *lc; + + foreach(lc, extDepState) + { + PartitionIndexExtDepEntry *entry = lfirst(lc); + + list_free(entry->extensionOids); + pfree(entry); + } + list_free(extDepState); +} + /* * ALTER TABLE MERGE PARTITIONS INTO */ @@ -23009,6 +23221,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel, { Relation newPartRel; List *mergingPartitions = NIL; + List *extDepState = NIL; Oid defaultPartOid; Oid existingRelid; Oid ownerId = InvalidOid; @@ -23098,6 +23311,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) { @@ -23175,6 +23395,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); @@ -23469,11 +23698,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)); @@ -23506,6 +23737,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); @@ -23585,10 +23826,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/backend/nodes/list.c b/src/backend/nodes/list.c index 98fc2b44b50..0af633de130 100644 --- a/src/backend/nodes/list.c +++ b/src/backend/nodes/list.c @@ -1219,6 +1219,32 @@ list_intersection_int(const List *list1, const List *list2) return result; } +/* + * As list_intersection but operates on lists of oids. + */ +List * +list_intersection_oid(const List *list1, const List *list2) +{ + List *result; + const ListCell *cell; + + if (list1 == NIL || list2 == NIL) + return NIL; + + Assert(IsOidList(list1)); + Assert(IsOidList(list2)); + + result = NIL; + foreach(cell, list1) + { + if (list_member_oid(list2, lfirst_oid(cell))) + result = lappend_oid(result, lfirst_oid(cell)); + } + + check_list_invariants(result); + return result; +} + /* * Return a list that contains all the cells in list1 that are not in * list2. The returned list is freshly allocated via palloc(), but the diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h index e93bcbf2698..e0f29aa1139 100644 --- a/src/include/nodes/pg_list.h +++ b/src/include/nodes/pg_list.h @@ -681,6 +681,7 @@ extern List *list_union_oid(const List *list1, const List *list2); extern List *list_intersection(const List *list1, const List *list2); extern List *list_intersection_int(const List *list1, const List *list2); +extern List *list_intersection_oid(const List *list1, const List *list2); /* currently, there's no need for list_intersection_ptr etc */ diff --git a/src/test/regress/expected/partition_merge.out b/src/test/regress/expected/partition_merge.out index 883110e25d9..b29f5f2c764 100644 --- a/src/test/regress/expected/partition_merge.out +++ b/src/test/regress/expected/partition_merge.out @@ -1091,6 +1091,111 @@ SELECT count(*) FROM t WHERE i = 15 AND g IN (SELECT g + 10 FROM t WHERE i = 5); (1 row) DROP TABLE t; +-- +-- Test that extension dependencies on partition indexes are preserved +-- after MERGE PARTITIONS. +-- +CREATE EXTENSION IF NOT EXISTS btree_gist; +CREATE EXTENSION IF NOT EXISTS btree_gin; +CREATE TABLE t_merge_extdep (i int, x int, y int) PARTITION BY RANGE (i); +CREATE TABLE t_merge_extdep_1 PARTITION OF t_merge_extdep FOR VALUES FROM (1) TO (2); +CREATE TABLE t_merge_extdep_2 PARTITION OF t_merge_extdep FOR VALUES FROM (2) TO (3); +CREATE TABLE t_merge_extdep_3 PARTITION OF t_merge_extdep FOR VALUES FROM (3) TO (4); -- Don't have a dependency +CREATE TABLE t_merge_extdep_4 PARTITION OF t_merge_extdep FOR VALUES FROM (4) TO (5); +CREATE TABLE t_merge_extdep_5 PARTITION OF t_merge_extdep FOR VALUES FROM (5) TO (6); +CREATE INDEX t_merge_extdep_idx ON t_merge_extdep USING gist (i); +CREATE INDEX t_merge_extdep_x ON t_merge_extdep USING gist (x); +-- Add extension dependency for some partition indexes +ALTER INDEX t_merge_extdep_1_i_idx DEPENDS ON EXTENSION btree_gist; +ALTER INDEX t_merge_extdep_1_x_idx DEPENDS ON EXTENSION btree_gin; +ALTER INDEX t_merge_extdep_2_i_idx DEPENDS ON EXTENSION btree_gist; +ALTER INDEX t_merge_extdep_2_x_idx DEPENDS ON EXTENSION btree_gin; +-- Add a different extension dependency to ensure that only indexes with the +-- same dependencies has the dependencies preserved. +ALTER INDEX t_merge_extdep_4_i_idx DEPENDS ON EXTENSION btree_gin; +ALTER INDEX t_merge_extdep_4_x_idx DEPENDS ON EXTENSION btree_gin; +-- Should fail: dependencies exist +DROP EXTENSION btree_gist; +ERROR: cannot drop extension btree_gist because other objects depend on it +DETAIL: index t_merge_extdep_idx depends on operator class gist_int4_ops for access method gist +index t_merge_extdep_x depends on operator class gist_int4_ops for access method gist +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- Merge partitions +ALTER TABLE t_merge_extdep MERGE PARTITIONS (t_merge_extdep_1, t_merge_extdep_2) INTO t_merge_extdep_merged; +-- Should still fail: dependencies should be preserved on the new partition's index +DROP EXTENSION btree_gist; +ERROR: cannot drop extension btree_gist because other objects depend on it +DETAIL: index t_merge_extdep_idx depends on operator class gist_int4_ops for access method gist +index t_merge_extdep_x depends on operator class gist_int4_ops for access method gist +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- Verify that dependencies for all indexes exists in pg_depend +SELECT relname, 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 ('t_merge_extdep_merged_i_idx', 't_merge_extdep_merged_x_idx') + AND e.extname IN ('btree_gist', 'btree_gin') + AND d.deptype = 'x' +ORDER BY relname, extname; + relname | extname +-----------------------------+------------ + t_merge_extdep_merged_i_idx | btree_gist + t_merge_extdep_merged_x_idx | btree_gin +(2 rows) + +-- Merge a partition with a dependency with a partition that don't have a +-- dependency. The new merged partition should not have a dependency. +ALTER TABLE t_merge_extdep MERGE PARTITIONS (t_merge_extdep_merged, t_merge_extdep_3) INTO t_merge_extdep_merged2; +SELECT 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 ('t_merge_extdep_merged2_i_idx', 't_merge_extdep_merged2_x_idx') + AND e.extname IN ('btree_gin', 'btree_gist') + AND d.deptype = 'x'; + extname +--------- +(0 rows) + +-- Merge partitions with different dependencies and check that the new merged +-- partition only have the dependencies that intersect. +ALTER INDEX t_merge_extdep_merged2_i_idx DEPENDS ON EXTENSION btree_gist; +ALTER INDEX t_merge_extdep_merged2_x_idx DEPENDS ON EXTENSION btree_gin; +ALTER TABLE t_merge_extdep MERGE PARTITIONS (t_merge_extdep_merged2, t_merge_extdep_4) INTO t_merge_extdep_merged3; +SELECT relname, 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 ('t_merge_extdep_merged3_i_idx', 't_merge_extdep_merged3_x_idx') + AND e.extname IN ('btree_gin', 'btree_gist') + AND d.deptype = 'x' +ORDER BY relname, extname; + relname | extname +------------------------------+----------- + t_merge_extdep_merged3_x_idx | btree_gin +(1 row) + +-- Create an index directly on a partition (without a parent partitioned index). +-- Such indexes are not recreated on merge because they have no parent to map to. +-- This test verifies that partition-only indexes don't cause issues during merge. +CREATE INDEX t_merge_extdep_5_y_idx ON t_merge_extdep_5 USING gist(y); +ALTER TABLE t_merge_extdep MERGE PARTITIONS (t_merge_extdep_merged3, t_merge_extdep_5) INTO t_merge_extdep_merged4; +-- The partition-only index is dropped with its partition; no corresponding +-- index exists on the merged partition since there's no parent index. +SELECT relname +FROM pg_class +WHERE relname LIKE 't_merge_extdep_merged4%idx' +ORDER BY relname; + relname +------------------------------ + t_merge_extdep_merged4_i_idx + t_merge_extdep_merged4_x_idx +(2 rows) + +-- Clean up +DROP TABLE t_merge_extdep; +DROP EXTENSION btree_gist; +DROP EXTENSION btree_gin; RESET search_path; -- DROP SCHEMA partitions_merge_schema; diff --git a/src/test/regress/expected/partition_split.out b/src/test/regress/expected/partition_split.out index 43ca299648e..574119c11a7 100644 --- a/src/test/regress/expected/partition_split.out +++ b/src/test/regress/expected/partition_split.out @@ -1589,6 +1589,47 @@ SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = (1 row) DROP TABLE t; +-- +-- Test that extension dependencies on partition indexes are preserved +-- after SPLIT PARTITION. +-- +CREATE EXTENSION citext; +CREATE TABLE t_extdep (i int) PARTITION BY RANGE (i); +CREATE TABLE t_extdep_1_3 PARTITION OF t_extdep FOR VALUES FROM (1) TO (3); +CREATE INDEX t_extdep_idx ON t_extdep (i); +-- Add extension dependency on partition index +ALTER INDEX t_extdep_1_3_i_idx DEPENDS ON EXTENSION citext; +-- Should fail: dependency exists +DROP EXTENSION citext; +ERROR: cannot drop index t_extdep_1_3_i_idx because index t_extdep_idx requires it +HINT: You can drop index t_extdep_idx instead. +-- Split partition +ALTER TABLE t_extdep SPLIT PARTITION t_extdep_1_3 INTO + (PARTITION t_extdep_1 FOR VALUES FROM (1) TO (2), + PARTITION t_extdep_2 FOR VALUES FROM (2) TO (3)); +-- Should still fail: dependencies should be preserved on all new partitions' indexes +DROP EXTENSION citext; +ERROR: cannot drop index t_extdep_2_i_idx because index t_extdep_idx requires it +HINT: You can drop index t_extdep_idx instead. +-- Verify the dependencies exist in pg_depend for both new partitions +SELECT c.relname, COUNT(*) > 0 AS has_ext_dep +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 ('t_extdep_1_i_idx', 't_extdep_2_i_idx') + AND e.extname = 'citext' + AND d.deptype = 'x' +GROUP BY c.relname +ORDER BY c.relname; + relname | has_ext_dep +------------------+------------- + t_extdep_1_i_idx | t + t_extdep_2_i_idx | t +(2 rows) + +-- Clean up +DROP TABLE t_extdep; +DROP EXTENSION citext; RESET search_path; -- DROP SCHEMA partition_split_schema; diff --git a/src/test/regress/sql/partition_merge.sql b/src/test/regress/sql/partition_merge.sql index a211fee2ad1..e01378452bf 100644 --- a/src/test/regress/sql/partition_merge.sql +++ b/src/test/regress/sql/partition_merge.sql @@ -784,6 +784,98 @@ SELECT count(*) FROM t WHERE i = 15 AND g IN (SELECT g + 10 FROM t WHERE i = 5); DROP TABLE t; +-- +-- Test that extension dependencies on partition indexes are preserved +-- after MERGE PARTITIONS. +-- +CREATE EXTENSION IF NOT EXISTS btree_gist; +CREATE EXTENSION IF NOT EXISTS btree_gin; + +CREATE TABLE t_merge_extdep (i int, x int, y int) PARTITION BY RANGE (i); +CREATE TABLE t_merge_extdep_1 PARTITION OF t_merge_extdep FOR VALUES FROM (1) TO (2); +CREATE TABLE t_merge_extdep_2 PARTITION OF t_merge_extdep FOR VALUES FROM (2) TO (3); +CREATE TABLE t_merge_extdep_3 PARTITION OF t_merge_extdep FOR VALUES FROM (3) TO (4); -- Don't have a dependency +CREATE TABLE t_merge_extdep_4 PARTITION OF t_merge_extdep FOR VALUES FROM (4) TO (5); +CREATE TABLE t_merge_extdep_5 PARTITION OF t_merge_extdep FOR VALUES FROM (5) TO (6); +CREATE INDEX t_merge_extdep_idx ON t_merge_extdep USING gist (i); +CREATE INDEX t_merge_extdep_x ON t_merge_extdep USING gist (x); + +-- Add extension dependency for some partition indexes +ALTER INDEX t_merge_extdep_1_i_idx DEPENDS ON EXTENSION btree_gist; +ALTER INDEX t_merge_extdep_1_x_idx DEPENDS ON EXTENSION btree_gin; +ALTER INDEX t_merge_extdep_2_i_idx DEPENDS ON EXTENSION btree_gist; +ALTER INDEX t_merge_extdep_2_x_idx DEPENDS ON EXTENSION btree_gin; + +-- Add a different extension dependency to ensure that only indexes with the +-- same dependencies has the dependencies preserved. +ALTER INDEX t_merge_extdep_4_i_idx DEPENDS ON EXTENSION btree_gin; +ALTER INDEX t_merge_extdep_4_x_idx DEPENDS ON EXTENSION btree_gin; + +-- Should fail: dependencies exist +DROP EXTENSION btree_gist; + +-- Merge partitions +ALTER TABLE t_merge_extdep MERGE PARTITIONS (t_merge_extdep_1, t_merge_extdep_2) INTO t_merge_extdep_merged; + +-- Should still fail: dependencies should be preserved on the new partition's index +DROP EXTENSION btree_gist; + +-- Verify that dependencies for all indexes exists in pg_depend +SELECT relname, 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 ('t_merge_extdep_merged_i_idx', 't_merge_extdep_merged_x_idx') + AND e.extname IN ('btree_gist', 'btree_gin') + AND d.deptype = 'x' +ORDER BY relname, extname; + +-- Merge a partition with a dependency with a partition that don't have a +-- dependency. The new merged partition should not have a dependency. +ALTER TABLE t_merge_extdep MERGE PARTITIONS (t_merge_extdep_merged, t_merge_extdep_3) INTO t_merge_extdep_merged2; + +SELECT 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 ('t_merge_extdep_merged2_i_idx', 't_merge_extdep_merged2_x_idx') + AND e.extname IN ('btree_gin', 'btree_gist') + AND d.deptype = 'x'; + +-- Merge partitions with different dependencies and check that the new merged +-- partition only have the dependencies that intersect. +ALTER INDEX t_merge_extdep_merged2_i_idx DEPENDS ON EXTENSION btree_gist; +ALTER INDEX t_merge_extdep_merged2_x_idx DEPENDS ON EXTENSION btree_gin; +ALTER TABLE t_merge_extdep MERGE PARTITIONS (t_merge_extdep_merged2, t_merge_extdep_4) INTO t_merge_extdep_merged3; + +SELECT relname, 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 ('t_merge_extdep_merged3_i_idx', 't_merge_extdep_merged3_x_idx') + AND e.extname IN ('btree_gin', 'btree_gist') + AND d.deptype = 'x' +ORDER BY relname, extname; + +-- Create an index directly on a partition (without a parent partitioned index). +-- Such indexes are not recreated on merge because they have no parent to map to. +-- This test verifies that partition-only indexes don't cause issues during merge. +CREATE INDEX t_merge_extdep_5_y_idx ON t_merge_extdep_5 USING gist(y); +ALTER TABLE t_merge_extdep MERGE PARTITIONS (t_merge_extdep_merged3, t_merge_extdep_5) INTO t_merge_extdep_merged4; + +-- The partition-only index is dropped with its partition; no corresponding +-- index exists on the merged partition since there's no parent index. +SELECT relname +FROM pg_class +WHERE relname LIKE 't_merge_extdep_merged4%idx' +ORDER BY relname; + +-- Clean up +DROP TABLE t_merge_extdep; +DROP EXTENSION btree_gist; +DROP EXTENSION btree_gin; + + RESET search_path; -- diff --git a/src/test/regress/sql/partition_split.sql b/src/test/regress/sql/partition_split.sql index 44fcf208ac6..22563d3804c 100644 --- a/src/test/regress/sql/partition_split.sql +++ b/src/test/regress/sql/partition_split.sql @@ -1127,6 +1127,45 @@ SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = DROP TABLE t; +-- +-- Test that extension dependencies on partition indexes are preserved +-- after SPLIT PARTITION. +-- +CREATE EXTENSION citext; + +CREATE TABLE t_extdep (i int) PARTITION BY RANGE (i); +CREATE TABLE t_extdep_1_3 PARTITION OF t_extdep FOR VALUES FROM (1) TO (3); +CREATE INDEX t_extdep_idx ON t_extdep (i); + +-- Add extension dependency on partition index +ALTER INDEX t_extdep_1_3_i_idx DEPENDS ON EXTENSION citext; + +-- Should fail: dependency exists +DROP EXTENSION citext; + +-- Split partition +ALTER TABLE t_extdep SPLIT PARTITION t_extdep_1_3 INTO + (PARTITION t_extdep_1 FOR VALUES FROM (1) TO (2), + PARTITION t_extdep_2 FOR VALUES FROM (2) TO (3)); + +-- Should still fail: dependencies should be preserved on all new partitions' indexes +DROP EXTENSION citext; + +-- Verify the dependencies exist in pg_depend for both new partitions +SELECT c.relname, COUNT(*) > 0 AS has_ext_dep +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 ('t_extdep_1_i_idx', 't_extdep_2_i_idx') + AND e.extname = 'citext' + AND d.deptype = 'x' +GROUP BY c.relname +ORDER BY c.relname; + +-- Clean up +DROP TABLE t_extdep; +DROP EXTENSION citext; + RESET search_path; -- 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.53.0