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-2024, 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 12324664 : index_form_tuple(TupleDesc tupleDescriptor,
45 : const Datum *values,
46 : const bool *isnull)
47 : {
48 12324664 : 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 24886172 : 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 24886172 : unsigned short infomask = 0;
77 24886172 : bool hasnull = false;
78 24886172 : uint16 tupmask = 0;
79 24886172 : int numberOfAttributes = tupleDescriptor->natts;
80 :
81 : #ifdef TOAST_INDEX_HACK
82 24886172 : Datum untoasted_values[INDEX_MAX_KEYS] = {0};
83 24886172 : bool untoasted_free[INDEX_MAX_KEYS] = {0};
84 : #endif
85 :
86 24886172 : 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 64293806 : for (i = 0; i < numberOfAttributes; i++)
94 : {
95 39407634 : Form_pg_attribute att = TupleDescAttr(tupleDescriptor, i);
96 :
97 39407634 : untoasted_values[i] = values[i];
98 39407634 : untoasted_free[i] = false;
99 :
100 : /* Do nothing if value is NULL or not of varlena type */
101 39407634 : if (isnull[i] || att->attlen != -1)
102 37998998 : 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 1408636 : if (VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
109 : {
110 362 : untoasted_values[i] =
111 362 : PointerGetDatum(detoast_external_attr((struct varlena *)
112 362 : DatumGetPointer(values[i])));
113 362 : 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 1408636 : if (!VARATT_IS_EXTENDED(DatumGetPointer(untoasted_values[i])) &&
121 813106 : VARSIZE(DatumGetPointer(untoasted_values[i])) > TOAST_INDEX_TARGET &&
122 128588 : (att->attstorage == TYPSTORAGE_EXTENDED ||
123 115924 : att->attstorage == TYPSTORAGE_MAIN))
124 : {
125 : Datum cvalue;
126 :
127 12664 : cvalue = toast_compress_datum(untoasted_values[i],
128 12664 : att->attcompression);
129 :
130 12664 : if (DatumGetPointer(cvalue) != NULL)
131 : {
132 : /* successful compression */
133 2542 : if (untoasted_free[i])
134 0 : pfree(DatumGetPointer(untoasted_values[i]));
135 2542 : untoasted_values[i] = cvalue;
136 2542 : untoasted_free[i] = true;
137 : }
138 : }
139 : }
140 : #endif
141 :
142 64184960 : for (i = 0; i < numberOfAttributes; i++)
143 : {
144 39405430 : if (isnull[i])
145 : {
146 106642 : hasnull = true;
147 106642 : break;
148 : }
149 : }
150 :
151 24886172 : if (hasnull)
152 106642 : infomask |= INDEX_NULL_MASK;
153 :
154 24886172 : hoff = IndexInfoFindDataOffset(infomask);
155 : #ifdef TOAST_INDEX_HACK
156 24886172 : 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 24886172 : size = hoff + data_size;
163 24886172 : size = MAXALIGN(size); /* be conservative */
164 :
165 24886172 : tp = (char *) MemoryContextAllocZero(context, size);
166 24886172 : tuple = (IndexTuple) tp;
167 :
168 24886172 : heap_fill_tuple(tupleDescriptor,
169 : #ifdef TOAST_INDEX_HACK
170 : untoasted_values,
171 : #else
172 : values,
173 : #endif
174 : isnull,
175 : (char *) tp + hoff,
176 : data_size,
177 : &tupmask,
178 : (hasnull ? (bits8 *) tp + sizeof(IndexTupleData) : NULL));
179 :
180 : #ifdef TOAST_INDEX_HACK
181 64293806 : for (i = 0; i < numberOfAttributes; i++)
182 : {
183 39407634 : if (untoasted_free[i])
184 2904 : 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 24886172 : if (tupmask & HEAP_HASVARWIDTH)
195 3242786 : 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 24886172 : 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 24886172 : infomask |= size;
213 :
214 : /*
215 : * initialize metadata
216 : */
217 24886172 : tuple->t_info = infomask;
218 24886172 : 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 cacheoffset and the value is not null.
226 : *
227 : * This caches attribute offsets in the attribute descriptor.
228 : *
229 : * An alternative way to speed things up would be to cache offsets
230 : * with the tuple, but that seems more difficult unless you take
231 : * the storage hit of actually putting those offsets into the
232 : * tuple you send to disk. Yuck.
233 : *
234 : * This scheme will be slightly slower than that, but should
235 : * perform well for queries which hit large #'s of tuples. After
236 : * you cache the offsets once, examining all the other tuples using
237 : * the same attribute descriptor will go much quicker. -cim 5/4/91
238 : * ----------------
239 : */
240 : Datum
241 5880364 : nocache_index_getattr(IndexTuple tup,
242 : int attnum,
243 : TupleDesc tupleDesc)
244 : {
245 : char *tp; /* ptr to data part of tuple */
246 5880364 : bits8 *bp = NULL; /* ptr to null bitmap in tuple */
247 5880364 : bool slow = false; /* do we have to walk attrs? */
248 : int data_off; /* tuple data offset */
249 : int off; /* current offset within data */
250 :
251 : /* ----------------
252 : * Three cases:
253 : *
254 : * 1: No nulls and no variable-width attributes.
255 : * 2: Has a null or a var-width AFTER att.
256 : * 3: Has nulls or var-widths BEFORE att.
257 : * ----------------
258 : */
259 :
260 5880364 : data_off = IndexInfoFindDataOffset(tup->t_info);
261 :
262 5880364 : attnum--;
263 :
264 5880364 : if (IndexTupleHasNulls(tup))
265 : {
266 : /*
267 : * there's a null somewhere in the tuple
268 : *
269 : * check to see if desired att is null
270 : */
271 :
272 : /* XXX "knows" t_bits are just after fixed tuple header! */
273 113240 : bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
274 :
275 : /*
276 : * Now check to see if any preceding bits are null...
277 : */
278 : {
279 113240 : int byte = attnum >> 3;
280 113240 : int finalbit = attnum & 0x07;
281 :
282 : /* check for nulls "before" final bit of last byte */
283 113240 : if ((~bp[byte]) & ((1 << finalbit) - 1))
284 18 : slow = true;
285 : else
286 : {
287 : /* check for nulls in any "earlier" bytes */
288 : int i;
289 :
290 113222 : for (i = 0; i < byte; i++)
291 : {
292 0 : if (bp[i] != 0xFF)
293 : {
294 0 : slow = true;
295 0 : break;
296 : }
297 : }
298 : }
299 : }
300 : }
301 :
302 5880364 : tp = (char *) tup + data_off;
303 :
304 5880364 : if (!slow)
305 : {
306 : Form_pg_attribute att;
307 :
308 : /*
309 : * If we get here, there are no nulls up to and including the target
310 : * attribute. If we have a cached offset, we can use it.
311 : */
312 5880346 : att = TupleDescAttr(tupleDesc, attnum);
313 5880346 : if (att->attcacheoff >= 0)
314 113074 : return fetchatt(att, tp + att->attcacheoff);
315 :
316 : /*
317 : * Otherwise, check for non-fixed-length attrs up to and including
318 : * target. If there aren't any, it's safe to cheaply initialize the
319 : * cached offsets for these attrs.
320 : */
321 5767272 : if (IndexTupleHasVarwidths(tup))
322 : {
323 : int j;
324 :
325 6295626 : for (j = 0; j <= attnum; j++)
326 : {
327 6293520 : if (TupleDescAttr(tupleDesc, j)->attlen <= 0)
328 : {
329 5689616 : slow = true;
330 5689616 : break;
331 : }
332 : }
333 : }
334 : }
335 :
336 5767290 : if (!slow)
337 : {
338 77656 : int natts = tupleDesc->natts;
339 77656 : int j = 1;
340 :
341 : /*
342 : * If we get here, we have a tuple with no nulls or var-widths up to
343 : * and including the target attribute, so we can use the cached offset
344 : * ... only we don't have it yet, or we'd not have got here. Since
345 : * it's cheap to compute offsets for fixed-width columns, we take the
346 : * opportunity to initialize the cached offsets for *all* the leading
347 : * fixed-width columns, in hope of avoiding future visits to this
348 : * routine.
349 : */
350 77656 : TupleDescAttr(tupleDesc, 0)->attcacheoff = 0;
351 :
352 : /* we might have set some offsets in the slow path previously */
353 77656 : while (j < natts && TupleDescAttr(tupleDesc, j)->attcacheoff > 0)
354 0 : j++;
355 :
356 77656 : off = TupleDescAttr(tupleDesc, j - 1)->attcacheoff +
357 77656 : TupleDescAttr(tupleDesc, j - 1)->attlen;
358 :
359 198714 : for (; j < natts; j++)
360 : {
361 123122 : Form_pg_attribute att = TupleDescAttr(tupleDesc, j);
362 :
363 123122 : if (att->attlen <= 0)
364 2064 : break;
365 :
366 121058 : off = att_align_nominal(off, att->attalign);
367 :
368 121058 : att->attcacheoff = off;
369 :
370 121058 : off += att->attlen;
371 : }
372 :
373 : Assert(j > attnum);
374 :
375 77656 : off = TupleDescAttr(tupleDesc, attnum)->attcacheoff;
376 : }
377 : else
378 : {
379 5689634 : bool usecache = true;
380 : int i;
381 :
382 : /*
383 : * Now we know that we have to walk the tuple CAREFULLY. But we still
384 : * might be able to cache some offsets for next time.
385 : *
386 : * Note - This loop is a little tricky. For each non-null attribute,
387 : * we have to first account for alignment padding before the attr,
388 : * then advance over the attr based on its length. Nulls have no
389 : * storage and no alignment padding either. We can use/set
390 : * attcacheoff until we reach either a null or a var-width attribute.
391 : */
392 5689634 : off = 0;
393 5689634 : for (i = 0;; i++) /* loop exit is at "break" */
394 7007918 : {
395 12697552 : Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
396 :
397 12697552 : if (IndexTupleHasNulls(tup) && att_isnull(i, bp))
398 : {
399 18 : usecache = false;
400 18 : continue; /* this cannot be the target att */
401 : }
402 :
403 : /* If we know the next offset, we can skip the rest */
404 12697534 : if (usecache && att->attcacheoff >= 0)
405 6270706 : off = att->attcacheoff;
406 6426828 : else if (att->attlen == -1)
407 : {
408 : /*
409 : * We can only cache the offset for a varlena attribute if the
410 : * offset is already suitably aligned, so that there would be
411 : * no pad bytes in any case: then the offset will be valid for
412 : * either an aligned or unaligned value.
413 : */
414 1202614 : if (usecache &&
415 9144 : off == att_align_nominal(off, att->attalign))
416 1584 : att->attcacheoff = off;
417 : else
418 : {
419 1191886 : off = att_align_pointer(off, att->attalign, -1,
420 : tp + off);
421 1191886 : usecache = false;
422 : }
423 : }
424 : else
425 : {
426 : /* not varlena, so safe to use att_align_nominal */
427 5233358 : off = att_align_nominal(off, att->attalign);
428 :
429 5233358 : if (usecache)
430 9928 : att->attcacheoff = off;
431 : }
432 :
433 12697534 : if (i == attnum)
434 5689634 : break;
435 :
436 7007900 : off = att_addlength_pointer(off, att->attlen, tp + off);
437 :
438 7007900 : if (usecache && att->attlen <= 0)
439 5670594 : usecache = false;
440 : }
441 : }
442 :
443 5767290 : return fetchatt(TupleDescAttr(tupleDesc, attnum), tp + off);
444 : }
445 :
446 : /*
447 : * Convert an index tuple into Datum/isnull arrays.
448 : *
449 : * The caller must allocate sufficient storage for the output arrays.
450 : * (INDEX_MAX_KEYS entries should be enough.)
451 : *
452 : * This is nearly the same as heap_deform_tuple(), but for IndexTuples.
453 : * One difference is that the tuple should never have any missing columns.
454 : */
455 : void
456 3898056 : index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
457 : Datum *values, bool *isnull)
458 : {
459 : char *tp; /* ptr to tuple data */
460 : bits8 *bp; /* ptr to null bitmap in tuple */
461 :
462 : /* XXX "knows" t_bits are just after fixed tuple header! */
463 3898056 : bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
464 :
465 3898056 : tp = (char *) tup + IndexInfoFindDataOffset(tup->t_info);
466 :
467 3898056 : index_deform_tuple_internal(tupleDescriptor, values, isnull,
468 3898056 : tp, bp, IndexTupleHasNulls(tup));
469 3898056 : }
470 :
471 : /*
472 : * Convert an index tuple into Datum/isnull arrays,
473 : * without assuming any specific layout of the index tuple header.
474 : *
475 : * Caller must supply pointer to data area, pointer to nulls bitmap
476 : * (which can be NULL if !hasnulls), and hasnulls flag.
477 : */
478 : void
479 3960664 : index_deform_tuple_internal(TupleDesc tupleDescriptor,
480 : Datum *values, bool *isnull,
481 : char *tp, bits8 *bp, int hasnulls)
482 : {
483 3960664 : int natts = tupleDescriptor->natts; /* number of atts to extract */
484 : int attnum;
485 3960664 : int off = 0; /* offset in tuple data */
486 3960664 : bool slow = false; /* can we use/set attcacheoff? */
487 :
488 : /* Assert to protect callers who allocate fixed-size arrays */
489 : Assert(natts <= INDEX_MAX_KEYS);
490 :
491 9156040 : for (attnum = 0; attnum < natts; attnum++)
492 : {
493 5195376 : Form_pg_attribute thisatt = TupleDescAttr(tupleDescriptor, attnum);
494 :
495 5195376 : if (hasnulls && att_isnull(attnum, bp))
496 : {
497 5108 : values[attnum] = (Datum) 0;
498 5108 : isnull[attnum] = true;
499 5108 : slow = true; /* can't use attcacheoff anymore */
500 5108 : continue;
501 : }
502 :
503 5190268 : isnull[attnum] = false;
504 :
505 5190268 : if (!slow && thisatt->attcacheoff >= 0)
506 5169248 : off = thisatt->attcacheoff;
507 21020 : else if (thisatt->attlen == -1)
508 : {
509 : /*
510 : * We can only cache the offset for a varlena attribute if the
511 : * offset is already suitably aligned, so that there would be no
512 : * pad bytes in any case: then the offset will be valid for either
513 : * an aligned or unaligned value.
514 : */
515 10648 : if (!slow &&
516 10 : off == att_align_nominal(off, thisatt->attalign))
517 10 : thisatt->attcacheoff = off;
518 : else
519 : {
520 10628 : off = att_align_pointer(off, thisatt->attalign, -1,
521 : tp + off);
522 10628 : slow = true;
523 : }
524 : }
525 : else
526 : {
527 : /* not varlena, so safe to use att_align_nominal */
528 10382 : off = att_align_nominal(off, thisatt->attalign);
529 :
530 10382 : if (!slow)
531 1114 : thisatt->attcacheoff = off;
532 : }
533 :
534 5190268 : values[attnum] = fetchatt(thisatt, tp + off);
535 :
536 5190268 : off = att_addlength_pointer(off, thisatt->attlen, tp + off);
537 :
538 5190268 : if (thisatt->attlen <= 0)
539 69822 : slow = true; /* can't use attcacheoff anymore */
540 : }
541 3960664 : }
542 :
543 : /*
544 : * Create a palloc'd copy of an index tuple.
545 : */
546 : IndexTuple
547 4975034 : CopyIndexTuple(IndexTuple source)
548 : {
549 : IndexTuple result;
550 : Size size;
551 :
552 4975034 : size = IndexTupleSize(source);
553 4975034 : result = (IndexTuple) palloc(size);
554 4975034 : memcpy(result, source, size);
555 4975034 : return result;
556 : }
557 :
558 : /*
559 : * Create a palloc'd copy of an index tuple, leaving only the first
560 : * leavenatts attributes remaining.
561 : *
562 : * Truncation is guaranteed to result in an index tuple that is no
563 : * larger than the original. It is safe to use the IndexTuple with
564 : * the original tuple descriptor, but caller must avoid actually
565 : * accessing truncated attributes from returned tuple! In practice
566 : * this means that index_getattr() must be called with special care,
567 : * and that the truncated tuple should only ever be accessed by code
568 : * under caller's direct control.
569 : *
570 : * It's safe to call this function with a buffer lock held, since it
571 : * never performs external table access. If it ever became possible
572 : * for index tuples to contain EXTERNAL TOAST values, then this would
573 : * have to be revisited.
574 : */
575 : IndexTuple
576 59770 : index_truncate_tuple(TupleDesc sourceDescriptor, IndexTuple source,
577 : int leavenatts)
578 : {
579 : TupleDesc truncdesc;
580 : Datum values[INDEX_MAX_KEYS];
581 : bool isnull[INDEX_MAX_KEYS];
582 : IndexTuple truncated;
583 :
584 : Assert(leavenatts <= sourceDescriptor->natts);
585 :
586 : /* Easy case: no truncation actually required */
587 59770 : if (leavenatts == sourceDescriptor->natts)
588 34686 : return CopyIndexTuple(source);
589 :
590 : /* Create temporary descriptor to scribble on */
591 25084 : truncdesc = palloc(TupleDescSize(sourceDescriptor));
592 25084 : TupleDescCopy(truncdesc, sourceDescriptor);
593 25084 : truncdesc->natts = leavenatts;
594 :
595 : /* Deform, form copy of tuple with fewer attributes */
596 25084 : index_deform_tuple(source, truncdesc, values, isnull);
597 25084 : truncated = index_form_tuple(truncdesc, values, isnull);
598 25084 : truncated->t_tid = source->t_tid;
599 : Assert(IndexTupleSize(truncated) <= IndexTupleSize(source));
600 :
601 : /*
602 : * Cannot leak memory here, TupleDescCopy() doesn't allocate any inner
603 : * structure, so, plain pfree() should clean all allocated memory
604 : */
605 25084 : pfree(truncdesc);
606 :
607 25084 : return truncated;
608 : }
|