From ce447f4413b1794203a0b089568b6b28fedc365f Mon Sep 17 00:00:00 2001 From: Zizhuan Liu <44973863@qq.com> Date: Wed, 22 Jul 2026 20:56:46 +0800 Subject: [PATCH v9] v9 enhance-index-support-for-virtual-generated-columns --- src/backend/access/heap/heapam_handler.c | 20 +- src/backend/bootstrap/bootstrap.c | 6 + src/backend/catalog/index.c | 70 ++++-- src/backend/catalog/indexing.c | 2 + src/backend/catalog/toasting.c | 4 + src/backend/commands/analyze.c | 10 +- src/backend/commands/indexcmds.c | 43 +--- src/backend/executor/execIndexing.c | 13 +- src/backend/nodes/makefuncs.c | 10 +- src/backend/optimizer/path/costsize.c | 6 + src/backend/optimizer/path/indxpath.c | 22 +- src/backend/optimizer/plan/createplan.c | 6 +- src/backend/optimizer/util/plancat.c | 52 ++-- src/backend/utils/adt/selfuncs.c | 16 +- src/backend/utils/cache/relcache.c | 114 +++++++++ src/include/nodes/execnodes.h | 4 + src/include/nodes/pathnodes.h | 2 + src/include/utils/rel.h | 2 + src/include/utils/relcache.h | 4 + .../regress/expected/generated_virtual.out | 233 +++++++++++++++--- src/test/regress/expected/indexing.out | 48 ++-- src/test/regress/sql/generated_virtual.sql | 88 ++++--- src/test/regress/sql/indexing.sql | 24 +- 23 files changed, 601 insertions(+), 198 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index bf87430c..bd0db25e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -1153,7 +1153,7 @@ heapam_index_build_range_scan(Relation heapRelation, Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; double reltuples; - ExprState *predicate; + ExprState *predicateExpand; TupleTableSlot *slot; EState *estate; ExprContext *econtext; @@ -1194,7 +1194,7 @@ heapam_index_build_range_scan(Relation heapRelation, econtext->ecxt_scantuple = slot; /* Set up execution state for predicate, if any. */ - predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); + predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate); /* * Prepare for scan of the base relation. In a normal index build, we use @@ -1601,9 +1601,9 @@ heapam_index_build_range_scan(Relation heapRelation, * In a partial index, discard tuples that don't satisfy the * predicate. */ - if (predicate != NULL) + if (predicateExpand != NULL) { - if (!ExecQual(predicate, econtext)) + if (!ExecQual(predicateExpand, econtext)) continue; } @@ -1703,7 +1703,9 @@ heapam_index_build_range_scan(Relation heapRelation, /* These may have been pointing to the now-gone estate */ indexInfo->ii_ExpressionsState = NIL; + indexInfo->ii_ExpressionsExpandState = NIL; indexInfo->ii_PredicateState = NULL; + indexInfo->ii_PredicateExpandState = NULL; return reltuples; } @@ -1720,7 +1722,7 @@ heapam_index_validate_scan(Relation heapRelation, HeapTuple heapTuple; Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; - ExprState *predicate; + ExprState *predicateExpand; TupleTableSlot *slot; EState *estate; ExprContext *econtext; @@ -1752,7 +1754,7 @@ heapam_index_validate_scan(Relation heapRelation, econtext->ecxt_scantuple = slot; /* Set up execution state for predicate, if any. */ - predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); + predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate); /* * Prepare for scan of the base relation. We need just those tuples @@ -1889,9 +1891,9 @@ heapam_index_validate_scan(Relation heapRelation, * In a partial index, discard tuples that don't satisfy the * predicate. */ - if (predicate != NULL) + if (predicateExpand != NULL) { - if (!ExecQual(predicate, econtext)) + if (!ExecQual(predicateExpand, econtext)) continue; } @@ -1946,7 +1948,9 @@ heapam_index_validate_scan(Relation heapRelation, /* These may have been pointing to the now-gone estate */ indexInfo->ii_ExpressionsState = NIL; + indexInfo->ii_ExpressionsExpandState = NIL; indexInfo->ii_PredicateState = NULL; + indexInfo->ii_PredicateExpandState = NULL; } /* diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index a678f345..c26d06bf 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -1156,11 +1156,17 @@ index_register(Oid heap, /* expressions will likely be null, but may as well copy it */ newind->il_info->ii_Expressions = copyObject(indexInfo->ii_Expressions); + newind->il_info->ii_ExpressionsExpand = + copyObject(indexInfo->ii_ExpressionsExpand); newind->il_info->ii_ExpressionsState = NIL; + newind->il_info->ii_ExpressionsExpandState = NIL; /* predicate will likely be null, but may as well copy it */ newind->il_info->ii_Predicate = copyObject(indexInfo->ii_Predicate); + newind->il_info->ii_PredicateExpand = + copyObject(indexInfo->ii_PredicateExpand); newind->il_info->ii_PredicateState = NULL; + newind->il_info->ii_PredicateExpandState = NULL; /* no exclusion constraints at bootstrap time, so no need to copy */ Assert(indexInfo->ii_ExclusionOps == NULL); Assert(indexInfo->ii_ExclusionProcs == NULL); diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 31ef84d0..2ea60181 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1403,6 +1403,10 @@ index_create_copy(Relation heapRelation, uint16 flags, concurrently, /* concurrent */ indexRelation->rd_indam->amsummarizing, oldInfo->ii_WithoutOverlaps); + newInfo->ii_ExpressionsExpand = + ExpandVirtualGeneratedColumns(newInfo->ii_ExpressionsExpand, heapRelation, InvalidOid); + newInfo->ii_PredicateExpand = + ExpandVirtualGeneratedColumns(newInfo->ii_PredicateExpand, heapRelation, InvalidOid); /* fetch exclusion constraint info if any */ if (indexRelation->rd_index->indisexclusion) @@ -2471,6 +2475,8 @@ BuildIndexInfo(Relation index) false, index->rd_indam->amsummarizing, indexStruct->indisexclusion && indexStruct->indisunique); + ii->ii_ExpressionsExpand = (List *)RelationGetIndexExpressionsExpand(index); + ii->ii_PredicateExpand = (List *)RelationGetIndexPredicateExpand(index); /* fill in attribute numbers */ for (i = 0; i < numAtts; i++) @@ -2763,7 +2769,7 @@ FormIndexDatum(IndexInfo *indexInfo, Datum *values, bool *isnull) { - ListCell *indexpr_item; + ListCell *indexprExpand_item; int i; if (indexInfo->ii_Expressions != NIL && @@ -2772,10 +2778,12 @@ FormIndexDatum(IndexInfo *indexInfo, /* First time through, set up expression evaluation state */ indexInfo->ii_ExpressionsState = ExecPrepareExprList(indexInfo->ii_Expressions, estate); + indexInfo->ii_ExpressionsExpandState = + ExecPrepareExprList(indexInfo->ii_ExpressionsExpand, estate); /* Check caller has set up context correctly */ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot); } - indexpr_item = list_head(indexInfo->ii_ExpressionsState); + indexprExpand_item = list_head(indexInfo->ii_ExpressionsExpandState); for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) { @@ -2787,29 +2795,59 @@ FormIndexDatum(IndexInfo *indexInfo, iDatum = slot_getsysattr(slot, keycol, &isNull); else if (keycol != 0) { - /* - * Plain index column; get the value we need directly from the - * heap tuple. - */ - iDatum = slot_getattr(slot, keycol, &isNull); + TupleDesc tupdesc = slot->tts_tupleDescriptor; + Form_pg_attribute att = TupleDescAttr(tupdesc, keycol - 1); + + if (att->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL) + { + /* + * Plain index column; get the value we need directly from the + * heap tuple. + */ + iDatum = slot_getattr(slot, keycol, &isNull); + } + else + { + TupleConstr *constr = tupdesc->constr; + int j; + AttrDefault *defval; + Expr *expr; + ExprState *exprstate; + + Assert(constr->num_defval > 0); + for (j = 0; j < constr->num_defval; j++) + { + defval = &constr->defval[j]; + if (defval->adnum != keycol) + continue; + + expr = stringToNode(defval->adbin); + exprstate = ExecPrepareExpr(expr, estate); + iDatum = ExecEvalExprSwitchContext(exprstate, + GetPerTupleExprContext(estate), + &isNull); + break; + } + Assert(j < constr->num_defval); + } } else { /* * Index expression --- need to evaluate it. */ - if (indexpr_item == NULL) + if (indexprExpand_item == NULL) elog(ERROR, "wrong number of index expressions"); - iDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(indexpr_item), + iDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(indexprExpand_item), GetPerTupleExprContext(estate), &isNull); - indexpr_item = lnext(indexInfo->ii_ExpressionsState, indexpr_item); + indexprExpand_item = lnext(indexInfo->ii_ExpressionsExpandState, indexprExpand_item); } values[i] = iDatum; isnull[i] = isNull; } - if (indexpr_item != NULL) + if (indexprExpand_item != NULL) elog(ERROR, "wrong number of index expressions"); } @@ -3232,7 +3270,7 @@ IndexCheckExclusion(Relation heapRelation, TableScanDesc scan; Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; - ExprState *predicate; + ExprState *predicateExpand; TupleTableSlot *slot; EState *estate; ExprContext *econtext; @@ -3258,7 +3296,7 @@ IndexCheckExclusion(Relation heapRelation, econtext->ecxt_scantuple = slot; /* Set up execution state for predicate, if any. */ - predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); + predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate); /* * Scan all live tuples in the base relation. @@ -3278,9 +3316,9 @@ IndexCheckExclusion(Relation heapRelation, /* * In a partial index, ignore tuples that don't satisfy the predicate. */ - if (predicate != NULL) + if (predicateExpand != NULL) { - if (!ExecQual(predicate, econtext)) + if (!ExecQual(predicateExpand, econtext)) continue; } @@ -3313,7 +3351,9 @@ IndexCheckExclusion(Relation heapRelation, /* These may have been pointing to the now-gone estate */ indexInfo->ii_ExpressionsState = NIL; + indexInfo->ii_ExpressionsExpandState = NIL; indexInfo->ii_PredicateState = NULL; + indexInfo->ii_PredicateExpandState = NULL; } /* diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c index fd7d2ec0..d27aaa09 100644 --- a/src/backend/catalog/indexing.c +++ b/src/backend/catalog/indexing.c @@ -133,7 +133,9 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple, * supported, nor exclusion constraints, nor deferred uniqueness */ Assert(indexInfo->ii_Expressions == NIL); + Assert(indexInfo->ii_ExpressionsExpand == NIL); Assert(indexInfo->ii_Predicate == NIL); + Assert(indexInfo->ii_PredicateExpand == NIL); Assert(indexInfo->ii_ExclusionOps == NULL); Assert(index->rd_index->indimmediate); Assert(indexInfo->ii_NumIndexKeyAttrs != 0); diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 4aa52a4b..daebfda1 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -298,9 +298,13 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, indexInfo->ii_IndexAttrNumbers[0] = 1; indexInfo->ii_IndexAttrNumbers[1] = 2; indexInfo->ii_Expressions = NIL; + indexInfo->ii_ExpressionsExpand = NIL; indexInfo->ii_ExpressionsState = NIL; + indexInfo->ii_ExpressionsExpandState = NIL; indexInfo->ii_Predicate = NIL; + indexInfo->ii_PredicateExpand = NIL; indexInfo->ii_PredicateState = NULL; + indexInfo->ii_PredicateExpandState = NULL; indexInfo->ii_ExclusionOps = NULL; indexInfo->ii_ExclusionProcs = NULL; indexInfo->ii_ExclusionStrats = NULL; diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index f66e80b7..fc67c24a 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -899,7 +899,7 @@ compute_index_stats(Relation onerel, double totalrows, TupleTableSlot *slot; EState *estate; ExprContext *econtext; - ExprState *predicate; + ExprState *predicateExpand; Datum *exprvals; bool *exprnulls; int numindexrows, @@ -908,7 +908,7 @@ compute_index_stats(Relation onerel, double totalrows, double totalindexrows; /* Ignore index if no columns to analyze and not partial */ - if (attr_cnt == 0 && indexInfo->ii_Predicate == NIL) + if (attr_cnt == 0 && indexInfo->ii_PredicateExpand == NIL) continue; /* @@ -926,7 +926,7 @@ compute_index_stats(Relation onerel, double totalrows, econtext->ecxt_scantuple = slot; /* Set up execution state for predicate. */ - predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); + predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate); /* Compute and save index expression values */ exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum)); @@ -949,9 +949,9 @@ compute_index_stats(Relation onerel, double totalrows, ExecStoreHeapTuple(heapTuple, slot, false); /* If index is partial, check predicate */ - if (predicate != NULL) + if (predicateExpand != NULL) { - if (!ExecQual(predicate, econtext)) + if (!ExecQual(predicateExpand, econtext)) continue; } numindexrows++; diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 713bb5d1..4df55c97 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -942,6 +942,8 @@ DefineIndex(ParseState *pstate, concurrent, amissummarizing, stmt->iswithoutoverlaps); + indexInfo->ii_PredicateExpand = + ExpandVirtualGeneratedColumns(indexInfo->ii_PredicateExpand, rel, InvalidOid); typeIds = palloc_array(Oid, numberOfAttributes); collationIds = palloc_array(Oid, numberOfAttributes); @@ -1119,9 +1121,6 @@ DefineIndex(ParseState *pstate, /* * We disallow indexes on system columns. They would not necessarily get * updated correctly, and they don't seem useful anyway. - * - * Also disallow virtual generated columns in indexes (use expression - * index instead). */ for (int i = 0; i < indexInfo->ii_NumIndexAttrs; i++) { @@ -1131,27 +1130,14 @@ DefineIndex(ParseState *pstate, ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("index creation on system columns is not supported"))); - - - if (attno > 0 && - TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - stmt->primary ? - errmsg("primary keys on virtual generated columns are not supported") : - stmt->isconstraint ? - errmsg("unique constraints on virtual generated columns are not supported") : - errmsg("indexes on virtual generated columns are not supported")); } /* - * Also check for system and generated columns used in expressions or - * predicates. + * Also check for system columns used in expressions or predicates. */ if (indexInfo->ii_Expressions || indexInfo->ii_Predicate) { Bitmapset *indexattrs = NULL; - int j; pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs); pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs); @@ -1164,25 +1150,6 @@ DefineIndex(ParseState *pstate, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("index creation on system columns is not supported"))); } - - /* - * XXX Virtual generated columns in index expressions or predicates - * could be supported, but it needs support in - * RelationGetIndexExpressions() and RelationGetIndexPredicate(). - */ - j = -1; - while ((j = bms_next_member(indexattrs, j)) >= 0) - { - AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber; - - if (attno > 0 && - TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - stmt->isconstraint ? - errmsg("unique constraints on virtual generated columns are not supported") : - errmsg("indexes on virtual generated columns are not supported"))); - } } /* Is index safe for others to ignore? See set_indexsafe_procflags() */ @@ -2284,6 +2251,10 @@ ComputeIndexAttrs(ParseState *pstate, attn++; } + + indexInfo->ii_ExpressionsExpand = + ExpandVirtualGeneratedColumns(copyObject(indexInfo->ii_Expressions), + NULL, relId); } /* diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c index eb383812..b47daaf5 100644 --- a/src/backend/executor/execIndexing.c +++ b/src/backend/executor/execIndexing.c @@ -380,20 +380,24 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo, if (indexInfo->ii_Predicate != NIL) { ExprState *predicate; + ExprState *predicateExpand; /* * If predicate state not set up yet, create it (in the estate's * per-query context) */ predicate = indexInfo->ii_PredicateState; + predicateExpand = indexInfo->ii_PredicateExpandState; if (predicate == NULL) { predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); + predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate); indexInfo->ii_PredicateState = predicate; + indexInfo->ii_PredicateExpandState = predicateExpand; } /* Skip this index-update if the predicate isn't satisfied */ - if (!ExecQual(predicate, econtext)) + if (!ExecQual(predicateExpand, econtext)) continue; } @@ -616,20 +620,24 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, if (indexInfo->ii_Predicate != NIL) { ExprState *predicate; + ExprState *predicateExpand; /* * If predicate state not set up yet, create it (in the estate's * per-query context) */ predicate = indexInfo->ii_PredicateState; + predicateExpand = indexInfo->ii_PredicateExpandState; if (predicate == NULL) { predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); + predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate); indexInfo->ii_PredicateState = predicate; + indexInfo->ii_PredicateExpandState = predicateExpand; } /* Skip this index-update if the predicate isn't satisfied */ - if (!ExecQual(predicate, econtext)) + if (!ExecQual(predicateExpand, econtext)) continue; } @@ -1102,6 +1110,7 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate, * pass hint. */ idxExprs = RelationGetIndexExpressions(indexRelation); + idxExprs = list_concat(idxExprs, RelationGetIndexExpressionsExpand(indexRelation)); hasexpression = index_expression_changed_walker((Node *) idxExprs, allUpdatedCols); list_free(idxExprs); diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index cdc02b27..00136507 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -828,7 +828,11 @@ make_ands_implicit(Expr *clause) /* * makeIndexInfo - * create an IndexInfo node + * create an IndexInfo node. + * Upon returning from this function, callers must apply + * ExpandVirtualGeneratedColumns() or RelationGetIndexExpressionsExpand + * or RelationGetIndexPredicateExpand() to ii_ExpressionsExpand and + * ii_PredicateExpand as needed for actual expansion when they are not NIL or dummy. */ IndexInfo * makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions, @@ -856,11 +860,15 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions, /* expressions */ n->ii_Expressions = expressions; + n->ii_ExpressionsExpand = copyObject(expressions); n->ii_ExpressionsState = NIL; + n->ii_ExpressionsExpandState = NIL; /* predicates */ n->ii_Predicate = predicates; + n->ii_PredicateExpand = copyObject(predicates); n->ii_PredicateState = NULL; + n->ii_PredicateExpandState = NULL; /* exclusion constraints */ n->ii_ExclusionOps = NULL; diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index ac523ecf..aed8fe60 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -596,6 +596,12 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count, else { path->path.rows = baserel->rows; + /* + * If index->predOK holds true, this index path's estimated + * output tuples can not exceed the index's total tuples. + */ + if (index->predOK && path->path.rows > index->tuples) + path->path.rows = index->tuples; /* qpquals come from just the rel's restriction clauses */ qpquals = extract_nonindex_conditions(path->indexinfo->indrestrictinfo, path->indexclauses); diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 3f5d4fa3..23117e4a 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -267,7 +267,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) * (generate_bitmap_or_paths() might be able to do something with * them, but that's of no concern here.) */ - if (index->indpred != NIL && !index->predOK) + if (index->indpredExpand != NIL && !index->predOK) continue; /* @@ -1119,7 +1119,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, * just scanning the predOK index alone, no OR.) */ useful_predicate = false; - if (index->indpred != NIL) + if (index->indpredExpand != NIL) { if (index->predOK) { @@ -1131,10 +1131,10 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, if (all_clauses == NIL) all_clauses = list_concat_copy(clauses, other_clauses); - if (!predicate_implied_by(index->indpred, all_clauses, false)) + if (!predicate_implied_by(index->indpredExpand, all_clauses, false)) continue; /* can't use it at all */ - if (!predicate_implied_by(index->indpred, other_clauses, false)) + if (!predicate_implied_by(index->indpredExpand, other_clauses, false)) useful_predicate = true; } } @@ -2183,7 +2183,7 @@ find_indexpath_quals(Path *bitmapqual, List **quals, List **preds) *quals = lappend(*quals, iclause->rinfo->clause); } - *preds = list_concat(*preds, ipath->indexinfo->indpred); + *preds = list_concat(*preds, ipath->indexinfo->indpredExpand); } else elog(ERROR, "unrecognized node type: %d", nodeTag(bitmapqual)); @@ -3959,7 +3959,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel) IndexOptInfo *index = (IndexOptInfo *) lfirst(lc); index->indrestrictinfo = rel->baserestrictinfo; - if (index->indpred) + if (index->indpredExpand) have_partial = true; } if (!have_partial) @@ -4038,11 +4038,11 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel) IndexOptInfo *index = (IndexOptInfo *) lfirst(lc); ListCell *lcr; - if (index->indpred == NIL) + if (index->indpredExpand == NIL) continue; /* ignore non-partial indexes here */ if (!index->predOK) /* don't repeat work if already proven OK */ - index->predOK = predicate_implied_by(index->indpred, clauselist, + index->predOK = predicate_implied_by(index->indpredExpand, clauselist, false); /* If rel is an update target, leave indrestrictinfo as set above */ @@ -4068,7 +4068,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel) /* predicate_implied_by() assumes first arg is immutable */ if (contain_mutable_functions((Node *) rinfo->clause) || !predicate_implied_by(list_make1(rinfo->clause), - index->indpred, false)) + index->indpredExpand, false)) index->indrestrictinfo = lappend(index->indrestrictinfo, rinfo); } } @@ -4402,14 +4402,14 @@ match_index_to_operand(Node *operand, int i; Node *indexkey; - indexpr_item = list_head(index->indexprs); + indexpr_item = list_head(index->indexprsExpand); for (i = 0; i < indexcol; i++) { if (index->indexkeys[i] == 0) { if (indexpr_item == NULL) elog(ERROR, "wrong number of index expressions"); - indexpr_item = lnext(index->indexprs, indexpr_item); + indexpr_item = lnext(index->indexprsExpand, indexpr_item); } } if (indexpr_item == NULL) diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 2c696ea0..36a98f41 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -3346,7 +3346,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, subindexECs = lappend(subindexECs, rinfo->parent_ec); } /* We can add any index predicate conditions, too */ - foreach(l, ipath->indexinfo->indpred) + foreach(l, ipath->indexinfo->indpredExpand) { Expr *pred = (Expr *) lfirst(l); @@ -5169,7 +5169,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol) } /* It's an index expression, so find and cross-check the expression */ - indexpr_item = list_head(index->indexprs); + indexpr_item = list_head(index->indexprsExpand); for (pos = 0; pos < index->ncolumns; pos++) { if (index->indexkeys[pos] == 0) @@ -5194,7 +5194,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol) else elog(ERROR, "index key does not match expected index column"); } - indexpr_item = lnext(index->indexprs, indexpr_item); + indexpr_item = lnext(index->indexprsExpand, indexpr_item); } } diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 7c4be174..07c7627e 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -433,24 +433,38 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * properly reduced. */ info->indexprs = RelationGetIndexExpressions(indexRelation); + info->indexprsExpand = RelationGetIndexExpressionsExpand(indexRelation); info->indpred = RelationGetIndexPredicate(indexRelation); + info->indpredExpand = RelationGetIndexPredicateExpand(indexRelation); if (info->indexprs) { if (varno != 1) + { ChangeVarNodes((Node *) info->indexprs, 1, varno, 0); + ChangeVarNodes((Node *) info->indexprsExpand, 1, varno, 0); + } info->indexprs = (List *) eval_const_expressions(root, (Node *) info->indexprs); + info->indexprsExpand = (List *) + eval_const_expressions(root, (Node *) info->indexprsExpand); } if (info->indpred) { if (varno != 1) + { ChangeVarNodes((Node *) info->indpred, 1, varno, 0); + ChangeVarNodes((Node *) info->indpredExpand, 1, varno, 0); + } info->indpred = (List *) eval_const_expressions(root, (Node *) make_ands_explicit(info->indpred)); + info->indpredExpand = (List *) + eval_const_expressions(root, + (Node *) make_ands_explicit(info->indpredExpand)); info->indpred = make_ands_implicit((Expr *) info->indpred); + info->indpredExpand = make_ands_implicit((Expr *) info->indpredExpand); } /* Build targetlist using the completed indexprs data */ @@ -941,8 +955,8 @@ infer_arbiter_indexes(PlannerInfo *root) attno - FirstLowInvalidHeapAttributeNumber); } - inferElems = RelationGetIndexExpressions(idxRel); - inferIndexExprs = RelationGetIndexPredicate(idxRel); + inferElems = RelationGetIndexExpressionsExpand(idxRel); + inferIndexExprs = RelationGetIndexPredicateExpand(idxRel); break; } } @@ -956,8 +970,8 @@ infer_arbiter_indexes(PlannerInfo *root) { Form_pg_index idxForm; Bitmapset *indexedAttrs; - List *idxExprs; - List *predExprs; + List *idxExpandExprs; + List *predExpandExprs; AttrNumber natt; bool match; @@ -1072,13 +1086,13 @@ infer_arbiter_indexes(PlannerInfo *root) continue; /* Expression attributes (if any) must match */ - idxExprs = RelationGetIndexExpressions(idxRel); - if (idxExprs) + idxExpandExprs = RelationGetIndexExpressionsExpand(idxRel); + if (idxExpandExprs) { if (varno != 1) - ChangeVarNodes((Node *) idxExprs, 1, varno, 0); + ChangeVarNodes((Node *) idxExpandExprs, 1, varno, 0); - idxExprs = (List *) eval_const_expressions(root, (Node *) idxExprs); + idxExpandExprs = (List *) eval_const_expressions(root, (Node *) idxExpandExprs); } /* @@ -1095,7 +1109,7 @@ infer_arbiter_indexes(PlannerInfo *root) * this for both expressions and ordinary (non-expression) * attributes appearing as inference elements. */ - if (!infer_collation_opclass_match(elem, idxRel, idxExprs)) + if (!infer_collation_opclass_match(elem, idxRel, idxExpandExprs)) { match = false; break; @@ -1117,7 +1131,7 @@ infer_arbiter_indexes(PlannerInfo *root) */ if (elem->infercollid != InvalidOid || elem->inferopclass != InvalidOid || - list_member(idxExprs, elem->expr)) + list_member(idxExpandExprs, elem->expr)) continue; match = false; @@ -1136,19 +1150,19 @@ infer_arbiter_indexes(PlannerInfo *root) * In case a constraint was named, ensure the candidate has an equal * set of expressions as the named constraint's index. */ - if (list_difference(idxExprs, inferElems) != NIL) + if (list_difference(idxExpandExprs, inferElems) != NIL) continue; - predExprs = RelationGetIndexPredicate(idxRel); - if (predExprs) + predExpandExprs = RelationGetIndexPredicateExpand(idxRel); + if (predExpandExprs) { if (varno != 1) - ChangeVarNodes((Node *) predExprs, 1, varno, 0); + ChangeVarNodes((Node *) predExpandExprs, 1, varno, 0); - predExprs = (List *) + predExpandExprs = (List *) eval_const_expressions(root, - (Node *) make_ands_explicit(predExprs)); - predExprs = make_ands_implicit((Expr *) predExprs); + (Node *) make_ands_explicit(predExpandExprs)); + predExpandExprs = make_ands_implicit((Expr *) predExpandExprs); } /* @@ -1159,12 +1173,12 @@ infer_arbiter_indexes(PlannerInfo *root) */ if (OidIsValid(indexOidFromConstraint)) { - if (list_difference(predExprs, inferIndexExprs) != NIL) + if (list_difference(predExpandExprs, inferIndexExprs) != NIL) continue; } else { - if (!predicate_implied_by(predExprs, inferIndexExprs, false)) + if (!predicate_implied_by(predExpandExprs, inferIndexExprs, false)) continue; } diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 2b4e6acf..8284ec60 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -7531,6 +7531,7 @@ genericcostestimate(PlannerInfo *root, double qual_arg_cost; List *selectivityQuals; ListCell *l; + bool needCalcIndexSelectivity = false; /* * If the index is partial, AND the index predicate with the explicitly @@ -7595,9 +7596,20 @@ genericcostestimate(PlannerInfo *root, * indexSelectivity estimate is tiny. */ if (numIndexTuples > index->tuples) + { numIndexTuples = index->tuples; + needCalcIndexSelectivity = true; + } + /* Recalculate indexSelectivity based on the estimated numIndexTuples */ if (numIndexTuples < 1.0) + { numIndexTuples = 1.0; + indexSelectivity = 0.0; + } + else if (needCalcIndexSelectivity && index->rel->tuples > 0) + { + indexSelectivity = numIndexTuples / index->rel->tuples; + } /* * Estimate the number of index pages that will be retrieved. @@ -7739,10 +7751,10 @@ add_predicate_to_index_quals(IndexOptInfo *index, List *indexQuals) List *predExtraQuals = NIL; ListCell *lc; - if (index->indpred == NIL) + if (index->indpredExpand == NIL) return indexQuals; - foreach(lc, index->indpred) + foreach(lc, index->indpredExpand) { Node *predQual = (Node *) lfirst(lc); List *oneQual = list_make1(predQual); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 19c4ff6e..1b536c3c 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -92,6 +92,7 @@ #include "utils/resowner.h" #include "utils/snapmgr.h" #include "utils/syscache.h" +#include "rewrite/rewriteHandler.h" #define RELCACHE_INIT_FILEMAGIC 0x573266 /* version ID value */ @@ -1587,7 +1588,9 @@ RelationInitIndexAccessInfo(Relation relation) * expressions, predicate, exclusion caches will be filled later */ relation->rd_indexprs = NIL; + relation->rd_indexprsExpand = NIL; relation->rd_indpred = NIL; + relation->rd_indpredExpand = NIL; relation->rd_exclops = NULL; relation->rd_exclprocs = NULL; relation->rd_exclstrats = NULL; @@ -5157,6 +5160,49 @@ RelationGetIndexExpressions(Relation relation) return result; } +/* + * RelationGetIndexExpressionsExpand -- Similar to RelationGetIndexExpressions, + * except that it expands the expressions internally. + */ +List * +RelationGetIndexExpressionsExpand(Relation relation) +{ + List *result; + MemoryContext oldcxt; + bool isnull; + Datum heapRelidDatum; + Oid heapRelid; + + /* Quick exit if we already computed the result. */ + if (relation->rd_indexprsExpand) + return copyObject(relation->rd_indexprsExpand); + + /* Quick exit if there is nothing to do. */ + if (relation->rd_indextuple == NULL || + heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL)) + return NIL; + + result = RelationGetIndexExpressions(relation); + if (result == NIL) + return NIL; + + heapRelidDatum = heap_getattr(relation->rd_indextuple, + Anum_pg_index_indrelid, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + heapRelid = DatumGetObjectId(heapRelidDatum); + + result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid); + + /* Now save a copy of the completed tree in the relcache entry. */ + oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt); + relation->rd_indexprsExpand = copyObject(result); + MemoryContextSwitchTo(oldcxt); + + return result; +} + /* * RelationGetDummyIndexExpressions -- get dummy expressions for an index * @@ -5277,6 +5323,49 @@ RelationGetIndexPredicate(Relation relation) return result; } +/* + * RelationGetIndexPredicateExpand -- Similar to RelationGetIndexPredicate, + * except that it expands the predicate expressions internally. + */ +List * +RelationGetIndexPredicateExpand(Relation relation) +{ + List *result; + MemoryContext oldcxt; + bool isnull; + Datum heapRelidDatum; + Oid heapRelid; + + /* Quick exit if we already computed the result. */ + if (relation->rd_indpredExpand) + return copyObject(relation->rd_indpredExpand); + + /* Quick exit if there is nothing to do. */ + if (relation->rd_indextuple == NULL || + heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred, NULL)) + return NIL; + + result = RelationGetIndexPredicate(relation); + if (result == NIL) + return NIL; + + heapRelidDatum = heap_getattr(relation->rd_indextuple, + Anum_pg_index_indrelid, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + heapRelid = DatumGetObjectId(heapRelidDatum); + + result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid); + + /* Now save a copy of the completed tree in the relcache entry. */ + oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt); + relation->rd_indpredExpand = copyObject(result); + MemoryContextSwitchTo(oldcxt); + + return result; +} + /* * RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers * @@ -6501,7 +6590,9 @@ load_relcache_init_file(bool shared) rel->rd_partcheckvalid = false; rel->rd_partcheckcxt = NULL; rel->rd_indexprs = NIL; + rel->rd_indexprsExpand = NIL; rel->rd_indpred = NIL; + rel->rd_indpredExpand = NIL; rel->rd_exclops = NULL; rel->rd_exclprocs = NULL; rel->rd_exclstrats = NULL; @@ -7030,3 +7121,26 @@ ResOwnerReleaseRelation(Datum res) RelationCloseCleanup((Relation) DatumGetPointer(res)); } + +/* Expands the expression list by invoking expand_generated_columns_in_expr */ +List * +ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId) +{ + bool opened_relation = false; + + if (list == NIL || (heapRelation == NULL && heapRelId == InvalidOid)) + return list; + + if (heapRelation == NULL) + { + heapRelation = table_open(heapRelId, NoLock); + opened_relation = true; + } + + list = (List *)expand_generated_columns_in_expr((Node *)list, heapRelation, 1); + + if (opened_relation) + table_close(heapRelation, NoLock); + + return list; +} diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index e64fd8c7..dd0aba03 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -190,13 +190,17 @@ typedef struct IndexInfo /* expr trees for expression entries, or NIL if none */ List *ii_Expressions; /* list of Expr */ + List *ii_ExpressionsExpand; /* list of Expr */ /* exec state for expressions, or NIL if none */ List *ii_ExpressionsState; /* list of ExprState */ + List *ii_ExpressionsExpandState; /* list of ExprState */ /* partial-index predicate, or NIL if none */ List *ii_Predicate; /* list of Expr */ + List *ii_PredicateExpand; /* list of Expr */ /* exec state for expressions, or NIL if none */ ExprState *ii_PredicateState; + ExprState *ii_PredicateExpandState; /* Per-column exclusion operators, or NULL if none */ Oid *ii_ExclusionOps; /* array with one entry per column */ diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 27a2c681..9c65e123 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1403,8 +1403,10 @@ typedef struct IndexOptInfo * print indextlist */ List *indexprs pg_node_attr(read_write_ignore); + List *indexprsExpand pg_node_attr(read_write_ignore); /* predicate if a partial index, else NIL */ List *indpred; + List *indpredExpand; /* targetlist representing index columns */ List *indextlist; diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 89c159b1..0101c561 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -210,7 +210,9 @@ typedef struct RelationData struct FmgrInfo *rd_supportinfo; /* lookup info for support procedures */ int16 *rd_indoption; /* per-column AM-specific flags */ List *rd_indexprs; /* index expression trees, if any */ + List *rd_indexprsExpand; /* expanded index expression trees, if any */ List *rd_indpred; /* index predicate tree, if any */ + List *rd_indpredExpand; /* expanded index predicate tree, if any */ Oid *rd_exclops; /* OIDs of exclusion operators, if any */ Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */ uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */ diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index 89c27aa1..70531bb8 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -58,8 +58,10 @@ extern List *RelationGetStatExtList(Relation relation); extern Oid RelationGetPrimaryKeyIndex(Relation relation, bool deferrable_ok); extern Oid RelationGetReplicaIndex(Relation relation); extern List *RelationGetIndexExpressions(Relation relation); +extern List *RelationGetIndexExpressionsExpand(Relation relation); extern List *RelationGetDummyIndexExpressions(Relation relation); extern List *RelationGetIndexPredicate(Relation relation); +extern List *RelationGetIndexPredicateExpand(Relation relation); extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy); /* @@ -161,4 +163,6 @@ extern PGDLLIMPORT bool criticalRelcachesBuilt; /* should be used only by relcache.c and postinit.c */ extern PGDLLIMPORT bool criticalSharedRelcachesBuilt; +extern List *ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId); + #endif /* RELCACHE_H */ diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out index 6ee02979..d8a424bf 100644 --- a/src/test/regress/expected/generated_virtual.out +++ b/src/test/regress/expected/generated_virtual.out @@ -814,39 +814,202 @@ ERROR: column "c" of relation "gtestnn_child" contains null values ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 4) + nullif(f2, 6)) VIRTUAL; -- ok -- index constraints CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) VIRTUAL UNIQUE); -ERROR: unique constraints on virtual generated columns are not supported ---INSERT INTO gtest22a VALUES (2); ---INSERT INTO gtest22a VALUES (3); ---INSERT INTO gtest22a VALUES (4); +INSERT INTO gtest22a VALUES (2); -- ok +INSERT INTO gtest22a VALUES (3); -- error +ERROR: duplicate key value violates unique constraint "gtest22a_b_key" +DETAIL: Key (b)=(1) already exists. +INSERT INTO gtest22a VALUES (4); -- ok CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) VIRTUAL, PRIMARY KEY (a, b)); -ERROR: primary keys on virtual generated columns are not supported ---INSERT INTO gtest22b VALUES (2); ---INSERT INTO gtest22b VALUES (2); +INSERT INTO gtest22b VALUES (2); -- ok +INSERT INTO gtest22b VALUES (2); -- error +ERROR: duplicate key value violates unique constraint "gtest22b_pkey" +DETAIL: Key (a, b)=(2, 1) already exists. -- indexes CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL); ---CREATE INDEX gtest22c_b_idx ON gtest22c (b); ---CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); ---CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; ---\d gtest22c ---INSERT INTO gtest22c VALUES (1), (2), (3); ---SET enable_seqscan TO off; ---SET enable_bitmapscan TO off; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4; ---SELECT * FROM gtest22c WHERE b = 4; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6; ---SELECT * FROM gtest22c WHERE b * 3 = 6; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; ---SELECT * FROM gtest22c WHERE a = 1 AND b > 0; ---ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4); ---ANALYZE gtest22c; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8; ---SELECT * FROM gtest22c WHERE b = 8; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12; ---SELECT * FROM gtest22c WHERE b * 3 = 12; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; ---SELECT * FROM gtest22c WHERE a = 1 AND b > 0; ---RESET enable_seqscan; ---RESET enable_bitmapscan; +CREATE INDEX gtest22c_b_idx ON gtest22c (b); -- ok +CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); -- ok +CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; -- ok +\d gtest22c + Table "generated_virtual_tests.gtest22c" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+----------------------------- + a | integer | | | + b | integer | | | generated always as (a * 2) +Indexes: + "gtest22c_b_idx" btree (b) + "gtest22c_expr_idx" btree ((b * 3)) + "gtest22c_pred_idx" btree (a) WHERE b > 0 + +INSERT INTO gtest22c VALUES (1), (2), (3); +SET enable_seqscan TO off; +SET enable_bitmapscan TO off; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4; + QUERY PLAN +----------------------------------------------------- + Index Only Scan using gtest22c_pred_idx on gtest22c + Filter: ((a * 2) = 4) +(2 rows) + +SELECT * FROM gtest22c WHERE b = 4; + a | b +---+--- + 2 | 4 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6; + QUERY PLAN +------------------------------------------------ + Index Scan using gtest22c_expr_idx on gtest22c + Index Cond: (((a * 2) * 3) = 6) +(2 rows) + +SELECT * FROM gtest22c WHERE b * 3 = 6; + a | b +---+--- + 1 | 2 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; + QUERY PLAN +----------------------------------------------------- + Index Only Scan using gtest22c_pred_idx on gtest22c + Index Cond: (a = 1) +(2 rows) + +SELECT * FROM gtest22c WHERE a = 1 AND b > 0; + a | b +---+--- + 1 | 2 +(1 row) + +ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4); +ANALYZE gtest22c; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8; + QUERY PLAN +----------------------------------------------------- + Index Only Scan using gtest22c_pred_idx on gtest22c + Filter: ((a * 4) = 8) +(2 rows) + +SELECT * FROM gtest22c WHERE b = 8; + a | b +---+--- + 2 | 8 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12; + QUERY PLAN +------------------------------------------------ + Index Scan using gtest22c_expr_idx on gtest22c + Index Cond: (((a * 4) * 3) = 12) +(2 rows) + +SELECT * FROM gtest22c WHERE b * 3 = 12; + a | b +---+--- + 1 | 4 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; + QUERY PLAN +----------------------------------------------------- + Index Only Scan using gtest22c_pred_idx on gtest22c + Index Cond: (a = 1) +(2 rows) + +SELECT * FROM gtest22c WHERE a = 1 AND b > 0; + a | b +---+--- + 1 | 4 +(1 row) + +CREATE TABLE gtest22d (a int + ,v1 int GENERATED ALWAYS AS (a * 11) VIRTUAL + ,v2 int GENERATED ALWAYS AS (a * 22) VIRTUAL + ,v3 int GENERATED ALWAYS AS (a * 33) VIRTUAL + ,v4 int GENERATED ALWAYS AS (a * 44) VIRTUAL +); -- ok +insert into gtest22d select oid from pg_catalog.pg_class union all select -1; -- ok +CREATE INDEX gtest22d_vgc_idx ON gtest22d(a,v1,abs(v2)) Include(v3) WHERE v4 < 100; -- ok +\d gtest22d + Table "generated_virtual_tests.gtest22d" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------ + a | integer | | | + v1 | integer | | | generated always as (a * 11) + v2 | integer | | | generated always as (a * 22) + v3 | integer | | | generated always as (a * 33) + v4 | integer | | | generated always as (a * 44) +Indexes: + "gtest22d_vgc_idx" btree (a, v1, abs(v2)) INCLUDE (v3) WHERE v4 < 100 + +analyze gtest22d; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 < 100; --Index Only Scan or Index Scan + QUERY PLAN +---------------------------------------------------- + Index Only Scan using gtest22d_vgc_idx on gtest22d +(1 row) + +SELECT * FROM gtest22d WHERE v4 < 100; + a | v1 | v2 | v3 | v4 +----+-----+-----+-----+----- + -1 | -11 | -22 | -33 | -44 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 * 5 < 100; --Seq Scan + QUERY PLAN +---------------------------------- + Seq Scan on gtest22d + Disabled: true + Filter: (((a * 44) * 5) < 100) +(3 rows) + +SELECT * FROM gtest22d WHERE v4 * 5 < 100; + a | v1 | v2 | v3 | v4 +----+-----+-----+-----+----- + -1 | -11 | -22 | -33 | -44 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE a * 44 < 100; --Index Only Scan or Index Scan + QUERY PLAN +---------------------------------------------------- + Index Only Scan using gtest22d_vgc_idx on gtest22d +(1 row) + +SELECT * FROM gtest22d WHERE a * 44 < 100; + a | v1 | v2 | v3 | v4 +----+-----+-----+-----+----- + -1 | -11 | -22 | -33 | -44 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 44) < 100; --Index Only Scan or Index Scan + QUERY PLAN +---------------------------------------------------- + Index Only Scan using gtest22d_vgc_idx on gtest22d +(1 row) + +SELECT * FROM gtest22d WHERE (a * 44) < 100; + a | v1 | v2 | v3 | v4 +----+-----+-----+-----+----- + -1 | -11 | -22 | -33 | -44 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 33) < 100; --Seq Scan + QUERY PLAN +---------------------------- + Seq Scan on gtest22d + Disabled: true + Filter: ((a * 33) < 100) +(3 rows) + +SELECT * FROM gtest22d WHERE (a * 33) < 100; + a | v1 | v2 | v3 | v4 +----+-----+-----+-----+----- + -1 | -11 | -22 | -33 | -44 +(1 row) + +RESET enable_seqscan; +RESET enable_bitmapscan; -- foreign keys CREATE TABLE gtest23a (x int PRIMARY KEY, y int); --INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33); @@ -864,12 +1027,12 @@ ERROR: foreign key constraints on virtual generated columns are not supported --DROP TABLE gtest23b; --DROP TABLE gtest23a; CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) VIRTUAL, PRIMARY KEY (y)); -ERROR: primary keys on virtual generated columns are not supported ---INSERT INTO gtest23p VALUES (1), (2), (3); +INSERT INTO gtest23p VALUES (1), (2), (3); -- ok CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y)); -ERROR: relation "gtest23p" does not exist ---INSERT INTO gtest23q VALUES (1, 2); -- ok ---INSERT INTO gtest23q VALUES (2, 5); -- error +INSERT INTO gtest23q VALUES (1, 2); -- ok +INSERT INTO gtest23q VALUES (2, 5); -- error +ERROR: insert or update on table "gtest23q" violates foreign key constraint "gtest23q_b_fkey" +DETAIL: Key (b)=(5) is not present in table "gtest23p". -- domains CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10); CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) VIRTUAL); diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out index 4a0a652e..0d16c59e 100644 --- a/src/test/regress/expected/indexing.out +++ b/src/test/regress/expected/indexing.out @@ -1785,18 +1785,36 @@ insert into test_pg_wholerow_index values (2, 'addition', 0); drop index row_image_index; drop function row_image(test_pg_wholerow_index); drop table test_pg_wholerow_index; --- Test of a partitioned index attach, when there are exclusion constraints. -create table idx_excl_part (a int4range, b int4range) partition by list (a); -create table idx_excl_part_1 (a int4range, b int4range); -alter table only idx_excl_part attach partition idx_excl_part_1 for values in ('[0,1)'::int4range); -alter table only idx_excl_part add constraint idxpart_id_data_excl exclude using gist (a with =, b with &&); -alter table idx_excl_part_1 add constraint idxpart_1_id_data_excl exclude using gist (a with &&, b with &&); --- This should be disallowed, because the constraints don't match. -alter index idxpart_id_data_excl attach partition idxpart_1_id_data_excl; -ERROR: cannot attach index "idxpart_1_id_data_excl" as a partition of index "idxpart_id_data_excl" -DETAIL: The index definitions do not match. --- but if we recreate the constraint differently, it's allowed: -alter table idx_excl_part_1 drop constraint idxpart_1_id_data_excl; -alter table idx_excl_part_1 add constraint idxpart_1_id_data_excl exclude using gist (a with =, b with &&); -alter index idxpart_id_data_excl attach partition idxpart_1_id_data_excl; --- leave these tables around, for pg_upgrade testing +-- test creation of a predicate index with a whole-row expression +DROP TABLE IF EXISTS test_pg_wholerow; +NOTICE: table "test_pg_wholerow" does not exist, skipping +CREATE TABLE test_pg_wholerow(a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL, c text); +CREATE UNIQUE INDEX test_pg_wholerow_pred_idx ON test_pg_wholerow(a) WHERE test_pg_wholerow IS NOT NULL; +INSERT INTO test_pg_wholerow(a,c) SELECT NULL, NULL FROM generate_series(1, 1000); +INSERT INTO test_pg_wholerow(a,c) SELECT 1, '1'; +ANALYZE test_pg_wholerow; +SELECT relname,reltuples FROM pg_catalog.pg_class WHERE relname IN ('test_pg_wholerow', 'test_pg_wholerow_pred_idx') ORDER BY OID; + relname | reltuples +---------------------------+----------- + test_pg_wholerow | 1001 + test_pg_wholerow_pred_idx | 1 +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_pg_wholerow WHERE test_pg_wholerow IS NOT NULL; + QUERY PLAN +---------------------------------------------------------------- + Index Scan using test_pg_wholerow_pred_idx on test_pg_wholerow +(1 row) + +EXPLAIN (COSTS OFF) SELECT a FROM test_pg_wholerow WHERE test_pg_wholerow IS NOT NULL; + QUERY PLAN +--------------------------------------------------------------------- + Index Only Scan using test_pg_wholerow_pred_idx on test_pg_wholerow +(1 row) + +EXPLAIN (COSTS OFF) SELECT a,b FROM test_pg_wholerow WHERE test_pg_wholerow IS NOT NULL; + QUERY PLAN +--------------------------------------------------------------------- + Index Only Scan using test_pg_wholerow_pred_idx on test_pg_wholerow +(1 row) + diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index ed9d50fe..1d062d96 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -427,40 +427,62 @@ ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif -- index constraints CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) VIRTUAL UNIQUE); ---INSERT INTO gtest22a VALUES (2); ---INSERT INTO gtest22a VALUES (3); ---INSERT INTO gtest22a VALUES (4); +INSERT INTO gtest22a VALUES (2); -- ok +INSERT INTO gtest22a VALUES (3); -- error +INSERT INTO gtest22a VALUES (4); -- ok CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) VIRTUAL, PRIMARY KEY (a, b)); ---INSERT INTO gtest22b VALUES (2); ---INSERT INTO gtest22b VALUES (2); +INSERT INTO gtest22b VALUES (2); -- ok +INSERT INTO gtest22b VALUES (2); -- error -- indexes CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL); ---CREATE INDEX gtest22c_b_idx ON gtest22c (b); ---CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); ---CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; ---\d gtest22c - ---INSERT INTO gtest22c VALUES (1), (2), (3); ---SET enable_seqscan TO off; ---SET enable_bitmapscan TO off; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4; ---SELECT * FROM gtest22c WHERE b = 4; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6; ---SELECT * FROM gtest22c WHERE b * 3 = 6; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; ---SELECT * FROM gtest22c WHERE a = 1 AND b > 0; - ---ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4); ---ANALYZE gtest22c; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8; ---SELECT * FROM gtest22c WHERE b = 8; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12; ---SELECT * FROM gtest22c WHERE b * 3 = 12; ---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; ---SELECT * FROM gtest22c WHERE a = 1 AND b > 0; ---RESET enable_seqscan; ---RESET enable_bitmapscan; +CREATE INDEX gtest22c_b_idx ON gtest22c (b); -- ok +CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); -- ok +CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; -- ok +\d gtest22c + +INSERT INTO gtest22c VALUES (1), (2), (3); +SET enable_seqscan TO off; +SET enable_bitmapscan TO off; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4; +SELECT * FROM gtest22c WHERE b = 4; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6; +SELECT * FROM gtest22c WHERE b * 3 = 6; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; +SELECT * FROM gtest22c WHERE a = 1 AND b > 0; + +ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4); +ANALYZE gtest22c; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8; +SELECT * FROM gtest22c WHERE b = 8; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12; +SELECT * FROM gtest22c WHERE b * 3 = 12; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; +SELECT * FROM gtest22c WHERE a = 1 AND b > 0; + +CREATE TABLE gtest22d (a int + ,v1 int GENERATED ALWAYS AS (a * 11) VIRTUAL + ,v2 int GENERATED ALWAYS AS (a * 22) VIRTUAL + ,v3 int GENERATED ALWAYS AS (a * 33) VIRTUAL + ,v4 int GENERATED ALWAYS AS (a * 44) VIRTUAL +); -- ok +insert into gtest22d select oid from pg_catalog.pg_class union all select -1; -- ok +CREATE INDEX gtest22d_vgc_idx ON gtest22d(a,v1,abs(v2)) Include(v3) WHERE v4 < 100; -- ok +\d gtest22d +analyze gtest22d; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 < 100; --Index Only Scan or Index Scan +SELECT * FROM gtest22d WHERE v4 < 100; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 * 5 < 100; --Seq Scan +SELECT * FROM gtest22d WHERE v4 * 5 < 100; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE a * 44 < 100; --Index Only Scan or Index Scan +SELECT * FROM gtest22d WHERE a * 44 < 100; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 44) < 100; --Index Only Scan or Index Scan +SELECT * FROM gtest22d WHERE (a * 44) < 100; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 33) < 100; --Seq Scan +SELECT * FROM gtest22d WHERE (a * 33) < 100; + +RESET enable_seqscan; +RESET enable_bitmapscan; -- foreign keys CREATE TABLE gtest23a (x int PRIMARY KEY, y int); @@ -481,11 +503,11 @@ CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) VIRT --DROP TABLE gtest23a; CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) VIRTUAL, PRIMARY KEY (y)); ---INSERT INTO gtest23p VALUES (1), (2), (3); +INSERT INTO gtest23p VALUES (1), (2), (3); -- ok CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y)); ---INSERT INTO gtest23q VALUES (1, 2); -- ok ---INSERT INTO gtest23q VALUES (2, 5); -- error +INSERT INTO gtest23q VALUES (1, 2); -- ok +INSERT INTO gtest23q VALUES (2, 5); -- error -- domains CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10); diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql index bbcfb365..5bc3781a 100644 --- a/src/test/regress/sql/indexing.sql +++ b/src/test/regress/sql/indexing.sql @@ -1006,16 +1006,14 @@ drop index row_image_index; drop function row_image(test_pg_wholerow_index); drop table test_pg_wholerow_index; --- Test of a partitioned index attach, when there are exclusion constraints. -create table idx_excl_part (a int4range, b int4range) partition by list (a); -create table idx_excl_part_1 (a int4range, b int4range); -alter table only idx_excl_part attach partition idx_excl_part_1 for values in ('[0,1)'::int4range); -alter table only idx_excl_part add constraint idxpart_id_data_excl exclude using gist (a with =, b with &&); -alter table idx_excl_part_1 add constraint idxpart_1_id_data_excl exclude using gist (a with &&, b with &&); --- This should be disallowed, because the constraints don't match. -alter index idxpart_id_data_excl attach partition idxpart_1_id_data_excl; --- but if we recreate the constraint differently, it's allowed: -alter table idx_excl_part_1 drop constraint idxpart_1_id_data_excl; -alter table idx_excl_part_1 add constraint idxpart_1_id_data_excl exclude using gist (a with =, b with &&); -alter index idxpart_id_data_excl attach partition idxpart_1_id_data_excl; --- leave these tables around, for pg_upgrade testing +-- test creation of a predicate index with a whole-row expression +DROP TABLE IF EXISTS test_pg_wholerow; +CREATE TABLE test_pg_wholerow(a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL, c text); +CREATE UNIQUE INDEX test_pg_wholerow_pred_idx ON test_pg_wholerow(a) WHERE test_pg_wholerow IS NOT NULL; +INSERT INTO test_pg_wholerow(a,c) SELECT NULL, NULL FROM generate_series(1, 1000); +INSERT INTO test_pg_wholerow(a,c) SELECT 1, '1'; +ANALYZE test_pg_wholerow; +SELECT relname,reltuples FROM pg_catalog.pg_class WHERE relname IN ('test_pg_wholerow', 'test_pg_wholerow_pred_idx') ORDER BY OID; +EXPLAIN (COSTS OFF) SELECT * FROM test_pg_wholerow WHERE test_pg_wholerow IS NOT NULL; +EXPLAIN (COSTS OFF) SELECT a FROM test_pg_wholerow WHERE test_pg_wholerow IS NOT NULL; +EXPLAIN (COSTS OFF) SELECT a,b FROM test_pg_wholerow WHERE test_pg_wholerow IS NOT NULL; -- 2.43.0