On Mon, Apr 14, 2025 at 8:05 PM Kirill Reshke <reshkekir...@gmail.com> wrote: > > On Mon, 14 Apr 2025 at 16:10, jian he <jian.universal...@gmail.com> wrote: > > > > new patch attached. Now, > > ALTER TABLE DROP COLUMN works fine. > > ALTER INDEX ATTACH PARTITION works fine. > > creating such an index on a partitioned table works just fine. > > for table inheritance: create index on parent table will not cascade > > to child table, > > so we don't need to worry about this. > > Hi! I reviewed v2, and it seems to be working now. > > But there are tests that are comment-out, what is their purpose? I > note that commit 83ea6c5 also included some commented tests, so > perhaps there's a reason I'm not aware of. >
> ``` > ALTER TABLE gtest22c DROP COLUMN e; > \d gtest22c > > -- 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; > ``` comment out tests are for to be implemented feature. There are some test changes that are indeed not necessary, I restored it back, please check attached.
From c3f39858798c27de7afdf5156c4174217920c676 Mon Sep 17 00:00:00 2001 From: jian he <jian.universal...@gmail.com> Date: Tue, 15 Apr 2025 16:28:46 +0800 Subject: [PATCH v3 1/1] index on virtual generated column * internally such index transformed into expression index. For example, an index on (b int GENERATED ALWAYS AS (a * 2) VIRTUAL) internal representation: an expression index on ((a * 2)). * in pageinspect module, add some test to check the index content of virtual generated column. * primary key, unique index over virtual generated column are not supported. * expression index and predicate index over virtual generated columns are currently not supported. * virtual generated column can not be in "include column" * all types of indexes are supported, hash index, gist index regress test added. * To support ALTER TABLE SET EXPRESSION, in pg_index, we need to track the original virtual generated column attribute number, so ALTER TABLE SET EXPRESSION can identify which index needs to be rebuilt. * ALTER COLUMN SET DATA TYPE will also cause table rewrite, so we really need to track the virtual generated column attribute number that index was built on. * if the partitioned table and partition both have an index, then the index over the virtual generated column should be the same expression. For example, the following last command should error out. CREATE TABLE parted (b integer,c integer,a integer GENERATED ALWAYS AS (c+1)) PARTITION BY RANGE (b); CREATE TABLE part (b integer,c integer,a integer GENERATED ALWAYS AS (c)); create index on part(a); create index on parted(a); alter table parted ATTACH partition part for values from (1) to (10); discussion: https://postgr.es/m/CACJufxGao-cypdNhifHAdt8jHfK6-HX=trbovbkgruxw063...@mail.gmail.com commitfest entry: https://commitfest.postgresql.org/patch/5667/ --- contrib/pageinspect/expected/btree.out | 33 ++ contrib/pageinspect/sql/btree.sql | 21 ++ doc/src/sgml/catalogs.sgml | 15 + src/backend/catalog/index.c | 69 ++++ src/backend/commands/indexcmds.c | 301 +++++++++++++++--- src/backend/commands/tablecmds.c | 61 ++++ src/backend/parser/parse_utilcmd.c | 25 +- src/backend/utils/adt/ruleutils.c | 28 +- src/include/catalog/index.h | 6 + src/include/catalog/pg_index.h | 1 + src/include/nodes/execnodes.h | 1 + src/test/regress/expected/fast_default.out | 8 + .../regress/expected/generated_virtual.out | 187 ++++++++++- src/test/regress/sql/fast_default.sql | 6 + src/test/regress/sql/generated_virtual.sql | 94 +++++- 15 files changed, 788 insertions(+), 68 deletions(-) diff --git a/contrib/pageinspect/expected/btree.out b/contrib/pageinspect/expected/btree.out index 0aa5d73322f..56d57848cf7 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 102ebdefe3c..d3392c11d5f 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 cbd4e40a320..d7ee73373f5 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -4595,6 +4595,21 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>indattrgenerated</structfield> <type>int2vector</type> + (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>) + </para> + <para> + This is an array of <structfield>indnatts</structfield> values that + indicate which table virtual generated columns this index indexes. + For example, a value of <literal>1 3</literal> 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. + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>indexprs</structfield> <type>pg_node_tree</type> diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 739a92bdcc1..dd379c2b79c 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 33c2106c17c..f36edaf5e38 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 b3ed69457fc..27aa3686e65 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8652,6 +8652,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 @@ -14755,6 +14781,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 @@ -20521,6 +20555,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 @@ -20534,9 +20569,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); /* @@ -20595,6 +20640,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 62015431fdf..3c7af2605b3 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 9e90acedb91..ae3db86294d 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 4daa8bef5ee..a7e93f3a107 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 4392b9d221d..7ed74a593a4 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 5b6cadb5a6c..ea4240b36e4 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -196,6 +196,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 ccbcdf8403f..ef19f667cc1 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 26bbe1e9c31..d3525a1352e 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); @@ -1587,3 +1732,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 068dd0bc8aa..b39e76bcfc3 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 13cfbd76859..04dd56e58cf 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); @@ -828,3 +879,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); -- 2.34.1