Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Preserve pg_attribute.attstattarget across REINDEX CONCURRENTLY
authorMichael Paquier <michael@paquier.xyz>
Wed, 10 Feb 2021 04:09:09 +0000 (13:09 +0900)
committerMichael Paquier <michael@paquier.xyz>
Wed, 10 Feb 2021 04:09:09 +0000 (13:09 +0900)
For an index, attstattarget can be updated using ALTER INDEX SET
STATISTICS.  This data was lost on the new index after REINDEX
CONCURRENTLY.

The update of this field is done when the old and new indexes are
swapped to make the fix back-patchable.  Another approach we could look
after in the long-term is to change index_create() to pass the wanted
values of attstattarget when creating the new relation, but, as this
would cause an ABI breakage this can be done only on HEAD.

Reported-by: Ronan Dunklau
Author: Michael Paquier
Reviewed-by: Ronan Dunklau, Tomas Vondra
Discussion: https://postgr.es/m/16628084.uLZWGnKmhe@laptop-ronand
Backpatch-through: 12

src/backend/catalog/index.c
src/backend/utils/cache/lsyscache.c
src/include/utils/lsyscache.h
src/test/regress/expected/create_index.out
src/test/regress/sql/create_index.sql

index 453b6e8793a73f571e8be28cc95c2728ecad9199..b9eed8ec38516e34a8ceed2835cdc9d1ac77fb16 100644 (file)
@@ -1728,6 +1728,62 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName)
    /* Copy data of pg_statistic from the old index to the new one */
    CopyStatistics(oldIndexId, newIndexId);
 
+   /* Copy pg_attribute.attstattarget for each index attribute */
+   {
+       HeapTuple   attrTuple;
+       Relation    pg_attribute;
+       SysScanDesc scan;
+       ScanKeyData key[1];
+
+       pg_attribute = table_open(AttributeRelationId, RowExclusiveLock);
+       ScanKeyInit(&key[0],
+                   Anum_pg_attribute_attrelid,
+                   BTEqualStrategyNumber, F_OIDEQ,
+                   ObjectIdGetDatum(newIndexId));
+       scan = systable_beginscan(pg_attribute, AttributeRelidNumIndexId,
+                                 true, NULL, 1, key);
+
+       while (HeapTupleIsValid((attrTuple = systable_getnext(scan))))
+       {
+           Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attrTuple);
+           Datum       repl_val[Natts_pg_attribute];
+           bool        repl_null[Natts_pg_attribute];
+           bool        repl_repl[Natts_pg_attribute];
+           int         attstattarget;
+           HeapTuple   newTuple;
+
+           /* Ignore dropped columns */
+           if (att->attisdropped)
+               continue;
+
+           /*
+            * Get attstattarget from the old index and refresh the new value.
+            */
+           attstattarget = get_attstattarget(oldIndexId, att->attnum);
+
+           /* no need for a refresh if both match */
+           if (attstattarget == att->attstattarget)
+               continue;
+
+           memset(repl_val, 0, sizeof(repl_val));
+           memset(repl_null, false, sizeof(repl_null));
+           memset(repl_repl, false, sizeof(repl_repl));
+
+           repl_repl[Anum_pg_attribute_attstattarget - 1] = true;
+           repl_val[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(attstattarget);
+
+           newTuple = heap_modify_tuple(attrTuple,
+                                        RelationGetDescr(pg_attribute),
+                                        repl_val, repl_null, repl_repl);
+           CatalogTupleUpdate(pg_attribute, &newTuple->t_self, newTuple);
+
+           heap_freetuple(newTuple);
+       }
+
+       systable_endscan(scan);
+       table_close(pg_attribute, RowExclusiveLock);
+   }
+
    /* Close relations */
    table_close(pg_class, RowExclusiveLock);
    table_close(pg_index, RowExclusiveLock);
index 140339073b63636328d0baae97aa0f602c9dae55..3303c31e5757a39a208e02bc821d2f77e1afa694 100644 (file)
@@ -871,6 +871,33 @@ get_attnum(Oid relid, const char *attname)
        return InvalidAttrNumber;
 }
 
