Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
amcheck: Support for different header sizes of short varlena datum
authorAlexander Korotkov <akorotkov@postgresql.org>
Sat, 23 Mar 2024 20:59:56 +0000 (22:59 +0200)
committerAlexander Korotkov <akorotkov@postgresql.org>
Sat, 23 Mar 2024 21:02:30 +0000 (23:02 +0200)
In the heap, tuples may contain short varlena datum with both 1B header and 4B
headers.  But the corresponding index tuple should always have such varlena's
with 1B headers.  So, for fingerprinting, we need to convert.

Backpatch to all supported versions.

Discussion: https://postgr.es/m/flat/7bdbe559-d61a-4ae4-a6e1-48abdf3024cc%40postgrespro.ru
Author: Michael Zhilin
Reviewed-by: Alexander Lakhin, Andrey Borodin, Jian He, Alexander Korotkov
Backpatch-through: 12

contrib/amcheck/expected/check_btree.out
contrib/amcheck/sql/check_btree.sql
contrib/amcheck/verify_nbtree.c

index 38791bbc1f4a49e49543f5db6d9ca39a4d0f932e..fe6bf2923f59b2be2a061f69d381d411b4e98596 100644 (file)
@@ -199,6 +199,18 @@ SELECT bt_index_check('bttest_a_expr_idx', true);
  
 (1 row)
 
+-- Check support of both 1B and 4B header sizes of short varlena datum
+CREATE TABLE varlena_bug (v text);
+ALTER TABLE varlena_bug ALTER column v SET storage plain;
+INSERT INTO varlena_bug VALUES ('x');
+COPY varlena_bug from stdin;
+CREATE INDEX varlena_bug_idx on varlena_bug(v);
+SELECT bt_index_check('varlena_bug_idx', true);
+ bt_index_check 
+----------------
+(1 row)
+
 -- cleanup
 DROP TABLE bttest_a;
 DROP TABLE bttest_b;
@@ -208,3 +220,4 @@ DROP TABLE toast_bug;
 DROP FUNCTION ifun(int8);
 DROP OWNED BY regress_bttest_role; -- permissions
 DROP ROLE regress_bttest_role;
+DROP TABLE varlena_bug;
index 033c04b4d057574995d53f33e62865582044c5d3..92a3b0f419c8dae033dab0006833caf31b796a8a 100644 (file)
@@ -135,6 +135,16 @@ CREATE INDEX bttest_a_expr_idx ON bttest_a ((ifun(id) + ifun(0)))
 
 SELECT bt_index_check('bttest_a_expr_idx', true);
 
+-- Check support of both 1B and 4B header sizes of short varlena datum
+CREATE TABLE varlena_bug (v text);
+ALTER TABLE varlena_bug ALTER column v SET storage plain;
+INSERT INTO varlena_bug VALUES ('x');
+COPY varlena_bug from stdin;
+x
+\.
+CREATE INDEX varlena_bug_idx on varlena_bug(v);
+SELECT bt_index_check('varlena_bug_idx', true);
+
 -- cleanup
 DROP TABLE bttest_a;
 DROP TABLE bttest_b;
@@ -144,3 +154,4 @@ DROP TABLE toast_bug;
 DROP FUNCTION ifun(int8);
 DROP OWNED BY regress_bttest_role; -- permissions
 DROP ROLE regress_bttest_role;
+DROP TABLE varlena_bug;
index dc7d4a516d8d50358ec30d6866f636ffb8676d54..bb9eb1ffc2f04d3a34b528f88087fa31f111ace7 100644 (file)
@@ -2642,7 +2642,7 @@ bt_normalize_tuple(BtreeCheckState *state, IndexTuple itup)
    TupleDesc   tupleDescriptor = RelationGetDescr(state->rel);
    Datum       normalized[INDEX_MAX_KEYS];
    bool        isnull[INDEX_MAX_KEYS];
-   bool        toast_free[INDEX_MAX_KEYS];
+   bool        need_free[INDEX_MAX_KEYS];
    bool        formnewtup = false;
    IndexTuple  reformed;
    int         i;
@@ -2661,7 +2661,7 @@ bt_normalize_tuple(BtreeCheckState *state, IndexTuple itup)
        att = TupleDescAttr(tupleDescriptor, i);
 
        /* Assume untoasted/already normalized datum initially */
-       toast_free[i] = false;
+       need_free[i] = false;
        normalized[i] = index_getattr(itup, att->attnum,
                                      tupleDescriptor,
                                      &isnull[i]);
@@ -2684,11 +2684,32 @@ bt_normalize_tuple(BtreeCheckState *state, IndexTuple itup)
        {
            formnewtup = true;
            normalized[i] = PointerGetDatum(PG_DETOAST_DATUM(normalized[i]));
-           toast_free[i] = true;
+           need_free[i] = true;
+       }
+
+       /*
+        * Short tuples may have 1B or 4B header. Convert 4B header of short
+        * tuples to 1B
+        */
+       else if (VARATT_CAN_MAKE_SHORT(DatumGetPointer(normalized[i])))
+       {
+           /* convert to short varlena */
+           Size        len = VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(normalized[i]));
+           char       *data = palloc(len);
+
+           SET_VARSIZE_SHORT(data, len);
+           memcpy(data + 1, VARDATA(DatumGetPointer(normalized[i])), len - 1);
+
+           formnewtup = true;
+           normalized[i] = PointerGetDatum(data);
+           need_free[i] = true;
        }
    }
 
-   /* Easier case: Tuple has varlena datums, none of which are compressed */
+   /*
+    * Easier case: Tuple has varlena datums, none of which are compressed or
+    * short with 4B header
+    */
    if (!formnewtup)
        return itup;
 
@@ -2698,6 +2719,11 @@ bt_normalize_tuple(BtreeCheckState *state, IndexTuple itup)
     * (normalized input datums).  This is rather naive, but shouldn't be
     * necessary too often.
     *
+    * In the heap, tuples may contain short varlena datums with both 1B
+    * header and 4B headers.  But the corresponding index tuple should always
+    * have such varlena's with 1B headers.  So, if there is a short varlena
+    * with 4B header, we need to convert it for for fingerprinting.
+    *
     * Note that we rely on deterministic index_form_tuple() TOAST compression
     * of normalized input.
     */
@@ -2706,7 +2732,7 @@ bt_normalize_tuple(BtreeCheckState *state, IndexTuple itup)
 
    /* Cannot leak memory here */
    for (i = 0; i < tupleDescriptor->natts; i++)
-       if (toast_free[i])
+       if (need_free[i])
            pfree(DatumGetPointer(normalized[i]));
 
    return reformed;