diff --git a/contrib/pageinspect/expected/btree.out b/contrib/pageinspect/expected/btree.out index 0aa5d73322f8..56d57848cf70 100644 --- a/contrib/pageinspect/expected/btree.out +++ b/contrib/pageinspect/expected/btree.out @@ -183,6 +183,39 @@ tids | SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 2)); ERROR: block number 2 is out of range for relation "test1_a_idx" +---test index over virtual generated column +CREATE TABLE test3 (a int8, b int4range, c int8 generated always as (a+1) virtual); +INSERT INTO test3 VALUES (72057594037927936, '[0,1)'); +CREATE INDEX test3_a_idx ON test3 USING btree (c); +SELECT * FROM bt_page_items('test3_a_idx', 1); +-[ RECORD 1 ]----------------------- +itemoffset | 1 +ctid | (0,1) +itemlen | 16 +nulls | f +vars | f +data | 01 00 00 00 00 00 00 01 +dead | f +htid | (0,1) +tids | + +--expect zero row. +SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 1)) +EXCEPT ALL +SELECT * FROM bt_page_items(get_raw_page('test3_a_idx', 1)); +(0 rows) + +CREATE TABLE test4(a int, b int GENERATED ALWAYS AS (a + 1), c text); +INSERT INTO test4(a,c) VALUES (10,11), (10,11); +CREATE INDEX test4_b ON test4 USING btree (b); +CREATE INDEX test4_c ON test4 USING btree (c); +ALTER TABLE test4 alter column b set data type text; +---should return zero row. +SELECT * FROM bt_page_items('test4_b', 1) +EXCEPT ALL +SELECT * FROM bt_page_items('test4_c', 1); +(0 rows) + -- Failure when using a non-btree index. CREATE INDEX test1_a_hash ON test1 USING hash(a); SELECT bt_metap('test1_a_hash'); diff --git a/contrib/pageinspect/sql/btree.sql b/contrib/pageinspect/sql/btree.sql index 102ebdefe3c1..d3392c11d5f4 100644 --- a/contrib/pageinspect/sql/btree.sql +++ b/contrib/pageinspect/sql/btree.sql @@ -32,6 +32,27 @@ SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 0)); SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 1)); SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 2)); +---test index over virtual generated column +CREATE TABLE test3 (a int8, b int4range, c int8 generated always as (a+1) virtual); +INSERT INTO test3 VALUES (72057594037927936, '[0,1)'); +CREATE INDEX test3_a_idx ON test3 USING btree (c); +SELECT * FROM bt_page_items('test3_a_idx', 1); + +--expect zero row. +SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 1)) +EXCEPT ALL +SELECT * FROM bt_page_items(get_raw_page('test3_a_idx', 1)); + +CREATE TABLE test4(a int, b int GENERATED ALWAYS AS (a + 1), c text); +INSERT INTO test4(a,c) VALUES (10,11), (10,11); +CREATE INDEX test4_b ON test4 USING btree (b); +CREATE INDEX test4_c ON test4 USING btree (c); +ALTER TABLE test4 alter column b set data type text; +---should return zero row. +SELECT * FROM bt_page_items('test4_b', 1) +EXCEPT ALL +SELECT * FROM bt_page_items('test4_c', 1); + -- Failure when using a non-btree index. CREATE INDEX test1_a_hash ON test1 USING hash(a); SELECT bt_metap('test1_a_hash'); diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index cbd4e40a320b..d7ee73373f56 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -4595,6 +4595,21 @@ SCRAM-SHA-256$<iteration count>:&l + + + indattrgenerated int2vector + (references pg_attribute.attnum) + + + This is an array of indnatts values that + indicate which table virtual generated columns this index indexes. + For example, a value of 1 3 would mean that the first + and the third table columns of this index entries are virtual generated + column. A zero in this array indicates that the corresponding index + attribute is not virtual generated column reference. + + + indexprs pg_node_tree diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 739a92bdcc1c..dd379c2b79c5 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -584,6 +584,12 @@ UpdateIndexRelation(Oid indexoid, Relation pg_index; HeapTuple tuple; int i; + int2vector *indgenkey; + int16 *colgenerated; + + colgenerated = palloc_array(int16, indexInfo->ii_NumIndexAttrs); + for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) + colgenerated[i] = indexInfo->ii_IndexAttrGeneratedNumbers[i]; /* * Copy the index key, opclass, and indoption info into arrays (should we @@ -596,6 +602,7 @@ UpdateIndexRelation(Oid indexoid, indclass = buildoidvector(opclassOids, indexInfo->ii_NumIndexKeyAttrs); indoption = buildint2vector(coloptions, indexInfo->ii_NumIndexKeyAttrs); + indgenkey = buildint2vector(colgenerated, indexInfo->ii_NumIndexAttrs); /* * Convert the index expressions (if any) to a text datum */ @@ -653,6 +660,7 @@ UpdateIndexRelation(Oid indexoid, values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation); values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass); values[Anum_pg_index_indoption - 1] = PointerGetDatum(indoption); + values[Anum_pg_index_indattrgenerated - 1] = PointerGetDatum(indgenkey); values[Anum_pg_index_indexprs - 1] = exprsDatum; if (exprsDatum == (Datum) 0) nulls[Anum_pg_index_indexprs - 1] = true; @@ -1134,6 +1142,28 @@ index_create(Relation heapRelation, } } + /* + * Internally, we convert index of virtual generation column into an + * expression index. For example, if column 'b' is defined as (b INT + * GENERATED ALWAYS AS (a * 2) VIRTUAL) then index over 'b' would + * transformed into an expression index as ((a * 2)). As a result, + * the pg_depend refobjsubid does not retain the original attribute + * number of the virtual generated column. But we need rebuild any + * index that was build on virtual generated column. so we need auto + * dependencies on referenced virtual generated columns. + */ + for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) + { + if (indexInfo->ii_IndexAttrGeneratedNumbers[i] != 0) + { + ObjectAddressSubSet(referenced, RelationRelationId, + heapRelationId, + indexInfo->ii_IndexAttrGeneratedNumbers[i]); + add_exact_object_address(&referenced, addrs); + have_simple_col = false; + } + } + /* * If there are no simply-referenced columns, give the index an * auto dependency on the whole table. In most cases, this will @@ -2428,9 +2458,12 @@ IndexInfo * BuildIndexInfo(Relation index) { IndexInfo *ii; + HeapTuple ht_idx; Form_pg_index indexStruct = index->rd_index; int i; int numAtts; + Datum indgenkeyDatum; + int2vector *indgenkey; /* check the number of keys, and copy attr numbers into the IndexInfo */ numAtts = indexStruct->indnatts; @@ -2454,9 +2487,19 @@ BuildIndexInfo(Relation index) index->rd_indam->amsummarizing, indexStruct->indisexclusion && indexStruct->indisunique); + ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexStruct->indexrelid)); + indgenkeyDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx, + Anum_pg_index_indattrgenerated); + indgenkey = (int2vector *) DatumGetPointer(indgenkeyDatum); + /* fill in attribute numbers */ for (i = 0; i < numAtts; i++) + { ii->ii_IndexAttrNumbers[i] = indexStruct->indkey.values[i]; + ii->ii_IndexAttrGeneratedNumbers[i] = indgenkey->values[i]; + } + + ReleaseSysCache(ht_idx); /* fetch exclusion constraint info if any */ if (indexStruct->indisexclusion) @@ -2523,6 +2566,23 @@ BuildDummyIndexInfo(Relation index) return ii; } +/* + * IndexOverVirtualGenerated + * Return whether this index is built on virtual generated column. + */ +bool +IsIndexOverVirtualGenerated(const IndexInfo *info) +{ + int i; + + for (i = 0; i < info->ii_NumIndexAttrs; i++) + { + if (AttributeNumberIsValid(info->ii_IndexAttrGeneratedNumbers[i])) + return true; + } + return false; +} + /* * CompareIndexInfo * Return whether the properties of two indexes (in different tables) @@ -2585,6 +2645,15 @@ CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2, return false; } + if (AttributeNumberIsValid(info1->ii_IndexAttrGeneratedNumbers[i]) || + AttributeNumberIsValid(info2->ii_IndexAttrGeneratedNumbers[i])) + { + /* fail if index over virtual generated column attribute does not match */ + if (attmap->attnums[info2->ii_IndexAttrGeneratedNumbers[i] - 1] != + info1->ii_IndexAttrGeneratedNumbers[i]) + return false; + } + /* collation and opfamily are not valid for included columns */ if (i >= info1->ii_NumIndexKeyAttrs) continue; diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index d962fe392cd2..b8079250f95a 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -54,6 +54,7 @@ #include "parser/parse_utilcmd.h" #include "partitioning/partdesc.h" #include "pgstat.h" +#include "rewrite/rewriteHandler.h" #include "rewrite/rewriteManip.h" #include "storage/lmgr.h" #include "storage/proc.h" @@ -90,9 +91,15 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo, bool amcanorder, bool isconstraint, bool iswithoutoverlaps, + bool is_primary, Oid ddl_userid, int ddl_sec_context, int *ddl_save_nestlevel); +static void compute_generated_indexattrs(IndexInfo *indexInfo, + Relation rel, + bool is_primary, + int attn, + int attnum); static char *ChooseIndexName(const char *tabname, Oid namespaceId, const List *colnames, const List *exclusionOpNames, bool primary, bool isconstraint); @@ -182,6 +189,7 @@ CheckIndexCompatible(Oid oldId, bool isWithoutOverlaps) { bool isconstraint; + bool is_primary; Oid *typeIds; Oid *collationIds; Oid *opclassIds; @@ -214,6 +222,12 @@ CheckIndexCompatible(Oid oldId, */ isconstraint = false; + /* + * We can pretend is_primary = false unconditionally. It only serves to + * decide the text of an error message that should never happen for us. + */ + is_primary = false; + numberOfAttributes = list_length(attributeList); Assert(numberOfAttributes > 0); Assert(numberOfAttributes <= INDEX_MAX_KEYS); @@ -254,7 +268,7 @@ CheckIndexCompatible(Oid oldId, coloptions, attributeList, exclusionOpNames, relationId, accessMethodName, accessMethodId, - amcanorder, isconstraint, isWithoutOverlaps, InvalidOid, + amcanorder, isconstraint, isWithoutOverlaps, is_primary, InvalidOid, 0, NULL); /* Get the soon-obsolete pg_index tuple. */ @@ -905,6 +919,31 @@ DefineIndex(Oid tableId, if (stmt->whereClause) CheckPredicate((Expr *) stmt->whereClause); + /* virtual generated column over predicate indexes are not supported */ + if (RelationGetDescr(rel)->constr && + RelationGetDescr(rel)->constr->has_generated_virtual && + stmt->whereClause) + { + Bitmapset *indexattrs_pred = NULL; + int j; + + pull_varattnos(stmt->whereClause, 1, &indexattrs_pred); + + j = -1; + while ((j = bms_next_member(indexattrs_pred, j)) >= 0) + { + AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber; + + if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) + { + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("partial index on virtual generated columns are not supported")); + break; + } + } + } + /* * Parse AM-specific options, convert to text array form, validate. */ @@ -941,6 +980,7 @@ DefineIndex(Oid tableId, stmt->excludeOpNames, tableId, accessMethodName, accessMethodId, amcanorder, stmt->isconstraint, stmt->iswithoutoverlaps, + stmt->primary, root_save_userid, root_save_sec_context, &root_save_nestlevel); @@ -1102,9 +1142,6 @@ DefineIndex(Oid tableId, /* * 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++) { @@ -1114,26 +1151,14 @@ DefineIndex(Oid tableId, ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("index creation on system columns is not supported"))); - - - if (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); @@ -1146,24 +1171,6 @@ DefineIndex(Oid tableId, (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 (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() */ @@ -1307,6 +1314,7 @@ DefineIndex(Oid tableId, bool invalidate_parent = false; Relation parentIndex; TupleDesc parentDesc; + bool parent_idx_virtual; /* * Report the total number of partitions at the start of the @@ -1353,6 +1361,8 @@ DefineIndex(Oid tableId, parentIndex = index_open(indexRelationId, lockmode); indexInfo = BuildIndexInfo(parentIndex); + parent_idx_virtual = IsIndexOverVirtualGenerated(indexInfo); + parentDesc = RelationGetDescr(rel); /* @@ -1412,6 +1422,14 @@ DefineIndex(Oid tableId, parentDesc, false); + /* + * child don't have any index, but parent have index over + * virtual generated column. We need ensure the indexed + * generated expression on parent match with child. + */ + if (childidxs == NIL && parent_idx_virtual) + check_generated_indexattrs(indexInfo, rel, childrel, attmap, false); + foreach(cell, childidxs) { Oid cldidxid = lfirst_oid(cell); @@ -1481,6 +1499,23 @@ DefineIndex(Oid tableId, index_close(cldidx, NoLock); break; } + else + { + bool cldidx_virtual; + bool index_virtual; + index_virtual = IsIndexOverVirtualGenerated(indexInfo); + cldidx_virtual = IsIndexOverVirtualGenerated(cldIdxInfo); + + /* should fail. otherwise pg_dump won't work */ + if (index_virtual || cldidx_virtual) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create index on partitioned table \"%s\"", + RelationGetRelationName(rel)), + errdetail("The index definition of partitioned table \"%s\" does not match table \"%s\"", + RelationGetRelationName(rel), + RelationGetRelationName(childrel))); + } index_close(cldidx, lockmode); } @@ -1857,6 +1892,72 @@ CheckPredicate(Expr *predicate) errmsg("functions in index predicate must be marked IMMUTABLE"))); } +/* + * rel_idx_info: the IndexInfo that is associated with rel. + * "childrel": the relation to be attached to "rel" or the child of "rel". + * attmap: Attribute mapping between childrel and rel. + * is_attach: is this command of ALTER TABLE ATTACH PARTITION + * + * Use build_attrmap_by_name(childrel, rel) to build the attmap. +*/ +void check_generated_indexattrs(const IndexInfo *rel_idx_info, + Relation rel, + Relation childrel, + const AttrMap *attmap, + bool is_attach) +{ + int i; + + /* if parent have virtual generated column, child must also have */ + Assert(rel->rd_att->constr->has_generated_virtual); + Assert(childrel->rd_att->constr->has_generated_virtual); + + for (i = 0; i < rel_idx_info->ii_NumIndexAttrs; i++) + { + if (AttributeNumberIsValid(rel_idx_info->ii_IndexAttrGeneratedNumbers[i])) + { + Node *node_rel; + Node *node_attach; + AttrNumber attno; + bool found_whole_row; + + attno = rel_idx_info->ii_IndexAttrGeneratedNumbers[i]; + + node_rel = build_generation_expression(rel, attno); + node_rel = map_variable_attnos(node_rel, + 1, 0, + attmap, + InvalidOid, &found_whole_row); + if (found_whole_row) + elog(ERROR, "Index contains a whole-row table reference"); + + node_attach = build_generation_expression(childrel, + attmap->attnums[attno - 1]); + + if (!equal(node_rel, node_attach)) + { + if (is_attach) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach table \"%s\" as partition of partitioned table \"%s\"", + RelationGetRelationName(childrel), + RelationGetRelationName(rel)); + errdetail("The index definition of partitioned table \"%s\" does not match table \"%s\"", + RelationGetRelationName(rel), + RelationGetRelationName(childrel))); + else + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create index on partitioned table \"%s\"", + RelationGetRelationName(rel)), + errdetail("The index definition of partitioned table \"%s\" does not match table \"%s\"", + RelationGetRelationName(rel), + RelationGetRelationName(childrel))); + } + } + } +} + /* * Compute per-index-column information, including indexed column numbers * or index expressions, opclasses and their options. Note, all output vectors @@ -1881,6 +1982,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, bool amcanorder, bool isconstraint, bool iswithoutoverlaps, + bool is_primary, Oid ddl_userid, int ddl_sec_context, int *ddl_save_nestlevel) @@ -1891,6 +1993,28 @@ ComputeIndexAttrs(IndexInfo *indexInfo, int nkeycols = indexInfo->ii_NumIndexKeyAttrs; Oid save_userid; int save_sec_context; + Relation rel; + TupleDesc reltupldesc; + List *virtual_generated = NIL; + + rel = table_open(relId, NoLock); + reltupldesc = RelationGetDescr(rel); + + /* + * currently, we do not support virtual generated columns over expression + * indexes. we accumulate the attribute number of virtual generated columns + * so we can verify it later. + */ + if (reltupldesc->constr && reltupldesc->constr->has_generated_virtual) + { + for (int i = 0; i < reltupldesc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(reltupldesc, i); + + if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) + virtual_generated = lappend_int(virtual_generated, attr->attnum); + } + } /* Allocate space for exclusion operator info, if needed */ if (exclusionOpNames) @@ -1933,6 +2057,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, IndexElem *attribute = (IndexElem *) lfirst(lc); Oid atttype; Oid attcollation; + char attgenerated = '\0'; /* * Process the column-or-expression to be indexed. @@ -1942,6 +2067,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, /* Simple index attribute */ HeapTuple atttuple; Form_pg_attribute attform; + AttrNumber attnum; Assert(attribute->expr == NULL); atttuple = SearchSysCacheAttName(relId, attribute->name); @@ -1960,10 +2086,17 @@ ComputeIndexAttrs(IndexInfo *indexInfo, attribute->name))); } attform = (Form_pg_attribute) GETSTRUCT(atttuple); + attnum = attform->attnum; indexInfo->ii_IndexAttrNumbers[attn] = attform->attnum; atttype = attform->atttypid; attcollation = attform->attcollation; + attgenerated = attform->attgenerated; ReleaseSysCache(atttuple); + + if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) + compute_generated_indexattrs(indexInfo, rel, is_primary, attn, attnum); + else + indexInfo->ii_IndexAttrGeneratedNumbers[attn] = 0; } else { @@ -1986,18 +2119,42 @@ ComputeIndexAttrs(IndexInfo *indexInfo, while (IsA(expr, CollateExpr)) expr = (Node *) ((CollateExpr *) expr)->arg; + if (!IsA(expr, Var)) + { + Bitmapset *idxattrs = NULL; + int j = -1; + + pull_varattnos(expr, 1, &idxattrs); + while ((j = bms_next_member(idxattrs, j)) >= 0) + { + AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber; + if (list_member_int(virtual_generated, attno)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("expression index over virtual generated columns are not supported")); + } + } + if (IsA(expr, Var) && ((Var *) expr)->varattno != InvalidAttrNumber) { + int attnum = ((Var *) expr)->varattno; + /* * User wrote "(column)" or "(column COLLATE something)". * Treat it like simple attribute anyway. */ - indexInfo->ii_IndexAttrNumbers[attn] = ((Var *) expr)->varattno; + indexInfo->ii_IndexAttrNumbers[attn] = attnum; + + if (list_member_int(virtual_generated, attnum)) + compute_generated_indexattrs(indexInfo, rel, is_primary, attn, attnum); + else + indexInfo->ii_IndexAttrGeneratedNumbers[attn] = 0; } else { indexInfo->ii_IndexAttrNumbers[attn] = 0; /* marks expression */ + indexInfo->ii_IndexAttrGeneratedNumbers[attn] = 0; indexInfo->ii_Expressions = lappend(indexInfo->ii_Expressions, expr); @@ -2248,6 +2405,78 @@ ComputeIndexAttrs(IndexInfo *indexInfo, attn++; } + table_close(rel, NoLock); +} + +/* + * indexInfo: this IndexInfo to be build. + * rel: the relation this indexInfo is based on. + * is_primary: is this index a primary key. + * attn: indices of the index key attribute, 0 based. + * attnum: virtual generated column attribute number. +*/ +static void +compute_generated_indexattrs(IndexInfo *indexInfo, Relation rel, + bool is_primary, int attn, int attnum) +{ + Node *node; + Bitmapset *genattrs = NULL; + + if (is_primary) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("primary keys on virtual generated columns are not supported")); + + if (indexInfo->ii_Unique) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unique constraints on virtual generated columns are not supported")); + + if (attn >= indexInfo->ii_NumIndexKeyAttrs) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("virtual generated column are not supported in index included columns")); + + /* Fetch the GENERATED AS expression tree */ + node = build_generation_expression(rel, attnum); + + /* + * if the generation expression just reference another Var node, then set + * ii_IndexAttrNumbers to that Var->varattno. + */ + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + Assert(var->varattno > 0); + indexInfo->ii_IndexAttrNumbers[attn] = var->varattno; + } + else + { + /* + * Strip any top-level COLLATE clause. This ensures that we treat + * "x COLLATE y" and "(x COLLATE y)" alike. + */ + while (IsA(node, CollateExpr)) + node = (Node *) ((CollateExpr *) node)->arg; + + /* generation expression are immutable, so this unlikely to happen */ + if (contain_mutable_functions_after_planning((Expr *) node)) + elog(ERROR,"functions in index expression must be marked IMMUTABLE"); + + pull_varattnos(node, 1, &genattrs); + + if (genattrs == NULL) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("can not create index based on variable free generation expression")); + + indexInfo->ii_IndexAttrNumbers[attn] = 0; /* mark as expression index */ + indexInfo->ii_Expressions = lappend(indexInfo->ii_Expressions, + node); + } + + indexInfo->ii_IndexAttrGeneratedNumbers[attn] = attnum; } /* diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 54ad38247aa3..c2f713e0d3d6 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8653,6 +8653,32 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName, */ RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName); } + else + { + Assert(attgenerated == ATTRIBUTE_GENERATED_VIRTUAL); + + RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName); + + /* + * Changing the generation expression of the virtual generated column + * does not require table rewrite. However, if an index is built on top + * of it, table rewrite is necessary. So in phase 3, index_rebuild can + * successfully rebuild the index based on the new generation expression + */ + if (tab->changedIndexOids != NIL) + { + rewrite = true; + + /* + * Clear all the missing values if we're rewriting the table, since + * this renders them pointless. + */ + RelationClearMissing(rel); + + /* make sure we don't conflict with later attribute modifications */ + CommandCounterIncrement(); + } + } /* * Drop the dependency records of the GENERATED expression, in particular @@ -14775,6 +14801,14 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, */ RememberAllDependentForRebuilding(tab, AT_AlterColumnType, rel, attnum, colName); + /* + * tell phase3 do table rewrite if there are any index based on virtual + * generated colum. + */ + if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL && + tab->changedIndexOids != NIL) + tab->rewrite |= AT_REWRITE_COLUMN_REWRITE; + /* * Now scan for dependencies of this column on other things. The only * things we should find are the dependency on the column datatype and @@ -20541,6 +20575,7 @@ AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel) AttrMap *attmap; bool found = false; Oid constraintOid; + bool parent_idx_virtual; /* * Ignore indexes in the partitioned table other than partitioned @@ -20554,9 +20589,19 @@ AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel) /* construct an indexinfo to compare existing indexes against */ info = BuildIndexInfo(idxRel); + parent_idx_virtual = IsIndexOverVirtualGenerated(info); attmap = build_attrmap_by_name(RelationGetDescr(attachrel), RelationGetDescr(rel), false); + + /* + * The attach partition don't have index, but parent have index over + * virtual generated column. We need ensure generated expression on + * parent that index was based on it match with attach partition. + */ + if (attachRelIdxs == NIL && parent_idx_virtual) + check_generated_indexattrs(info, rel, attachrel, attmap, true); + constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx); /* @@ -20615,6 +20660,22 @@ AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel) CommandCounterIncrement(); break; } + else + { + bool attach_idx_virtual; + attach_idx_virtual = IsIndexOverVirtualGenerated(attachInfos[i]); + + /* should fail. different index definition cannot merge */ + if (attach_idx_virtual || parent_idx_virtual) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach table \"%s\" as partition of partitioned table \"%s\"", + RelationGetRelationName(attachrel), + RelationGetRelationName(rel)), + errdetail("The index definition of partitioned table \"%s\" does not match table \"%s\"", + RelationGetRelationName(rel), + RelationGetRelationName(attachrel))); + } } /* diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 62015431fdf1..3c7af2605b3d 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1681,6 +1681,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, Form_pg_am amrec; oidvector *indcollation; oidvector *indclass; + int2vector *indgenkey; IndexStmt *index; List *indexprs; ListCell *indexpr_item; @@ -1688,6 +1689,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, int keyno; Oid keycoltype; Datum datum; + Datum indgenkeyDatum; bool isnull; if (constraintOid) @@ -1723,6 +1725,11 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, datum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx, Anum_pg_index_indclass); indclass = (oidvector *) DatumGetPointer(datum); + /* Extract indattrgenerated from the pg_index tuple */ + indgenkeyDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx, + Anum_pg_index_indattrgenerated); + indgenkey = (int2vector *) DatumGetPointer(indgenkeyDatum); + /* Begin building the IndexStmt */ index = makeNode(IndexStmt); index->relation = heapRel; @@ -1854,13 +1861,29 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, { IndexElem *iparam; AttrNumber attnum = idxrec->indkey.values[keyno]; + AttrNumber gennum = indgenkey->values[keyno]; Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(source_idx), keyno); int16 opt = source_idx->rd_indoption[keyno]; iparam = makeNode(IndexElem); - if (AttributeNumberIsValid(attnum)) + if (AttributeNumberIsValid(gennum)) + { + /* + * index over virtual generated column was converted into a + * expression index, but we need restore the original attribute + * number for recreate it. + */ + char *virtual_attname; + + virtual_attname = get_attname(indrelid, gennum, false); + keycoltype = get_atttype(indrelid, gennum); + + iparam->name = virtual_attname; + iparam->expr = NULL; + } + else if (AttributeNumberIsValid(attnum)) { /* Simple index column */ char *attname; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 3d6e6bdbfd21..14b9c57b9688 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -1290,9 +1290,11 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, Datum indcollDatum; Datum indclassDatum; Datum indoptionDatum; + Datum indgenkeyDatum; oidvector *indcollation; oidvector *indclass; int2vector *indoption; + int2vector *indgenkey; StringInfoData buf; char *str; char *sep; @@ -1325,6 +1327,10 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, Anum_pg_index_indoption); indoption = (int2vector *) DatumGetPointer(indoptionDatum); + indgenkeyDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx, + Anum_pg_index_indattrgenerated); + indgenkey = (int2vector *) DatumGetPointer(indgenkeyDatum); + /* * Fetch the pg_class tuple of the index relation */ @@ -1398,6 +1404,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, for (keyno = 0; keyno < idxrec->indnatts; keyno++) { AttrNumber attnum = idxrec->indkey.values[keyno]; + AttrNumber gennum = indgenkey->values[keyno]; Oid keycoltype; Oid keycolcollation; @@ -1418,7 +1425,26 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, appendStringInfoString(&buf, sep); sep = ", "; - if (attnum != 0) + if (!AttributeNumberIsValid(attnum) && AttributeNumberIsValid(gennum)) + indexpr_item = lnext(indexprs, indexpr_item); + + if (AttributeNumberIsValid(gennum)) + { + /* + * This index is created on virtual generated column + */ + char *virtual_attname; + int32 geneycoltypmod; + + virtual_attname = get_attname(indrelid, gennum, false); + if (!colno || colno == keyno + 1) + appendStringInfoString(&buf, quote_identifier(virtual_attname)); + + get_atttypetypmodcoll(indrelid, gennum, + &keycoltype, &geneycoltypmod, + &keycolcollation); + } + else if (attnum != 0) { /* Simple index column */ char *attname; diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 4daa8bef5eea..a7e93f3a1070 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -126,6 +126,7 @@ extern IndexInfo *BuildIndexInfo(Relation index); extern IndexInfo *BuildDummyIndexInfo(Relation index); +extern bool IsIndexOverVirtualGenerated(const IndexInfo *info); extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2, const Oid *collations1, const Oid *collations2, const Oid *opfamilies1, const Oid *opfamilies2, @@ -175,6 +176,11 @@ extern void RestoreReindexState(const void *reindexstate); extern void IndexSetParentIndex(Relation partitionIdx, Oid parentOid); +extern void check_generated_indexattrs(const IndexInfo *rel_idx_info, + Relation rel, + Relation childrel, + const AttrMap *attmap, + bool is_attach); /* * itemptr_encode - Encode ItemPointer as int64/int8 diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h index 4392b9d221d5..7ed74a593a4b 100644 --- a/src/include/catalog/pg_index.h +++ b/src/include/catalog/pg_index.h @@ -54,6 +54,7 @@ CATALOG(pg_index,2610,IndexRelationId) BKI_SCHEMA_MACRO oidvector indclass BKI_LOOKUP(pg_opclass) BKI_FORCE_NOT_NULL; /* opclass identifiers */ int2vector indoption BKI_FORCE_NOT_NULL; /* per-column flags * (AM-specific meanings) */ + int2vector indattrgenerated BKI_FORCE_NOT_NULL; /* the attribute of virtual generated column? */ pg_node_tree indexprs; /* expression trees for index attributes that * are not simple column references; one for * each zero entry in indkey[] */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 2492282213ff..1637f3072621 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -195,6 +195,7 @@ typedef struct IndexInfo int ii_NumIndexAttrs; /* total number of columns in index */ int ii_NumIndexKeyAttrs; /* number of key columns in index */ AttrNumber ii_IndexAttrNumbers[INDEX_MAX_KEYS]; + AttrNumber ii_IndexAttrGeneratedNumbers[INDEX_MAX_KEYS]; /* XXX more better comments */ List *ii_Expressions; /* list of Expr */ List *ii_ExpressionsState; /* list of ExprState */ List *ii_Predicate; /* list of Expr */ diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out index ccbcdf8403fa..ef19f667cc17 100644 --- a/src/test/regress/expected/fast_default.out +++ b/src/test/regress/expected/fast_default.out @@ -70,6 +70,14 @@ NOTICE: rewriting table has_volatile for reason 4 -- stored generated columns need a rewrite ALTER TABLE has_volatile ADD col7 int GENERATED ALWAYS AS (55) stored; NOTICE: rewriting table has_volatile for reason 2 +-- if there is any index over virtual generated columns, +-- change generation expression need rewrite +CREATE INDEX on has_volatile(col6); +ALTER TABLE has_volatile ALTER COLUMN col6 SET EXPRESSION AS (col1 * 3); +NOTICE: rewriting table has_volatile for reason 2 +-- table rewrite again. +ALTER TABLE has_volatile ALTER COLUMN col6 SET DATA TYPE INT8; +NOTICE: rewriting table has_volatile for reason 4 -- Test a large sample of different datatypes CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1); SELECT set('t'); diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out index 6300e7c1d96e..69ce5cb92c5f 100644 --- a/src/test/regress/expected/generated_virtual.out +++ b/src/test/regress/expected/generated_virtual.out @@ -742,30 +742,175 @@ ERROR: primary keys on virtual generated columns are not supported --INSERT INTO gtest22b VALUES (2); --INSERT INTO gtest22b VALUES (2); -- indexes -CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL); ---CREATE INDEX gtest22c_b_idx ON gtest22c (b); +CREATE TABLE gtestparted (b integer, c integer,a integer GENERATED ALWAYS AS (c+1))PARTITION BY RANGE (b); +CREATE TABLE gtestpart1 (b integer, c integer, a integer GENERATED ALWAYS AS (c)); +CREATE TABLE gtestpart2 (b integer, a integer GENERATED ALWAYS AS (c+1), c integer); +CREATE TABLE gtestpart3 (b integer, c integer, a integer GENERATED ALWAYS AS (c)); +ALTER TABLE gtestparted ATTACH PARTITION gtestpart3 for values from (1) to (10); +--error. partitioned and partition have different generation expression, can not +--build index on it. +CREATE INDEX gtestparted_a_idx_error on gtestparted(a); --error +ERROR: cannot create index on partitioned table "gtestparted" +DETAIL: The index definition of partitioned table "gtestparted" does not match table "gtestpart3" +ALTER TABLE gtestparted DETACH PARTITION gtestpart3; +CREATE INDEX gtestpart1_a_idx on gtestpart1(a); +CREATE INDEX gtestpart2_a_idx on gtestpart2(a); +CREATE INDEX gtestpart2_a_idx_copy on gtestpart2(a); +CREATE INDEX gtestparted_a_idx on gtestparted(a); +--error. index over different generation expression should not allowed +ALTER TABLE gtestparted ATTACH PARTITION gtestpart1 for values from (1) to (10); --error +ERROR: cannot attach table "gtestpart1" as partition of partitioned table "gtestparted" +DETAIL: The index definition of partitioned table "gtestparted" does not match table "gtestpart1" +ALTER TABLE gtestparted ATTACH PARTITION gtestpart2 for values from (1) to (10); --ok +SELECT * FROM pg_partition_tree('gtestparted_a_idx'::regclass); + relid | parentrelid | isleaf | level +-------------------+-------------------+--------+------- + gtestparted_a_idx | | f | 0 + gtestpart2_a_idx | gtestparted_a_idx | t | 1 +(2 rows) + +ALTER INDEX gtestparted_a_idx ATTACH PARTITION gtestpart2_a_idx; --ok +ALTER INDEX gtestparted_a_idx ATTACH PARTITION gtestpart2_a_idx_copy; --error +ERROR: cannot attach index "gtestpart2_a_idx_copy" as a partition of index "gtestparted_a_idx" +DETAIL: Another index is already attached for partition "gtestpart2". +CREATE INDEX gtestparted_a_idx_1 on gtestparted(a); +--now index gtestpart2_a_idx_copy should attach to the partition tree. +SELECT * FROM pg_partition_tree('gtestparted_a_idx_1'::regclass); + relid | parentrelid | isleaf | level +-----------------------+---------------------+--------+------- + gtestparted_a_idx_1 | | f | 0 + gtestpart2_a_idx_copy | gtestparted_a_idx_1 | t | 1 +(2 rows) + +--create table like should work just fine +CREATE TABLE gtestparted_like (LIKE gtestparted including all); +\d gtestparted_like + Table "generated_virtual_tests.gtestparted_like" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+----------------------------- + b | integer | | | + c | integer | | | + a | integer | | | generated always as (c + 1) +Indexes: + "gtestparted_like_a_idx" btree (a) + "gtestparted_like_a_idx1" btree (a) + +CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL + ,c int GENERATED ALWAYS AS (11) VIRTUAL + ,d int GENERATED ALWAYS AS (a *3) VIRTUAL + ,e int4range GENERATED ALWAYS AS (int4range(a, a+10)) VIRTUAL + ,f int GENERATED ALWAYS AS (a) VIRTUAL); +CREATE INDEX gtest22c_b_idx ON gtest22c (b); +--variable free generation expression have no pratical usage, so error out. +CREATE INDEX gtest22c_c_idx ON gtest22c (c); +ERROR: can not create index based on variable free generation expression +CREATE INDEX gtest22c_d_idx ON gtest22c USING hash(d); +CREATE INDEX gtest22c_e_idx ON gtest22c USING gist(e); +--error. include columns are not supported. +CREATE INDEX gtest22c_idx1 ON gtest22c USING btree(a) include (b,c); +ERROR: virtual generated column are not supported in index included columns +CREATE INDEX gtest22c_idx1 ON gtest22c USING btree(a) include (f); +ERROR: virtual generated column are not supported in index included columns --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; +\d gtest22c + Table "generated_virtual_tests.gtest22c" + Column | Type | Collation | Nullable | Default +--------+-----------+-----------+----------+-------------------------------------------- + a | integer | | | + b | integer | | | generated always as (a * 2) + c | integer | | | generated always as (11) + d | integer | | | generated always as (a * 3) + e | int4range | | | generated always as (int4range(a, a + 10)) + f | integer | | | generated always as (a) +Indexes: + "gtest22c_b_idx" btree (b) + "gtest22c_d_idx" hash (d) + "gtest22c_e_idx" gist (e) + +INSERT INTO gtest22c(a) VALUES (1), (2), (3), (10); +SET enable_seqscan TO off; +SET enable_bitmapscan TO off; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4; + QUERY PLAN +--------------------------------------------- + Index Scan using gtest22c_b_idx on gtest22c + Index Cond: ((a * 2) = 4) +(2 rows) + +SELECT * FROM gtest22c WHERE b = 4; + a | b | c | d | e | f +---+---+----+---+--------+--- + 2 | 4 | 11 | 6 | [2,12) | 2 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE d = 6; + QUERY PLAN +--------------------------------------------- + Index Scan using gtest22c_d_idx on gtest22c + Index Cond: ((a * 3) = 6) +(2 rows) + +SELECT * FROM gtest22c WHERE d = 6; + a | b | c | d | e | f +---+---+----+---+--------+--- + 2 | 4 | 11 | 6 | [2,12) | 2 +(1 row) + +EXPLAIN (COSTS OFF) SELECT count(*) FROM gtest22c WHERE e @> 12; + QUERY PLAN +---------------------------------------------------- + Aggregate + -> Index Scan using gtest22c_e_idx on gtest22c + Index Cond: (int4range(a, (a + 10)) @> 12) +(3 rows) + +SELECT count(*) from gtest22c where e @> 12; + count +------- + 2 +(1 row) + +--column drop then the index over that column should also being dropped +ALTER TABLE gtest22c DROP COLUMN e; +\d gtest22c + Table "generated_virtual_tests.gtest22c" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+----------------------------- + a | integer | | | + b | integer | | | generated always as (a * 2) + c | integer | | | generated always as (11) + d | integer | | | generated always as (a * 3) + f | integer | | | generated always as (a) +Indexes: + "gtest22c_b_idx" btree (b) + "gtest22c_d_idx" hash (d) + --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; +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 Scan using gtest22c_b_idx on gtest22c + Index Cond: ((a * 4) = 8) +(2 rows) + +SELECT * FROM gtest22c WHERE b = 8; + a | b | c | d | f +---+---+----+---+--- + 2 | 8 | 11 | 6 | 2 +(1 row) + --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; +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); @@ -1592,3 +1737,17 @@ select * from gtest32 t group by grouping sets (a, b, c, d) having c = 20; (1 row) drop table gtest32; +-- sanity check of system catalog +-- If the index is based on a virtual generated column, then the corresponding +-- attribute's attgenerated should be 'v'. +select pi.indrelid::regclass, pa.attnum, + pa.attname, + pa.attgenerated +from pg_index pi, unnest(indattrgenerated) sub(a), pg_attribute pa +where 0 <> a +and pa.attrelid = pi.indrelid and pa.attnum = sub.a +and (pa.attgenerated <> 'v' or pi.indnatts <> pi.indnkeyatts); + indrelid | attnum | attname | attgenerated +----------+--------+---------+-------------- +(0 rows) + diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql index 068dd0bc8aad..b39e76bcfc3c 100644 --- a/src/test/regress/sql/fast_default.sql +++ b/src/test/regress/sql/fast_default.sql @@ -77,6 +77,12 @@ ALTER TABLE has_volatile ALTER COLUMN col1 SET DATA TYPE float8, -- stored generated columns need a rewrite ALTER TABLE has_volatile ADD col7 int GENERATED ALWAYS AS (55) stored; +-- if there is any index over virtual generated columns, +-- change generation expression need rewrite +CREATE INDEX on has_volatile(col6); +ALTER TABLE has_volatile ALTER COLUMN col6 SET EXPRESSION AS (col1 * 3); +-- table rewrite again. +ALTER TABLE has_volatile ALTER COLUMN col6 SET DATA TYPE INT8; -- Test a large sample of different datatypes diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index b4eedeee2fb2..7685cae3d6e1 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -1,6 +1,4 @@ -- keep these tests aligned with generated_stored.sql - - CREATE SCHEMA generated_virtual_tests; GRANT USAGE ON SCHEMA generated_virtual_tests TO PUBLIC; SET search_path = generated_virtual_tests; @@ -391,32 +389,85 @@ CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) VIRTUAL, PRIMARY --INSERT INTO gtest22b VALUES (2); -- indexes -CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL); ---CREATE INDEX gtest22c_b_idx ON gtest22c (b); +CREATE TABLE gtestparted (b integer, c integer,a integer GENERATED ALWAYS AS (c+1))PARTITION BY RANGE (b); +CREATE TABLE gtestpart1 (b integer, c integer, a integer GENERATED ALWAYS AS (c)); +CREATE TABLE gtestpart2 (b integer, a integer GENERATED ALWAYS AS (c+1), c integer); +CREATE TABLE gtestpart3 (b integer, c integer, a integer GENERATED ALWAYS AS (c)); + +ALTER TABLE gtestparted ATTACH PARTITION gtestpart3 for values from (1) to (10); +--error. partitioned and partition have different generation expression, can not +--build index on it. +CREATE INDEX gtestparted_a_idx_error on gtestparted(a); --error +ALTER TABLE gtestparted DETACH PARTITION gtestpart3; + +CREATE INDEX gtestpart1_a_idx on gtestpart1(a); +CREATE INDEX gtestpart2_a_idx on gtestpart2(a); +CREATE INDEX gtestpart2_a_idx_copy on gtestpart2(a); +CREATE INDEX gtestparted_a_idx on gtestparted(a); + +--error. index over different generation expression should not allowed +ALTER TABLE gtestparted ATTACH PARTITION gtestpart1 for values from (1) to (10); --error +ALTER TABLE gtestparted ATTACH PARTITION gtestpart2 for values from (1) to (10); --ok + +SELECT * FROM pg_partition_tree('gtestparted_a_idx'::regclass); +ALTER INDEX gtestparted_a_idx ATTACH PARTITION gtestpart2_a_idx; --ok +ALTER INDEX gtestparted_a_idx ATTACH PARTITION gtestpart2_a_idx_copy; --error +CREATE INDEX gtestparted_a_idx_1 on gtestparted(a); +--now index gtestpart2_a_idx_copy should attach to the partition tree. +SELECT * FROM pg_partition_tree('gtestparted_a_idx_1'::regclass); + +--create table like should work just fine +CREATE TABLE gtestparted_like (LIKE gtestparted including all); +\d gtestparted_like + +CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL + ,c int GENERATED ALWAYS AS (11) VIRTUAL + ,d int GENERATED ALWAYS AS (a *3) VIRTUAL + ,e int4range GENERATED ALWAYS AS (int4range(a, a+10)) VIRTUAL + ,f int GENERATED ALWAYS AS (a) VIRTUAL); +CREATE INDEX gtest22c_b_idx ON gtest22c (b); +--variable free generation expression have no pratical usage, so error out. +CREATE INDEX gtest22c_c_idx ON gtest22c (c); +CREATE INDEX gtest22c_d_idx ON gtest22c USING hash(d); +CREATE INDEX gtest22c_e_idx ON gtest22c USING gist(e); +--error. include columns are not supported. +CREATE INDEX gtest22c_idx1 ON gtest22c USING btree(a) include (b,c); +CREATE INDEX gtest22c_idx1 ON gtest22c USING btree(a) include (f); + --CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); --CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; ---\d gtest22c +\d gtest22c + +INSERT INTO gtest22c(a) VALUES (1), (2), (3), (10); +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 d = 6; +SELECT * FROM gtest22c WHERE d = 6; +EXPLAIN (COSTS OFF) SELECT count(*) FROM gtest22c WHERE e @> 12; +SELECT count(*) from gtest22c where e @> 12; + +--column drop then the index over that column should also being dropped +ALTER TABLE gtest22c DROP COLUMN e; +\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; +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; + +RESET enable_seqscan; +RESET enable_bitmapscan; -- foreign keys CREATE TABLE gtest23a (x int PRIMARY KEY, y int); @@ -833,3 +884,14 @@ select * from gtest32 t group by grouping sets (a, b, c, d) having c = 20; select * from gtest32 t group by grouping sets (a, b, c, d) having c = 20; drop table gtest32; + +-- sanity check of system catalog +-- If the index is based on a virtual generated column, then the corresponding +-- attribute's attgenerated should be 'v'. +select pi.indrelid::regclass, pa.attnum, + pa.attname, + pa.attgenerated +from pg_index pi, unnest(indattrgenerated) sub(a), pg_attribute pa +where 0 <> a +and pa.attrelid = pi.indrelid and pa.attnum = sub.a +and (pa.attgenerated <> 'v' or pi.indnatts <> pi.indnkeyatts);