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
+
+
+ indattrgeneratedint2vector
+ (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.
+
+
+
indexprspg_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);