Hello !
We encountered the following bug recently in production: when running REINDEX
CONCURRENTLY on an index, the attstattarget is reset to 0.
Consider the following example:
junk=# \d+ t1_date_trunc_idx
Index "public.t1_date_trunc_idx"
Column | Type | Key? | Definition
| Storage | Stats target
------------+-----------------------------+------
+-----------------------------+---------+--------------
date_trunc | timestamp without time zone | yes | date_trunc('day'::text, ts)
| plain | 1000
btree, for table "public.t1"
junk=# REINDEX INDEX t1_date_trunc_idx;
REINDEX
junk=# \d+ t1_date_trunc_idx
Index "public.t1_date_trunc_idx"
Column | Type | Key? | Definition
| Storage | Stats target
------------+-----------------------------+------
+-----------------------------+---------+--------------
date_trunc | timestamp without time zone | yes | date_trunc('day'::text, ts)
| plain | 1000
btree, for table "public.t1"
junk=# REINDEX INDEX CONCURRENTLY t1_date_trunc_idx;
REINDEX
junk=# \d+ t1_date_trunc_idx
Index "public.t1_date_trunc_idx"
Column | Type | Key? | Definition
| Storage | Stats target
------------+-----------------------------+------
+-----------------------------+---------+--------------
date_trunc | timestamp without time zone | yes | date_trunc('day'::text, ts)
| plain |
btree, for table "public.t1"
I'm attaching a patch possibly solving the problem, but maybe the proposed
changes will be too intrusive ?
Regards,
--
Ronan Dunklau
[1mdiff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c[m
[1mindex 5a70fe4d2c..b4adb32af0 100644[m
[1m--- a/src/backend/catalog/index.c[m
[1m+++ b/src/backend/catalog/index.c[m
[36m@@ -107,7 +107,8 @@[m [mstatic TupleDesc ConstructTupleDescriptor(Relation heapRelation,[m
List *indexColNames,[m
Oid accessMethodObjectId,[m
Oid *collationObjectId,[m
[31m- Oid *classObjectId);[m
[32m+[m [32m Oid *classObjectId,[m
[32m+[m [32m int32 *attstattargets);[m
static void InitializeAttributeOids(Relation indexRelation,[m
int numatts, Oid indexoid);[m
static void AppendAttributeTuples(Relation indexRelation, Datum *attopts);[m
[36m@@ -272,7 +273,8 @@[m [mConstructTupleDescriptor(Relation heapRelation,[m
List *indexColNames,[m
Oid accessMethodObjectId,[m
Oid *collationObjectId,[m
[31m- Oid *classObjectId)[m
[32m+[m [32m Oid *classObjectId,[m
[32m+[m [32m int32 *attstattargets)[m
{[m
int numatts = indexInfo->ii_NumIndexAttrs;[m
int numkeyatts = indexInfo->ii_NumIndexKeyAttrs;[m
[36m@@ -310,12 +312,14 @@[m [mConstructTupleDescriptor(Relation heapRelation,[m
[m
MemSet(to, 0, ATTRIBUTE_FIXED_PART_SIZE);[m
to->attnum = i + 1;[m
[31m- to->attstattarget = -1;[m
to->attcacheoff = -1;[m
to->attislocal = true;[m
to->attcollation = (i < numkeyatts) ?[m
collationObjectId[i] : InvalidOid;[m
[31m-[m
[32m+[m [32mif(attstattargets != NULL)[m
[32m+[m [32mto->attstattarget = attstattargets[i];[m
[32m+[m [32melse[m
[32m+[m [32mto->attstattarget = -1;[m
/*[m
* Set the attribute name as specified by caller.[m
*/[m
[36m@@ -697,6 +701,7 @@[m [mindex_create(Relation heapRelation,[m
Oid *collationObjectId,[m
Oid *classObjectId,[m
int16 *coloptions,[m
[32m+[m [32m int32 *colstattargets,[m
Datum reloptions,[m
bits16 flags,[m
bits16 constr_flags,[m
[36m@@ -882,7 +887,8 @@[m [mindex_create(Relation heapRelation,[m
indexColNames,[m
accessMethodObjectId,[m
collationObjectId,[m
[31m- classObjectId);[m
[32m+[m [32mclassObjectId,[m
[32m+[m [32mcolstattargets);[m
[m
/*[m
* Allocate an OID for the index, unless we were told what to use.[m
[36m@@ -1413,11 +1419,13 @@[m [mindex_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,[m
optionDatum;[m
oidvector *indclass;[m
int2vector *indcoloptions;[m
[32m+[m [32mint32 *colstattargets;[m
bool isnull;[m
List *indexColNames = NIL;[m
List *indexExprs = NIL;[m
List *indexPreds = NIL;[m
[m
[32m+[m
indexRelation = index_open(oldIndexId, RowExclusiveLock);[m
[m
/* The new index needs some information from the old index */[m
[36m@@ -1501,10 +1509,11 @@[m [mindex_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,[m
true);[m
[m
/*[m
[31m- * Extract the list of column names and the column numbers for the new[m
[32m+[m [32m * Extract the list of column names, column numbers and stattargets for the new[m
* index information. All this information will be used for the index[m
* creation.[m
*/[m
[32m+[m [32mcolstattargets = palloc(sizeof(int32) * oldInfo->ii_NumIndexAttrs);[m
for (int i = 0; i < oldInfo->ii_NumIndexAttrs; i++)[m
{[m
TupleDesc indexTupDesc = RelationGetDescr(indexRelation);[m
[36m@@ -1512,8 +1521,8 @@[m [mindex_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,[m
[m
indexColNames = lappend(indexColNames, NameStr(att->attname));[m
newInfo->ii_IndexAttrNumbers[i] = oldInfo->ii_IndexAttrNumbers[i];[m
[32m+[m [32mcolstattargets[i] = att->attstattarget;[m
}[m
[31m-[m
/*[m
* Now create the new index.[m
*[m
[36m@@ -1534,13 +1543,14 @@[m [mindex_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,[m
indexRelation->rd_indcollation,[m
indclass->values,[m
indcoloptions->values,[m
[32m+[m [32m colstattargets,[m
optionDatum,[m
INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT,[m
0,[m
true, /* allow table to be a system catalog? */[m
false, /* is_internal? */[m
NULL);[m
[31m-[m
[32m+[m [32mpfree(colstattargets);[m
/* Close the relations used and clean up */[m
index_close(indexRelation, NoLock);[m
ReleaseSysCache(indexTuple);[m
[1mdiff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c[m
[1mindex d7b806020d..09c1be3611 100644[m
[1m--- a/src/backend/catalog/toasting.c[m
[1m+++ b/src/backend/catalog/toasting.c[m
[36m@@ -313,7 +313,7 @@[m [mcreate_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,[m
list_make2("chunk_id", "chunk_seq"),[m
BTREE_AM_OID,[m
rel->rd_rel->reltablespace,[m
[31m- collationObjectId, classObjectId, coloptions, (Datum) 0,[m
[32m+[m [32m collationObjectId, classObjectId, coloptions, NULL, (Datum) 0,[m
INDEX_CREATE_IS_PRIMARY, 0, true, true, NULL);[m
[m
table_close(toast_rel, NoLock);[m
[1mdiff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c[m
[1mindex 127ba7835d..6eba68c7ba 100644[m
[1m--- a/src/backend/commands/indexcmds.c[m
[1m+++ b/src/backend/commands/indexcmds.c[m
[36m@@ -1140,7 +1140,7 @@[m [mDefineIndex(Oid relationId,[m
stmt->oldNode, indexInfo, indexColNames,[m
accessMethodId, tablespaceId,[m
collationObjectId, classObjectId,[m
[31m- coloptions, reloptions,[m
[32m+[m [32m coloptions, NULL, reloptions,[m
flags, constr_flags,[m
allowSystemTableMods, !check_rights,[m
&createdConstraintId);[m
[1mdiff --git a/src/include/catalog/index.h b/src/include/catalog/index.h[m
[1mindex e22d506436..bf9bc6bcef 100644[m
[1m--- a/src/include/catalog/index.h[m
[1m+++ b/src/include/catalog/index.h[m
[36m@@ -79,6 +79,7 @@[m [mextern Oid index_create(Relation heapRelation,[m
Oid *collationObjectId,[m
Oid *classObjectId,[m
int16 *coloptions,[m
[32m+[m [32m int32 *colstattargets,[m
Datum reloptions,[m
bits16 flags,[m
bits16 constr_flags,[m
[1mdiff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out[m
[1mindex 0ce6ee4622..a9140bc837 100644[m
[1m--- a/src/test/regress/expected/alter_table.out[m
[1m+++ b/src/test/regress/expected/alter_table.out[m
[36m@@ -113,6 +113,27 @@[m [mALTER INDEX attmp_idx ALTER COLUMN 2 SET STATISTICS 1000;[m
b | cstring | yes | b | plain | [m
btree, for table "public.attmp"[m
[m
[32m+[m[32m-- Check we keep statistics after REINDEX and REINDEX CONCURRENTLY[m
[32m+[m[32mREINDEX INDEX attmp_idx;[m
[32m+[m[32m\d+ attmp_idx[m
[32m+[m[32m Index "public.attmp_idx"[m
[32m+[m[32m Column | Type | Key? | Definition | Storage | Stats target [m
[32m+[m[32m--------+------------------+------+------------+---------+--------------[m
[32m+[m[32m a | integer | yes | a | plain | [m
[32m+[m[32m expr | double precision | yes | (d + e) | plain | 1000[m
[32m+[m[32m b | cstring | yes | b | plain | [m
[32m+[m[32mbtree, for table "public.attmp"[m
[32m+[m
[32m+[m[32mREINDEX INDEX CONCURRENTLY attmp_idx;[m
[32m+[m[32m\d+ attmp_idx[m
[32m+[m[32m Index "public.attmp_idx"[m
[32m+[m[32m Column | Type | Key? | Definition | Storage | Stats target [m
[32m+[m[32m--------+------------------+------+------------+---------+--------------[m
[32m+[m[32m a | integer | yes | a | plain | [m
[32m+[m[32m expr | double precision | yes | (d + e) | plain | 1000[m
[32m+[m[32m b | cstring | yes | b | plain | [m
[32m+[m[32mbtree, for table "public.attmp"[m
[32m+[m
ALTER INDEX attmp_idx ALTER COLUMN 3 SET STATISTICS 1000;[m
ERROR: cannot alter statistics on non-expression column "b" of index "attmp_idx"[m
HINT: Alter statistics on table column instead.[m
[1mdiff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql[m
[1mindex 4cc55d8525..36b24bfeb0 100644[m
[1m--- a/src/test/regress/sql/alter_table.sql[m
[1m+++ b/src/test/regress/sql/alter_table.sql[m
[36m@@ -150,6 +150,15 @@[m [mALTER INDEX attmp_idx ALTER COLUMN 2 SET STATISTICS 1000;[m
[m
\d+ attmp_idx[m
[m
[32m+[m[32m-- Check we keep statistics after REINDEX and REINDEX CONCURRENTLY[m
[32m+[m[32mREINDEX INDEX attmp_idx;[m
[32m+[m
[32m+[m[32m\d+ attmp_idx[m
[32m+[m
[32m+[m[32mREINDEX INDEX CONCURRENTLY attmp_idx;[m
[32m+[m
[32m+[m[32m\d+ attmp_idx[m
[32m+[m
ALTER INDEX attmp_idx ALTER COLUMN 3 SET STATISTICS 1000;[m
[m
ALTER INDEX attmp_idx ALTER COLUMN 4 SET STATISTICS 1000;[m