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 6907283 : index_form_tuple(TupleDesc tupleDescriptor,
45 : const Datum *values,
46 : const bool *isnull)
47 : {
48 6907283 : 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 13649259 : 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 13649259 : unsigned short infomask = 0;
77 13649259 : bool hasnull = false;
78 13649259 : uint16 tupmask = 0;
79 13649259 : int numberOfAttributes = tupleDescriptor->natts;
80 :
81 : #ifdef TOAST_INDEX_HACK
82 13649259 : Datum untoasted_values[INDEX_MAX_KEYS] = {0};
83 13649259 : bool untoasted_free[INDEX_MAX_KEYS] = {0};
84 : #endif
85 :
86 13649259 : 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 35218575 : for (i = 0; i < numberOfAttributes; i++)
94 : {
95 21569316 : Form_pg_attribute att = TupleDescAttr(tupleDescriptor, i);
96 :
97 21569316 : untoasted_values[i] = values[i];
98 21569316 : untoasted_free[i] = false;
99 :
100 : /* Do nothing if value is NULL or not of varlena type */
101 21569316 : if (isnull[i] || att->attlen != -1)
102 20842472 : 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 726844 : if (VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
109 : {
110 181 : untoasted_values[i] =
111 181 : PointerGetDatum(detoast_external_attr((varlena *)
112 181 : DatumGetPointer(values[i])));
113 181 : 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 1153681 : if (!VARATT_IS_EXTENDED(DatumGetPointer(untoasted_values[i])) &&
121 426837 : VARSIZE(DatumGetPointer(untoasted_values[i])) > TOAST_INDEX_TARGET &&
122 64492 : (att->attstorage == TYPSTORAGE_EXTENDED ||
123 57738 : att->attstorage == TYPSTORAGE_MAIN))
124 : {
125 : Datum cvalue;
126 :
127 6754 : cvalue = toast_compress_datum(untoasted_values[i],
128 6754 : att->attcompression);
129 :
130 6754 : if (DatumGetPointer(cvalue) != NULL)
131 : {
132 : /* successful compression */
133 1867 : if (untoasted_free[i])
134 0 : pfree(DatumGetPointer(untoasted_values[i]));
135 1867 : untoasted_values[i] = cvalue;
136 1867 : untoasted_free[i] = true;
137 : }
138 : }
139 : }
140 : #endif
141 :
142 35163995 : for (i = 0; i < numberOfAttributes; i++)
143 : {
144 21568145 : if (isnull[i])
145 : {
146 53409 : hasnull = true;
147 53409 : break;
148 : }
149 : }
150 :
151 13649259 : if (hasnull)
152 53409 : infomask |= INDEX_NULL_MASK;
153 :
154 13649259 : hoff = IndexInfoFindDataOffset(infomask);
155 : #ifdef TOAST_INDEX_HACK
156 13649259 : 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 13649259 : size = hoff + data_size;
163 13649259 : size = MAXALIGN(size); /* be conservative */
164 :
165 13649259 : tp = (char *) MemoryContextAllocZero(context, size);
166 13649259 : tuple = (IndexTuple) tp;
167 :
168 13649259 : 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 ? (bits8 *) tp + sizeof(IndexTupleData) : NULL));
179 :
180 : #ifdef TOAST_INDEX_HACK
181 35218575 : for (i = 0; i < numberOfAttributes; i++)
182 : {
183 21569316 : if (untoasted_free[i])
184 2048 : 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 13649259 : if (tupmask & HEAP_HASVARWIDTH)
195 1762969 : 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 13649259 : 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 13649259 : infomask |= size;
213 :
214 : /*
215 : * initialize metadata
216 : */
217 13649259 : tuple->t_info = infomask;
218 13649259 : 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 : */
228 : Datum
229 25627340 : 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 25627340 : bits8 *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 25627340 : bool hasnulls = IndexTupleHasNulls(tup);
241 : int i;
242 :
243 : /* Did someone forget to call TupleDescFinalize()? */
244 : Assert(tupleDesc->firstNonCachedOffsetAttr >= 0);
245 :
246 25627340 : attnum--;
247 :
248 25627340 : data_off = IndexInfoFindDataOffset(tup->t_info);
249 25627340 : 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 25627340 : if (hasnulls)
257 : {
258 57298 : bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
259 57298 : firstNullAttr = first_null_attr(bp, attnum);
260 : }
261 : else
262 25570042 : firstNullAttr = attnum;
263 :
264 25627340 : if (tupleDesc->firstNonCachedOffsetAttr > 0)
265 : {
266 : /*
267 : * Start at the highest attcacheoff attribute with no NULLs in prior
268 : * attributes.
269 : */
270 4871028 : startAttr = Min(tupleDesc->firstNonCachedOffsetAttr - 1, firstNullAttr);
271 4871028 : off = TupleDescCompactAttr(tupleDesc, startAttr)->attcacheoff;
272 : }
273 : else
274 : {
275 : /* Otherwise, start at the beginning... */
276 20756312 : startAttr = 0;
277 20756312 : off = 0;
278 : }
279 :
280 : /*
281 : * Calculate 'off' up to the first NULL attr. We use two cheaper loops
282 : * when the tuple has no variable-width columns. When variable-width
283 : * columns exists, we use att_addlength_pointer() to move the offset
284 : * beyond the current attribute.
285 : */
286 25627340 : if (IndexTupleHasVarwidths(tup))
287 : {
288 : /* Calculate the offset up until the first NULL */
289 33983496 : for (i = startAttr; i < firstNullAttr; i++)
290 : {
291 8399236 : cattr = TupleDescCompactAttr(tupleDesc, i);
292 :
293 8399236 : off = att_pointer_alignby(off,
294 : cattr->attalignby,
295 : cattr->attlen,
296 : tp + off);
297 8399236 : off = att_addlength_pointer(off, cattr->attlen, tp + off);
298 : }
299 :
300 : /* Calculate the offset for any remaining columns. */
301 25584266 : for (; i < attnum; i++)
302 : {
303 : Assert(hasnulls);
304 :
305 6 : if (att_isnull(i, bp))
306 6 : continue;
307 :
308 0 : cattr = TupleDescCompactAttr(tupleDesc, i);
309 :
310 0 : off = att_pointer_alignby(off,
311 : cattr->attalignby,
312 : cattr->attlen,
313 : tp + off);
314 0 : off = att_addlength_pointer(off, cattr->attlen, tp + off);
315 : }
316 : }
317 : else
318 : {
319 : /* Handle tuples with only fixed-width attributes */
320 :
321 : /* Calculate the offset up until the first NULL */
322 43080 : for (i = startAttr; i < firstNullAttr; i++)
323 : {
324 0 : cattr = TupleDescCompactAttr(tupleDesc, i);
325 :
326 : Assert(cattr->attlen > 0);
327 0 : off = att_nominal_alignby(off, cattr->attalignby);
328 0 : off += cattr->attlen;
329 : }
330 :
331 : /* Calculate the offset for any remaining columns. */
332 43419 : for (; i < attnum; i++)
333 : {
334 : Assert(hasnulls);
335 :
336 339 : if (att_isnull(i, bp))
337 339 : continue;
338 :
339 0 : cattr = TupleDescCompactAttr(tupleDesc, i);
340 :
341 : Assert(cattr->attlen > 0);
342 0 : off = att_nominal_alignby(off, cattr->attalignby);
343 0 : off += cattr->attlen;
344 : }
345 : }
346 :
347 25627340 : cattr = TupleDescCompactAttr(tupleDesc, attnum);
348 25627340 : off = att_pointer_alignby(off, cattr->attalignby,
349 : cattr->attlen, tp + off);
350 25627340 : return fetchatt(cattr, tp + off);
351 : }
352 :
353 : /*
354 : * Convert an index tuple into Datum/isnull arrays.
355 : *
356 : * The caller must allocate sufficient storage for the output arrays.
357 : * (INDEX_MAX_KEYS entries should be enough.)
358 : *
359 : * This is nearly the same as heap_deform_tuple(), but for IndexTuples.
360 : * One difference is that the tuple should never have any missing columns.
361 : */
362 : void
363 2027940 : index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
364 : Datum *values, bool *isnull)
365 : {
366 : char *tp; /* ptr to tuple data */
367 : bits8 *bp; /* ptr to null bitmap in tuple */
368 :
369 : /* XXX "knows" t_bits are just after fixed tuple header! */
370 2027940 : bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
371 :
372 2027940 : tp = (char *) tup + IndexInfoFindDataOffset(tup->t_info);
373 :
374 2027940 : index_deform_tuple_internal(tupleDescriptor, values, isnull,
375 2027940 : tp, bp, IndexTupleHasNulls(tup));
376 2027940 : }
377 :
378 : /*
379 : * Convert an index tuple into Datum/isnull arrays,
380 : * without assuming any specific layout of the index tuple header.
381 : *
382 : * Caller must supply pointer to data area, pointer to nulls bitmap
383 : * (which can be NULL if !hasnulls), and hasnulls flag.
384 : */
385 : void
386 2058835 : index_deform_tuple_internal(TupleDesc tupleDescriptor,
387 : Datum *values, bool *isnull,
388 : char *tp, bits8 *bp, int hasnulls)
389 : {
390 : CompactAttribute *cattr;
391 2058835 : int natts = tupleDescriptor->natts; /* number of atts to extract */
392 2058835 : int attnum = 0;
393 2058835 : uint32 off = 0; /* offset in tuple data */
394 : int firstNonCacheOffsetAttr;
395 : int firstNullAttr;
396 :
397 : /* Assert to protect callers who allocate fixed-size arrays */
398 : Assert(natts <= INDEX_MAX_KEYS);
399 :
400 : /* Did someone forget to call TupleDescFinalize()? */
401 : Assert(tupleDescriptor->firstNonCachedOffsetAttr >= 0);
402 :
403 2058835 : firstNonCacheOffsetAttr = Min(tupleDescriptor->firstNonCachedOffsetAttr, natts);
404 :
405 2058835 : if (hasnulls)
406 : {
407 2657 : firstNullAttr = first_null_attr(bp, natts);
408 2657 : firstNonCacheOffsetAttr = Min(firstNonCacheOffsetAttr, firstNullAttr);
409 : }
410 : else
411 2056178 : firstNullAttr = natts;
412 :
413 2058835 : if (firstNonCacheOffsetAttr > 0)
414 : {
415 : #ifdef USE_ASSERT_CHECKING
416 : /* In Assert enabled builds, verify attcacheoff is correct */
417 : off = 0;
418 : #endif
419 :
420 : do
421 : {
422 2821409 : isnull[attnum] = false;
423 2821409 : cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
424 :
425 : #ifdef USE_ASSERT_CHECKING
426 : off = att_nominal_alignby(off, cattr->attalignby);
427 : Assert(off == cattr->attcacheoff);
428 : off += cattr->attlen;
429 : #endif
430 :
431 5642818 : values[attnum] = fetch_att_noerr(tp + cattr->attcacheoff, cattr->attbyval,
432 2821409 : cattr->attlen);
433 2821409 : } while (++attnum < firstNonCacheOffsetAttr);
434 :
435 2028615 : off = cattr->attcacheoff + cattr->attlen;
436 : }
437 :
438 2098474 : for (; attnum < firstNullAttr; attnum++)
439 : {
440 39639 : isnull[attnum] = false;
441 39639 : cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
442 :
443 : /* align 'off', fetch the datum, and increment off beyond the datum */
444 39639 : values[attnum] = align_fetch_then_add(tp,
445 : &off,
446 39639 : cattr->attbyval,
447 39639 : cattr->attlen,
448 39639 : cattr->attalignby);
449 : }
450 :
451 2063957 : for (; attnum < natts; attnum++)
452 : {
453 : Assert(hasnulls);
454 :
455 5122 : if (att_isnull(attnum, bp))
456 : {
457 2669 : values[attnum] = (Datum) 0;
458 2669 : isnull[attnum] = true;
459 2669 : continue;
460 : }
461 :
462 2453 : isnull[attnum] = false;
463 2453 : cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
464 :
465 : /* align 'off', fetch the attr's value, and increment off beyond it */
466 2453 : values[attnum] = align_fetch_then_add(tp,
467 : &off,
468 2453 : cattr->attbyval,
469 2453 : cattr->attlen,
470 2453 : cattr->attalignby);
471 : }
472 2058835 : }
473 :
474 : /*
475 : * Create a palloc'd copy of an index tuple.
476 : */
477 : IndexTuple
478 2538783 : CopyIndexTuple(IndexTuple source)
479 : {
480 : IndexTuple result;
481 : Size size;
482 :
483 2538783 : size = IndexTupleSize(source);
484 2538783 : result = (IndexTuple) palloc(size);
485 2538783 : memcpy(result, source, size);
486 2538783 : return result;
487 : }
488 :
489 : /*
490 : * Create a palloc'd copy of an index tuple, leaving only the first
491 : * leavenatts attributes remaining.
492 : *
493 : * Truncation is guaranteed to result in an index tuple that is no
494 : * larger than the original. It is safe to use the IndexTuple with
495 : * the original tuple descriptor, but caller must avoid actually
496 : * accessing truncated attributes from returned tuple! In practice
497 : * this means that index_getattr() must be called with special care,
498 : * and that the truncated tuple should only ever be accessed by code
499 : * under caller's direct control.
500 : *
501 : * It's safe to call this function with a buffer lock held, since it
502 : * never performs external table access. If it ever became possible
503 : * for index tuples to contain EXTERNAL TOAST values, then this would
504 : * have to be revisited.
505 : */
506 : IndexTuple
507 32477 : index_truncate_tuple(TupleDesc sourceDescriptor, IndexTuple source,
508 : int leavenatts)
509 : {
510 : TupleDesc truncdesc;
511 : Datum values[INDEX_MAX_KEYS];
512 : bool isnull[INDEX_MAX_KEYS];
513 : IndexTuple truncated;
514 :
515 : Assert(leavenatts <= sourceDescriptor->natts);
516 :
517 : /* Easy case: no truncation actually required */
518 32477 : if (leavenatts == sourceDescriptor->natts)
519 18943 : return CopyIndexTuple(source);
520 :
521 : /* Create temporary truncated tuple descriptor */
522 13534 : truncdesc = CreateTupleDescTruncatedCopy(sourceDescriptor, leavenatts);
523 :
524 : /* Deform, form copy of tuple with fewer attributes */
525 13534 : index_deform_tuple(source, truncdesc, values, isnull);
526 13534 : truncated = index_form_tuple(truncdesc, values, isnull);
527 13534 : truncated->t_tid = source->t_tid;
528 : Assert(IndexTupleSize(truncated) <= IndexTupleSize(source));
529 :
530 : /*
531 : * Cannot leak memory here, TupleDescCopy() doesn't allocate any inner
532 : * structure, so, plain pfree() should clean all allocated memory
533 : */
534 13534 : pfree(truncdesc);
535 :
536 13534 : return truncated;
537 : }
|