+/*
+ * get_attstattarget
+ *
+ *     Given the relation id and the attribute number,
+ *     return the "attstattarget" field from the attribute relation.
+ *
+ *     Errors if not found.
+ */
+int
+get_attstattarget(Oid relid, AttrNumber attnum)
+{
+   HeapTuple   tp;
+   Form_pg_attribute att_tup;
+   int         result;
+
+   tp = SearchSysCache2(ATTNUM,
+                        ObjectIdGetDatum(relid),
+                        Int16GetDatum(attnum));
+   if (!HeapTupleIsValid(tp))
+       elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+            attnum, relid);
+   att_tup = (Form_pg_attribute) GETSTRUCT(tp);
+   result = att_tup->attstattarget;
+   ReleaseSysCache(tp);
+   return result;
+}
+
 /*
  * get_attgenerated
  *
index fecfe1f4f6ef71a14eed1ab16d773250ec19e398..ee35686a660cbe550b8b84ade4490b403992d2f4 100644 (file)
@@ -87,6 +87,7 @@ extern Oid    get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype,
                              int16 procnum);
 extern char *get_attname(Oid relid, AttrNumber attnum, bool missing_ok);
 extern AttrNumber get_attnum(Oid relid, const char *attname);
+extern int get_attstattarget(Oid relid, AttrNumber attnum);
 extern char get_attgenerated(Oid relid, AttrNumber attnum);
 extern Oid get_atttype(Oid relid, AttrNumber attnum);
 extern void get_atttypetypmodcoll(Oid relid, AttrNumber attnum,
index 49aa5780a8bf417ca4c89cecded2b2ecfb138716..2cd07f64ddebd4d8d3e6d55a3d7704e03a5f3972 100644 (file)
@@ -2419,6 +2419,7 @@ CREATE UNIQUE INDEX concur_exprs_index_pred ON concur_exprs_tab (c1)
 CREATE UNIQUE INDEX concur_exprs_index_pred_2
   ON concur_exprs_tab ((1 / c1))
   WHERE ('-H') >= (c2::TEXT) COLLATE "C";
+ALTER INDEX concur_exprs_index_expr ALTER COLUMN 1 SET STATISTICS 100;
 ANALYZE concur_exprs_tab;
 SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
   'concur_exprs_index_expr'::regclass,
@@ -2498,6 +2499,20 @@ SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
  concur_exprs_index_expr |     1
 (1 row)
 
+-- attstattarget should remain intact
+SELECT attrelid::regclass, attnum, attstattarget
+  FROM pg_attribute WHERE attrelid IN (
+    'concur_exprs_index_expr'::regclass,
+    'concur_exprs_index_pred'::regclass,
+    'concur_exprs_index_pred_2'::regclass)
+  ORDER BY 'concur_exprs_index_expr'::regclass::text, attnum;
+         attrelid          | attnum | attstattarget 
+---------------------------+--------+---------------
+ concur_exprs_index_expr   |      1 |           100
+ concur_exprs_index_pred   |      1 |            -1
+ concur_exprs_index_pred_2 |      1 |            -1
+(3 rows)
+
 DROP TABLE concur_exprs_tab;
 -- Temporary tables and on-commit actions, where CONCURRENTLY is ignored.
 -- ON COMMIT PRESERVE ROWS, the default.
index 344c648485b3405a9dec1f8ceb128ff57453a54f..c130b86c025b93a55cb9d44714e2d26e7c90d546 100644 (file)
@@ -1003,6 +1003,7 @@ CREATE UNIQUE INDEX concur_exprs_index_pred ON concur_exprs_tab (c1)
 CREATE UNIQUE INDEX concur_exprs_index_pred_2
   ON concur_exprs_tab ((1 / c1))
   WHERE ('-H') >= (c2::TEXT) COLLATE "C";
+ALTER INDEX concur_exprs_index_expr ALTER COLUMN 1 SET STATISTICS 100;
 ANALYZE concur_exprs_tab;
 SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
   'concur_exprs_index_expr'::regclass,
@@ -1027,6 +1028,13 @@ SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
   'concur_exprs_index_pred'::regclass,
   'concur_exprs_index_pred_2'::regclass)
   GROUP BY starelid ORDER BY starelid::regclass::text;
+-- attstattarget should remain intact
+SELECT attrelid::regclass, attnum, attstattarget
+  FROM pg_attribute WHERE attrelid IN (
+    'concur_exprs_index_expr'::regclass,
+    'concur_exprs_index_pred'::regclass,
+    'concur_exprs_index_pred_2'::regclass)
+  ORDER BY 'concur_exprs_index_expr'::regclass::text, attnum;
 DROP TABLE concur_exprs_tab;
 
 -- Temporary tables and on-commit actions, where CONCURRENTLY is ignored.