1313#include "postgres.h"
1414
1515#include "access/genam.h"
16+ #include "access/heaptoast.h"
1617#include "access/table.h"
1718#include "access/tableam.h"
1819#include "verify_common.h"
@@ -189,3 +190,114 @@ index_checkable(Relation rel, Oid am_id)
189190
190191 return amcheck_index_mainfork_expected (rel );
191192}
193+
194+ IndexTuple
195+ amcheck_normalize_tuple (Relation irel , IndexTuple itup )
196+ {
197+ TupleDesc tupleDescriptor = RelationGetDescr (irel );
198+ Datum normalized [INDEX_MAX_KEYS ];
199+ bool isnull [INDEX_MAX_KEYS ];
200+ bool need_free [INDEX_MAX_KEYS ];
201+ bool formnewtup = false;
202+ IndexTuple reformed ;
203+ int i ;
204+
205+ /* Easy case: It's immediately clear that tuple has no varlena datums */
206+ if (!IndexTupleHasVarwidths (itup ))
207+ return itup ;
208+
209+ for (i = 0 ; i < tupleDescriptor -> natts ; i ++ )
210+ {
211+ Form_pg_attribute att ;
212+
213+ att = TupleDescAttr (tupleDescriptor , i );
214+
215+ /* Assume untoasted/already normalized datum initially */
216+ need_free [i ] = false;
217+ normalized [i ] = index_getattr (itup , att -> attnum ,
218+ tupleDescriptor ,
219+ & isnull [i ]);
220+ if (att -> attbyval || att -> attlen != -1 || isnull [i ])
221+ continue ;
222+
223+ /*
224+ * Callers always pass a tuple that could safely be inserted into the
225+ * index without further processing, so an external varlena header
226+ * should never be encountered here
227+ */
228+ if (VARATT_IS_EXTERNAL (DatumGetPointer (normalized [i ])))
229+ ereport (ERROR ,
230+ (errcode (ERRCODE_INDEX_CORRUPTED ),
231+ errmsg ("external varlena datum in tuple that references heap row (%u,%u) in index \"%s\"" ,
232+ ItemPointerGetBlockNumber (& (itup -> t_tid )),
233+ ItemPointerGetOffsetNumber (& (itup -> t_tid )),
234+ RelationGetRelationName (irel ))));
235+ else if (!VARATT_IS_COMPRESSED (DatumGetPointer (normalized [i ])) &&
236+ VARSIZE (DatumGetPointer (normalized [i ])) > TOAST_INDEX_TARGET &&
237+ (att -> attstorage == TYPSTORAGE_EXTENDED ||
238+ att -> attstorage == TYPSTORAGE_MAIN ))
239+ {
240+ /*
241+ * This value will be compressed by index_form_tuple() with the
242+ * current storage settings. We may be here because this tuple
243+ * was formed with different storage settings. So, force forming.
244+ */
245+ formnewtup = true;
246+ }
247+ else if (VARATT_IS_COMPRESSED (DatumGetPointer (normalized [i ])))
248+ {
249+ formnewtup = true;
250+ normalized [i ] = PointerGetDatum (PG_DETOAST_DATUM (normalized [i ]));
251+ need_free [i ] = true;
252+ }
253+
254+ /*
255+ * Short tuples may have 1B or 4B header. Convert 4B header of short
256+ * tuples to 1B
257+ */
258+ else if (VARATT_CAN_MAKE_SHORT (DatumGetPointer (normalized [i ])))
259+ {
260+ /* convert to short varlena */
261+ Size len = VARATT_CONVERTED_SHORT_SIZE (DatumGetPointer (normalized [i ]));
262+ char * data = palloc (len );
263+
264+ SET_VARSIZE_SHORT (data , len );
265+ memcpy (data + 1 , VARDATA (DatumGetPointer (normalized [i ])), len - 1 );
266+
267+ formnewtup = true;
268+ normalized [i ] = PointerGetDatum (data );
269+ need_free [i ] = true;
270+ }
271+ }
272+
273+ /*
274+ * Easier case: Tuple has varlena datums, none of which are compressed or
275+ * short with 4B header
276+ */
277+ if (!formnewtup )
278+ return itup ;
279+
280+ /*
281+ * Hard case: Tuple had compressed varlena datums that necessitate
282+ * creating normalized version of the tuple from uncompressed input datums
283+ * (normalized input datums). This is rather naive, but shouldn't be
284+ * necessary too often.
285+ *
286+ * In the heap, tuples may contain short varlena datums with both 1B
287+ * header and 4B headers. But the corresponding index tuple should always
288+ * have such varlena's with 1B headers. So, if there is a short varlena
289+ * with 4B header, we need to convert it for fingerprinting.
290+ *
291+ * Note that we rely on deterministic index_form_tuple() TOAST compression
292+ * of normalized input.
293+ */
294+ reformed = index_form_tuple (tupleDescriptor , normalized , isnull );
295+ reformed -> t_tid = itup -> t_tid ;
296+
297+ /* Cannot leak memory here */
298+ for (i = 0 ; i < tupleDescriptor -> natts ; i ++ )
299+ if (need_free [i ])
300+ pfree (DatumGetPointer (normalized [i ]));
301+
302+ return reformed ;
303+ }
0 commit comments