Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * indextuple.c
4 : : * This file contains index tuple accessor and mutator routines,
5 : : * as well as various tuple utilities.
6 : : *
7 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 : : * Portions Copyright (c) 1994, Regents of the University of California
9 : : *
10 : : *
11 : : * IDENTIFICATION
12 : : * src/backend/access/common/indextuple.c
13 : : *
14 : : *-------------------------------------------------------------------------
15 : : */
16 : :
17 : : #include "postgres.h"
18 : :
19 : : #include "access/detoast.h"
20 : : #include "access/heaptoast.h"
21 : : #include "access/htup_details.h"
22 : : #include "access/itup.h"
23 : : #include "access/toast_internals.h"
24 : :
25 : : /*
26 : : * This enables de-toasting of index entries. Needed until VACUUM is
27 : : * smart enough to rebuild indexes from scratch.
28 : : */
29 : : #define TOAST_INDEX_HACK
30 : :
31 : : /* ----------------------------------------------------------------
32 : : * index_ tuple interface routines
33 : : * ----------------------------------------------------------------
34 : : */
35 : :
36 : : /* ----------------
37 : : * index_form_tuple
38 : : *
39 : : * As index_form_tuple_context, but allocates the returned tuple in the
40 : : * CurrentMemoryContext.
41 : : * ----------------
42 : : */
43 : : IndexTuple
44 : 8665476 : index_form_tuple(TupleDesc tupleDescriptor,
45 : : const Datum *values,
46 : : const bool *isnull)
47 : : {
48 : 8665476 : return index_form_tuple_context(tupleDescriptor, values, isnull,
49 : : CurrentMemoryContext);
50 : : }
51 : :
52 : : /* ----------------
53 : : * index_form_tuple_context
54 : : *
55 : : * This shouldn't leak any memory; otherwise, callers such as
56 : : * tuplesort_putindextuplevalues() will be very unhappy.
57 : : *
58 : : * This shouldn't perform external table access provided caller
59 : : * does not pass values that are stored EXTERNAL.
60 : : *
61 : : * Allocates returned tuple in provided 'context'.
62 : : * ----------------
63 : : */
64 : : IndexTuple
65 : 16686002 : index_form_tuple_context(TupleDesc tupleDescriptor,
66 : : const Datum *values,
67 : : const bool *isnull,
68 : : MemoryContext context)
69 : : {
70 : : char *tp; /* tuple pointer */
71 : : IndexTuple tuple; /* return tuple */
72 : : Size size,
73 : : data_size,
74 : : hoff;
75 : : int i;
76 : 16686002 : unsigned short infomask = 0;
77 : 16686002 : bool hasnull = false;
78 : 16686002 : uint16 tupmask = 0;
79 : 16686002 : int numberOfAttributes = tupleDescriptor->natts;
80 : :
81 : : #ifdef TOAST_INDEX_HACK
82 : 16686002 : Datum untoasted_values[INDEX_MAX_KEYS] = {0};
83 : 16686002 : bool untoasted_free[INDEX_MAX_KEYS] = {0};
84 : : #endif
85 : :
86 [ - + ]: 16686002 : if (numberOfAttributes > INDEX_MAX_KEYS)
87 [ # # ]: 0 : ereport(ERROR,
88 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
89 : : errmsg("number of index columns (%d) exceeds limit (%d)",
90 : : numberOfAttributes, INDEX_MAX_KEYS)));
91 : :
92 : : #ifdef TOAST_INDEX_HACK
93 [ + + ]: 42963215 : for (i = 0; i < numberOfAttributes; i++)
94 : : {
95 : 26277213 : Form_pg_attribute att = TupleDescAttr(tupleDescriptor, i);
96 : :
97 : 26277213 : untoasted_values[i] = values[i];
98 : 26277213 : untoasted_free[i] = false;
99 : :
100 : : /* Do nothing if value is NULL or not of varlena type */
101 [ + + + + ]: 26277213 : if (isnull[i] || att->attlen != -1)
102 : 25415311 : continue;
103 : :
104 : : /*
105 : : * If value is stored EXTERNAL, must fetch it so we are not depending
106 : : * on outside storage. This should be improved someday.
107 : : */
108 [ + + ]: 861902 : if (VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
109 : : {
110 : 241 : untoasted_values[i] =
111 : 241 : PointerGetDatum(detoast_external_attr((varlena *)
112 : : DatumGetPointer(values[i])));
113 : 241 : untoasted_free[i] = true;
114 : : }
115 : :
116 : : /*
117 : : * If value is above size target, and is of a compressible datatype,
118 : : * try to compress it in-line.
119 : : */
120 [ + + + + ]: 1327879 : if (!VARATT_IS_EXTENDED(DatumGetPointer(untoasted_values[i])) &&
121 : 465977 : VARSIZE(DatumGetPointer(untoasted_values[i])) > TOAST_INDEX_TARGET &&
122 [ + + ]: 64878 : (att->attstorage == TYPSTORAGE_EXTENDED ||
123 [ - + ]: 58231 : att->attstorage == TYPSTORAGE_MAIN))
124 : : {
125 : : Datum cvalue;
126 : :
127 : 6647 : cvalue = toast_compress_datum(untoasted_values[i],
128 : 6647 : att->attcompression);
129 : :
130 [ + + ]: 6647 : if (DatumGetPointer(cvalue) != NULL)
131 : : {
132 : : /* successful compression */
133 [ - + ]: 1932 : if (untoasted_free[i])
134 : 0 : pfree(DatumGetPointer(untoasted_values[i]));
135 : 1932 : untoasted_values[i] = cvalue;
136 : 1932 : untoasted_free[i] = true;
137 : : }
138 : : }
139 : : }
140 : : #endif
141 : :
142 [ + + ]: 42904822 : for (i = 0; i < numberOfAttributes; i++)
143 : : {
144 [ + + ]: 26275566 : if (isnull[i])
145 : : {
146 : 56746 : hasnull = true;
147 : 56746 : break;
148 : : }
149 : : }
150 : :
151 [ + + ]: 16686002 : if (hasnull)
152 : 56746 : infomask |= INDEX_NULL_MASK;
153 : :
154 : 16686002 : hoff = IndexInfoFindDataOffset(infomask);
155 : : #ifdef TOAST_INDEX_HACK
156 : 16686002 : data_size = heap_compute_data_size(tupleDescriptor,
157 : : untoasted_values, isnull);
158 : : #else
159 : : data_size = heap_compute_data_size(tupleDescriptor,
160 : : values, isnull);
161 : : #endif
162 : 16686002 : size = hoff + data_size;
163 : 16686002 : size = MAXALIGN(size); /* be conservative */
164 : :
165 : 16686002 : tp = (char *) MemoryContextAllocZero(context, size);
166 : 16686002 : tuple = (IndexTuple) tp;
167 : :
168 [ + + ]: 16686002 : heap_fill_tuple(tupleDescriptor,
169 : : #ifdef TOAST_INDEX_HACK
170 : : untoasted_values,
171 : : #else
172 : : values,
173 : : #endif
174 : : isnull,
175 : : tp + hoff,
176 : : data_size,
177 : : &tupmask,
178 : : (hasnull ? (uint8 *) tp + sizeof(IndexTupleData) : NULL));
179 : :
180 : : #ifdef TOAST_INDEX_HACK
181 [ + + ]: 42963215 : for (i = 0; i < numberOfAttributes; i++)
182 : : {
183 [ + + ]: 26277213 : if (untoasted_free[i])
184 : 2173 : pfree(DatumGetPointer(untoasted_values[i]));
185 : : }
186 : : #endif
187 : :
188 : : /*
189 : : * We do this because heap_fill_tuple wants to initialize a "tupmask"
190 : : * which is used for HeapTuples, but we want an indextuple infomask. The
191 : : * only relevant info is the "has variable attributes" field. We have
192 : : * already set the hasnull bit above.
193 : : */
194 [ + + ]: 16686002 : if (tupmask & HEAP_HASVARWIDTH)
195 : 2110494 : infomask |= INDEX_VAR_MASK;
196 : :
197 : : /* Also assert we got rid of external attributes */
198 : : #ifdef TOAST_INDEX_HACK
199 : : Assert((tupmask & HEAP_HASEXTERNAL) == 0);
200 : : #endif
201 : :
202 : : /*
203 : : * Here we make sure that the size will fit in the field reserved for it
204 : : * in t_info.
205 : : */
206 [ - + ]: 16686002 : if ((size & INDEX_SIZE_MASK) != size)
207 [ # # ]: 0 : ereport(ERROR,
208 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
209 : : errmsg("index row requires %zu bytes, maximum size is %zu",
210 : : size, (Size) INDEX_SIZE_MASK)));
211 : :
212 : 16686002 : infomask |= size;
213 : :
214 : : /*
215 : : * initialize metadata
216 : : */
217 : 16686002 : tuple->t_info = infomask;
218 : 16686002 : return tuple;
219 : : }
220 : :
221 : : /* ----------------
222 : : * nocache_index_getattr
223 : : *
224 : : * This gets called from index_getattr() macro, and only in cases
225 : : * where we can't use attcacheoff and the value is not null.
226 : : * ----------------
227 : : */
228 : : Datum
229 : 30531406 : nocache_index_getattr(IndexTuple tup,
230 : : int attnum,
231 : : TupleDesc tupleDesc)
232 : : {
233 : : CompactAttribute *cattr;
234 : : char *tp; /* ptr to data part of tuple */
235 : 30531406 : uint8 *bp = NULL; /* ptr to null bitmap in tuple */
236 : : int data_off; /* tuple data offset */
237 : : int off; /* current offset within data */
238 : : int startAttr;
239 : : int firstNullAttr;
240 : 30531406 : bool hasnulls = IndexTupleHasNulls(tup);
241 : : int i;
242 : :
243 : : /* Did someone forget to call TupleDescFinalize()? */
244 : : Assert(tupleDesc->firstNonCachedOffsetAttr >= 0);
245 : :
246 : 30531406 : attnum--;
247 : :
248 : 30531406 : data_off = IndexInfoFindDataOffset(tup->t_info);
249 : 30531406 : tp = (char *) tup + data_off;
250 : :
251 : : /*
252 : : * To minimize the number of attributes we need to look at, start walking
253 : : * the tuple at the attribute with the highest attcacheoff prior to attnum
254 : : * or the first NULL attribute prior to attnum, whichever comes first.
255 : : */
256 [ + + ]: 30531406 : if (hasnulls)
257 : : {
258 : 64240 : bp = (uint8 *) ((char *) tup + sizeof(IndexTupleData));
259 : 64240 : firstNullAttr = first_null_attr(bp, attnum);
260 : : }
261 : : else
262 : 30467166 : firstNullAttr = attnum;
263 : :
264 [ + + + + ]: 30531406 : if (tupleDesc->firstNonCachedOffsetAttr > 0 && firstNullAttr > 0)
265 : : {
266 : : /*
267 : : * Try to start with the highest attribute with an attcacheoff that's
268 : : * prior to the one we're looking for, or with the attribute prior to
269 : : * the first NULL attribute, if there is one.
270 : : */
271 : 5697697 : startAttr = Min(tupleDesc->firstNonCachedOffsetAttr - 1, firstNullAttr - 1);
272 : 5697697 : off = TupleDescCompactAttr(tupleDesc, startAttr)->attcacheoff;
273 : : }
274 : : else
275 : : {
276 : : /* Otherwise, start at the beginning... */
277 : 24833709 : startAttr = 0;
278 : 24833709 : off = 0;
279 : : }
280 : :
281 : : /*
282 : : * Calculate 'off' up to the first NULL attr. We use two cheaper loops
283 : : * when the tuple has no variable-width columns. When variable-width
284 : : * columns exists, we use att_addlength_pointer() to move the offset
285 : : * beyond the current attribute.
286 : : */
287 [ + + ]: 30531406 : if (IndexTupleHasVarwidths(tup))
288 : : {
289 : : /* Calculate the offset up until the first NULL */
290 [ + + ]: 40343836 : for (i = startAttr; i < firstNullAttr; i++)
291 : : {
292 : 9859379 : cattr = TupleDescCompactAttr(tupleDesc, i);
293 : :
294 [ + + + + ]: 9859379 : off = att_pointer_alignby(off,
295 : : cattr->attalignby,
296 : : cattr->attlen,
297 : : tp + off);
298 [ + + + + ]: 9859379 : off = att_addlength_pointer(off, cattr->attlen, tp + off);
299 : : }
300 : :
301 : : /* Calculate the offset for any remaining columns. */
302 [ + + ]: 30484465 : for (; i < attnum; i++)
303 : : {
304 : : Assert(hasnulls);
305 : :
306 [ + - ]: 8 : if (att_isnull(i, bp))
307 : 8 : continue;
308 : :
309 : 0 : cattr = TupleDescCompactAttr(tupleDesc, i);
310 : :
311 [ # # # # ]: 0 : off = att_pointer_alignby(off,
312 : : cattr->attalignby,
313 : : cattr->attlen,
314 : : tp + off);
315 [ # # # # ]: 0 : off = att_addlength_pointer(off, cattr->attlen, tp + off);
316 : : }
317 : : }
318 : : else
319 : : {
320 : : /* Handle tuples with only fixed-width attributes */
321 : :
322 : : /* Calculate the offset up until the first NULL */
323 [ + + ]: 48209 : for (i = startAttr; i < firstNullAttr; i++)
324 : : {
325 : 1260 : cattr = TupleDescCompactAttr(tupleDesc, i);
326 : :
327 : : Assert(cattr->attlen > 0);
328 : 1260 : off = att_nominal_alignby(off, cattr->attalignby);
329 : 1260 : off += cattr->attlen;
330 : : }
331 : :
332 : : /* Calculate the offset for any remaining columns. */
333 [ + + ]: 47401 : for (; i < attnum; i++)
334 : : {
335 : : Assert(hasnulls);
336 : :
337 [ + - ]: 452 : if (att_isnull(i, bp))
338 : 452 : continue;
339 : :
340 : 0 : cattr = TupleDescCompactAttr(tupleDesc, i);
341 : :
342 : : Assert(cattr->attlen > 0);
343 : 0 : off = att_nominal_alignby(off, cattr->attalignby);
344 : 0 : off += cattr->attlen;
345 : : }
346 : : }
347 : :
348 : 30531406 : cattr = TupleDescCompactAttr(tupleDesc, attnum);
349 [ + + + + ]: 30531406 : off = att_pointer_alignby(off, cattr->attalignby,
350 : : cattr->attlen, tp + off);
351 : 30531406 : return fetchatt(cattr, tp + off);
352 : : }
353 : :
354 : : /*
355 : : * Convert an index tuple into Datum/isnull arrays.
356 : : *
357 : : * The caller must allocate sufficient storage for the output arrays.
358 : : * (INDEX_MAX_KEYS entries should be enough.)
359 : : *
360 : : * This is nearly the same as heap_deform_tuple(), but for IndexTuples.
361 : : * One difference is that the tuple should never have any missing columns.
362 : : */
363 : : void
364 : 2887393 : index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
365 : : Datum *values, bool *isnull)
366 : : {
367 : : char *tp; /* ptr to tuple data */
368 : : uint8 *bp; /* ptr to null bitmap in tuple */
369 : :
370 : : /* XXX "knows" t_bits are just after fixed tuple header! */
371 : 2887393 : bp = (uint8 *) ((char *) tup + sizeof(IndexTupleData));
372 : :
373 : 2887393 : tp = (char *) tup + IndexInfoFindDataOffset(tup->t_info);
374 : :
375 : 2887393 : index_deform_tuple_internal(tupleDescriptor, values, isnull,
376 : 2887393 : tp, bp, IndexTupleHasNulls(tup));
377 : 2887393 : }
378 : :
379 : : /*
380 : : * Convert an index tuple into Datum/isnull arrays,
381 : : * without assuming any specific layout of the index tuple header.
382 : : *
383 : : * Caller must supply pointer to data area, pointer to nulls bitmap
384 : : * (which can be NULL if !hasnulls), and hasnulls flag.
385 : : */
386 : : void
387 : 2927918 : index_deform_tuple_internal(TupleDesc tupleDescriptor,
388 : : Datum *values, bool *isnull,
389 : : char *tp, uint8 *bp, int hasnulls)
390 : : {
391 : : CompactAttribute *cattr;
392 : 2927918 : int natts = tupleDescriptor->natts; /* number of atts to extract */
393 : 2927918 : int attnum = 0;
394 : 2927918 : uint32 off = 0; /* offset in tuple data */
395 : : int firstNonCacheOffsetAttr;
396 : : int firstNullAttr;
397 : :
398 : : /* Assert to protect callers who allocate fixed-size arrays */
399 : : Assert(natts <= INDEX_MAX_KEYS);
400 : :
401 : : /* Did someone forget to call TupleDescFinalize()? */
402 : : Assert(tupleDescriptor->firstNonCachedOffsetAttr >= 0);
403 : :
404 : 2927918 : firstNonCacheOffsetAttr = Min(tupleDescriptor->firstNonCachedOffsetAttr, natts);
405 : :
406 [ + + ]: 2927918 : if (hasnulls)
407 : : {
408 : 2711 : firstNullAttr = first_null_attr(bp, natts);
409 : 2711 : firstNonCacheOffsetAttr = Min(firstNonCacheOffsetAttr, firstNullAttr);
410 : : }
411 : : else
412 : 2925207 : firstNullAttr = natts;
413 : :
414 [ + + ]: 2927918 : if (firstNonCacheOffsetAttr > 0)
415 : : {
416 : : #ifdef USE_ASSERT_CHECKING
417 : : /* In Assert enabled builds, verify attcacheoff is correct */
418 : : off = 0;
419 : : #endif
420 : :
421 : : do
422 : : {
423 : 3976455 : isnull[attnum] = false;
424 : 3976455 : cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
425 : :
426 : : #ifdef USE_ASSERT_CHECKING
427 : : off = att_nominal_alignby(off, cattr->attalignby);
428 : : Assert(off == cattr->attcacheoff);
429 : : off += cattr->attlen;
430 : : #endif
431 : :
432 : 7952910 : values[attnum] = fetch_att_noerr(tp + cattr->attcacheoff, cattr->attbyval,
433 : 3976455 : cattr->attlen);
434 [ + + ]: 3976455 : } while (++attnum < firstNonCacheOffsetAttr);
435 : :
436 : 2880175 : off = cattr->attcacheoff + cattr->attlen;
437 : : }
438 : :
439 [ + + ]: 2985357 : for (; attnum < firstNullAttr; attnum++)
440 : : {
441 : 57439 : isnull[attnum] = false;
442 : 57439 : cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
443 : :
444 : : /* align 'off', fetch the datum, and increment off beyond the datum */
445 : 57439 : values[attnum] = align_fetch_then_add(tp,
446 : : &off,
447 : 57439 : cattr->attbyval,
448 : 57439 : cattr->attlen,
449 : 57439 : cattr->attalignby);
450 : : }
451 : :
452 [ + + ]: 2933117 : for (; attnum < natts; attnum++)
453 : : {
454 : : Assert(hasnulls);
455 : :
456 [ + + ]: 5199 : if (att_isnull(attnum, bp))
457 : : {
458 : 2730 : values[attnum] = (Datum) 0;
459 : 2730 : isnull[attnum] = true;
460 : 2730 : continue;
461 : : }
462 : :
463 : 2469 : isnull[attnum] = false;
464 : 2469 : cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
465 : :
466 : : /* align 'off', fetch the attr's value, and increment off beyond it */
467 : 2469 : values[attnum] = align_fetch_then_add(tp,
468 : : &off,
469 : 2469 : cattr->attbyval,
470 : 2469 : cattr->attlen,
471 : 2469 : cattr->attalignby);
472 : : }
473 : 2927918 : }
474 : :
475 : : /*
476 : : * Create a palloc'd copy of an index tuple.
477 : : */
478 : : IndexTuple
479 : 3306371 : CopyIndexTuple(IndexTuple source)
480 : : {
481 : : IndexTuple result;
482 : : Size size;
483 : :
484 : 3306371 : size = IndexTupleSize(source);
485 : 3306371 : result = (IndexTuple) palloc(size);
486 : 3306371 : memcpy(result, source, size);
487 : 3306371 : return result;
488 : : }
489 : :
490 : : /*
491 : : * Create a palloc'd copy of an index tuple, leaving only the first
492 : : * leavenatts attributes remaining.
493 : : *
494 : : * Truncation is guaranteed to result in an index tuple that is no
495 : : * larger than the original. It is safe to use the IndexTuple with
496 : : * the original tuple descriptor, but caller must avoid actually
497 : : * accessing truncated attributes from returned tuple! In practice
498 : : * this means that index_getattr() must be called with special care,
499 : : * and that the truncated tuple should only ever be accessed by code
500 : : * under caller's direct control.
501 : : *
502 : : * It's safe to call this function with a buffer lock held, since it
503 : : * never performs external table access. If it ever became possible
504 : : * for index tuples to contain EXTERNAL TOAST values, then this would
505 : : * have to be revisited.
506 : : */
507 : : IndexTuple
508 : 39462 : index_truncate_tuple(TupleDesc sourceDescriptor, IndexTuple source,
509 : : int leavenatts)
510 : : {
511 : : TupleDesc truncdesc;
512 : : Datum values[INDEX_MAX_KEYS];
513 : : bool isnull[INDEX_MAX_KEYS];
514 : : IndexTuple truncated;
515 : :
516 : : Assert(leavenatts <= sourceDescriptor->natts);
517 : :
518 : : /* Easy case: no truncation actually required */
519 [ + + ]: 39462 : if (leavenatts == sourceDescriptor->natts)
520 : 23112 : return CopyIndexTuple(source);
521 : :
522 : : /* Create temporary truncated tuple descriptor */
523 : 16350 : truncdesc = CreateTupleDescTruncatedCopy(sourceDescriptor, leavenatts);
524 : :
525 : : /* Deform, form copy of tuple with fewer attributes */
526 : 16350 : index_deform_tuple(source, truncdesc, values, isnull);
527 : 16350 : truncated = index_form_tuple(truncdesc, values, isnull);
528 : 16350 : truncated->t_tid = source->t_tid;
529 : : Assert(IndexTupleSize(truncated) <= IndexTupleSize(source));
530 : :
531 : : /*
532 : : * Cannot leak memory here, TupleDescCopy() doesn't allocate any inner
533 : : * structure, so, plain pfree() should clean all allocated memory
534 : : */
535 : 16350 : pfree(truncdesc);
536 : :
537 : 16350 : return truncated;
538 : : }
|