From 8152062c9c0cc0e98eff2fe939abe5eb1f2bd5cc Mon Sep 17 00:00:00 2001 From: kenxx Date: Wed, 16 Sep 2026 21:00:49 +0800 Subject: [PATCH v1 1/2] Refactor unique-index GROUP BY key matching --- src/backend/optimizer/path/indxpath.c | 65 ++++++++++++++++++++ src/backend/optimizer/plan/initsplan.c | 82 ++------------------------ src/include/optimizer/paths.h | 15 +++++ 3 files changed, 85 insertions(+), 77 deletions(-) diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 3f5d4fa318..a4cd25d15f 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -4285,6 +4285,71 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, return false; } +/* + * unique_index_keys_match_groupby_cols + * Test whether an immediate unique index proves uniqueness under the + * equality semantics of the given GROUP BY columns. + * + * The caller passes simple GROUP BY Vars belonging to rel. For each index key + * column, there must be a GROUP BY Var on the same column whose mergejoin + * opfamilies include the index opfamily and whose collation agrees on + * equality. A NULLS DISTINCT index additionally requires every key column to + * be NOT NULL. + * + * If index_attnos isn't NULL, it is set to the heap attribute numbers of the + * matched index key columns. This allows callers to compare the key against a + * set of grouping columns. + */ +bool +unique_index_keys_match_groupby_cols(IndexOptInfo *index, RelOptInfo *rel, + List *groupbycols, + Bitmapset **index_attnos) +{ + if (index_attnos) + *index_attnos = NULL; + + /* + * Only an immediate, unconditional unique index proves that the input is + * unique. Expression and partial indexes cannot prove whole-relation + * uniqueness. Skip hypothetical indexes because they do not prove a + * property of the physical relation. + */ + if (!index->unique || !index->immediate || index->indpred != NIL || + index->indexprs != NIL || index->hypothetical) + return false; + + for (int i = 0; i < index->nkeycolumns; i++) + { + AttrNumber indkey = index->indexkeys[i]; + ListCell *lc; + + if (indkey <= 0 || + (!index->nullsnotdistinct && + !bms_is_member(indkey, rel->notnullattnums))) + return false; + + foreach(lc, groupbycols) + { + GroupByColInfo *info = (GroupByColInfo *) lfirst(lc); + + if (info->attno == indkey && + list_member_oid(info->eq_opfamilies, index->opfamily[i]) && + collations_agree_on_equality(index->indexcollations[i], + info->coll)) + break; + } + if (lc == NULL) + return false; + + if (index_attnos) + *index_attnos = bms_add_member(*index_attnos, + indkey - + FirstLowInvalidHeapAttributeNumber); + } + + return true; +} + /* * indexcol_is_bool_constant_for_query * diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 8893e37c8f..f6d976e813 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -81,19 +81,6 @@ typedef struct JoinTreeItem * lateral references */ } JoinTreeItem; -/* - * Compatibility info for one GROUP BY item, precomputed for use by - * remove_useless_groupby_columns() when matching unique-index columns against - * GROUP BY items. - */ -typedef struct GroupByColInfo -{ - AttrNumber attno; /* var->varattno */ - List *eq_opfamilies; /* mergejoin opfamilies of sgc->eqop */ - Oid coll; /* var->varcollid */ -} GroupByColInfo; - - static bool is_partial_agg_memory_risky(PlannerInfo *root); static void create_agg_clause_infos(PlannerInfo *root); static void create_grouping_expr_infos(PlannerInfo *root); @@ -488,73 +475,14 @@ remove_useless_groupby_columns(PlannerInfo *root) foreach_node(IndexOptInfo, index, rel->indexlist) { Bitmapset *ind_attnos; - bool index_check_ok; /* - * Skip any non-unique and deferrable indexes. Predicate indexes - * have not been checked yet, so we must skip those too as the - * predOK check that's done later might fail. + * Check that this is a usable unique index and that each key + * column agrees with a GROUP BY column's equality semantics. */ - if (!index->unique || !index->immediate || index->indpred != NIL) - continue; - - /* For simplicity, we currently don't support expression indexes */ - if (index->indexprs != NIL) - continue; - - ind_attnos = NULL; - index_check_ok = true; - for (int i = 0; i < index->nkeycolumns; i++) - { - AttrNumber indkey_attno = index->indexkeys[i]; - Oid indkey_opfamily = index->opfamily[i]; - Oid indkey_coll = index->indexcollations[i]; - ListCell *lc2; - - /* - * We must insist that the index columns are all defined NOT - * NULL otherwise duplicate NULLs could exist. However, we - * can relax this check when the index is defined with NULLS - * NOT DISTINCT as there can only be 1 NULL row, therefore - * functional dependency on the unique columns is maintained, - * despite the NULL. - */ - if (!index->nullsnotdistinct && - !bms_is_member(indkey_attno, rel->notnullattnums)) - { - index_check_ok = false; - break; - } - - /* - * The index proves uniqueness only under its own opfamily and - * collation. Require some GROUP BY item on this column to - * use a compatible eqop and collation, the same check - * relation_has_unique_index_for() applies to join clauses. - */ - foreach(lc2, groupbycols[relid]) - { - GroupByColInfo *info = (GroupByColInfo *) lfirst(lc2); - - if (info->attno != indkey_attno) - continue; - if (list_member_oid(info->eq_opfamilies, indkey_opfamily) && - collations_agree_on_equality(indkey_coll, info->coll)) - break; - } - if (lc2 == NULL) - { - index_check_ok = false; - break; - } - - ind_attnos = - bms_add_member(ind_attnos, - indkey_attno - - FirstLowInvalidHeapAttributeNumber); - } - - if (!index_check_ok) + if (!unique_index_keys_match_groupby_cols(index, rel, + groupbycols[relid], + &ind_attnos)) continue; /* diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index d3853d1c07..3285bd77af 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -79,10 +79,25 @@ extern void generate_partitionwise_join_paths(PlannerInfo *root, * indxpath.c * routines to generate index paths */ +/* + * Compatibility info for one GROUP BY item, used when matching unique-index + * columns against GROUP BY items. + */ +typedef struct GroupByColInfo +{ + AttrNumber attno; /* var->varattno */ + List *eq_opfamilies; /* mergejoin opfamilies of sgc->eqop */ + Oid coll; /* var->varcollid */ +} GroupByColInfo; + extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel); extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, List *restrictlist, List **extra_clauses); +extern bool unique_index_keys_match_groupby_cols(IndexOptInfo *index, + RelOptInfo *rel, + List *groupbycols, + Bitmapset **index_attnos); extern bool indexcol_is_bool_constant_for_query(PlannerInfo *root, IndexOptInfo *index, int indexcol); -- 2.43.0