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
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5a70fe4d2c..b4adb32af0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -107,7 +107,8 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
 										  List *indexColNames,
 										  Oid accessMethodObjectId,
 										  Oid *collationObjectId,
-										  Oid *classObjectId);
+										  Oid *classObjectId,
+										  int32 *attstattargets);
 static void InitializeAttributeOids(Relation indexRelation,
 									int numatts, Oid indexoid);
 static void AppendAttributeTuples(Relation indexRelation, Datum *attopts);
@@ -272,7 +273,8 @@ ConstructTupleDescriptor(Relation heapRelation,
 						 List *indexColNames,
 						 Oid accessMethodObjectId,
 						 Oid *collationObjectId,
-						 Oid *classObjectId)
+						 Oid *classObjectId,
+						 int32 *attstattargets)
 {
 	int			numatts = indexInfo->ii_NumIndexAttrs;
 	int			numkeyatts = indexInfo->ii_NumIndexKeyAttrs;
@@ -310,12 +312,14 @@ ConstructTupleDescriptor(Relation heapRelation,
 
 		MemSet(to, 0, ATTRIBUTE_FIXED_PART_SIZE);
 		to->attnum = i + 1;
-		to->attstattarget = -1;
 		to->attcacheoff = -1;
 		to->attislocal = true;
 		to->attcollation = (i < numkeyatts) ?
 			collationObjectId[i] : InvalidOid;
-
+		if(attstattargets != NULL)
+			to->attstattarget = attstattargets[i];
+		else
+			to->attstattarget = -1;
 		/*
 		 * Set the attribute name as specified by caller.
 		 */
@@ -697,6 +701,7 @@ index_create(Relation heapRelation,
 			 Oid *collationObjectId,
 			 Oid *classObjectId,
 			 int16 *coloptions,
+			 int32 *colstattargets,
 			 Datum reloptions,
 			 bits16 flags,
 			 bits16 constr_flags,
@@ -882,7 +887,8 @@ index_create(Relation heapRelation,
 											indexColNames,
 											accessMethodObjectId,
 											collationObjectId,
-											classObjectId);
+											classObjectId,
+											colstattargets);
 
 	/*
 	 * Allocate an OID for the index, unless we were told what to use.
@@ -1413,11 +1419,13 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 				optionDatum;
 	oidvector  *indclass;
 	int2vector *indcoloptions;
+	int32      *colstattargets;
 	bool		isnull;
 	List	   *indexColNames = NIL;
 	List	   *indexExprs = NIL;
 	List	   *indexPreds = NIL;
 
+
 	indexRelation = index_open(oldIndexId, RowExclusiveLock);
 
 	/* The new index needs some information from the old index */
@@ -1501,10 +1509,11 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 							true);
 
 	/*
-	 * Extract the list of column names and the column numbers for the new
+	 * Extract the list of column names, column numbers and stattargets for the new
 	 * index information.  All this information will be used for the index
 	 * creation.
 	 */
+	colstattargets = palloc(sizeof(int32) * oldInfo->ii_NumIndexAttrs);
 	for (int i = 0; i < oldInfo->ii_NumIndexAttrs; i++)
 	{
 		TupleDesc	indexTupDesc = RelationGetDescr(indexRelation);
@@ -1512,8 +1521,8 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 
 		indexColNames = lappend(indexColNames, NameStr(att->attname));
 		newInfo->ii_IndexAttrNumbers[i] = oldInfo->ii_IndexAttrNumbers[i];
+		colstattargets[i] = att->attstattarget;
 	}
-
 	/*
 	 * Now create the new index.
 	 *
@@ -1534,13 +1543,14 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 							  indexRelation->rd_indcollation,
 							  indclass->values,
 							  indcoloptions->values,
+							  colstattargets,
 							  optionDatum,
 							  INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT,
 							  0,
 							  true, /* allow table to be a system catalog? */
 							  false,	/* is_internal? */
 							  NULL);
-
+	pfree(colstattargets);
 	/* Close the relations used and clean up */
 	index_close(indexRelation, NoLock);
 	ReleaseSysCache(indexTuple);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index d7b806020d..09c1be3611 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -313,7 +313,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 				 list_make2("chunk_id", "chunk_seq"),
 				 BTREE_AM_OID,
 				 rel->rd_rel->reltablespace,
-				 collationObjectId, classObjectId, coloptions, (Datum) 0,
+				 collationObjectId, classObjectId, coloptions, NULL, (Datum) 0,
 				 INDEX_CREATE_IS_PRIMARY, 0, true, true, NULL);
 
 	table_close(toast_rel, NoLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 127ba7835d..6eba68c7ba 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1140,7 +1140,7 @@ DefineIndex(Oid relationId,
 					 stmt->oldNode, indexInfo, indexColNames,
 					 accessMethodId, tablespaceId,
 					 collationObjectId, classObjectId,
-					 coloptions, reloptions,
+					 coloptions, NULL, reloptions,
 					 flags, constr_flags,
 					 allowSystemTableMods, !check_rights,
 					 &createdConstraintId);
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index e22d506436..bf9bc6bcef 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -79,6 +79,7 @@ extern Oid	index_create(Relation heapRelation,
 						 Oid *collationObjectId,
 						 Oid *classObjectId,
 						 int16 *coloptions,
+						 int32 *colstattargets,
 						 Datum reloptions,
 						 bits16 flags,
 						 bits16 constr_flags,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0ce6ee4622..a9140bc837 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -113,6 +113,27 @@ ALTER INDEX attmp_idx ALTER COLUMN 2 SET STATISTICS 1000;
  b      | cstring          | yes  | b          | plain   | 
 btree, for table "public.attmp"
 
+-- Check we keep statistics after REINDEX and REINDEX CONCURRENTLY
+REINDEX INDEX attmp_idx;
+\d+ attmp_idx
+                        Index "public.attmp_idx"
+ Column |       Type       | Key? | Definition | Storage | Stats target 
+--------+------------------+------+------------+---------+--------------
+ a      | integer          | yes  | a          | plain   | 
+ expr   | double precision | yes  | (d + e)    | plain   | 1000
+ b      | cstring          | yes  | b          | plain   | 
+btree, for table "public.attmp"
+
+REINDEX INDEX CONCURRENTLY attmp_idx;
+\d+ attmp_idx
+                        Index "public.attmp_idx"
+ Column |       Type       | Key? | Definition | Storage | Stats target 
+--------+------------------+------+------------+---------+--------------
+ a      | integer          | yes  | a          | plain   | 
+ expr   | double precision | yes  | (d + e)    | plain   | 1000
+ b      | cstring          | yes  | b          | plain   | 
+btree, for table "public.attmp"
+
 ALTER INDEX attmp_idx ALTER COLUMN 3 SET STATISTICS 1000;
 ERROR:  cannot alter statistics on non-expression column "b" of index "attmp_idx"
 HINT:  Alter statistics on table column instead.
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 4cc55d8525..36b24bfeb0 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -150,6 +150,15 @@ ALTER INDEX attmp_idx ALTER COLUMN 2 SET STATISTICS 1000;
 
 \d+ attmp_idx
 
+-- Check we keep statistics after REINDEX and REINDEX CONCURRENTLY
+REINDEX INDEX attmp_idx;
+
+\d+ attmp_idx
+
+REINDEX INDEX CONCURRENTLY attmp_idx;
+
+\d+ attmp_idx
+
 ALTER INDEX attmp_idx ALTER COLUMN 3 SET STATISTICS 1000;
 
 ALTER INDEX attmp_idx ALTER COLUMN 4 SET STATISTICS 1000;

Reply via email to