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 8099747 : index_form_tuple(TupleDesc tupleDescriptor,
45 : const Datum *values,
46 : const bool *isnull)
47 : {
48 8099747 : 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 15947384 : 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 15947384 : unsigned short infomask = 0;
77 15947384 : bool hasnull = false;
78 15947384 : uint16 tupmask = 0;
79 15947384 : int numberOfAttributes = tupleDescriptor->natts;
80 :
81 : #ifdef TOAST_INDEX_HACK
82 15947384 : Datum untoasted_values[INDEX_MAX_KEYS] = {0};
83 15947384 : bool untoasted_free[INDEX_MAX_KEYS] = {0};
84 : #endif
85 :
86 15947384 : 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 41057947 : for (i = 0; i < numberOfAttributes; i++)
94 : {
95 25110563 : Form_pg_attribute att = TupleDescAttr(tupleDescriptor, i);
96 :
97 25110563 : untoasted_values[i] = values[i];
98 25110563 : untoasted_free[i] = false;
99 :
100 : /* Do nothing if value is NULL or not of varlena type */
101 25110563 : if (isnull[i] || att->attlen != -1)
102 24273706 : 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 836857 : if (VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
109 : {
110 241 : untoasted_values[i] =
111 241 : PointerGetDatum(detoast_external_attr((varlena *)
112 241 : 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 1281177 : if (!VARATT_IS_EXTENDED(DatumGetPointer(untoasted_values[i])) &&
121 444320 : VARSIZE(DatumGetPointer(untoasted_values[i])) > TOAST_INDEX_TARGET &&
122 64887 : (att->attstorage == TYPSTORAGE_EXTENDED ||
123 57949 : att->attstorage == TYPSTORAGE_MAIN))
124 : {
125 : Datum cvalue;
126 :
127 6938 : cvalue = toast_compress_datum(untoasted_values[i],
128 6938 : att->attcompression);
129 :
130 6938 : if (DatumGetPointer(cvalue) != NULL)
131 : {
132 : /* successful compression */
133 2024 : if (untoasted_free[i])
134 0 : pfree(DatumGetPointer(untoasted_values[i]));
135 2024 : untoasted_values[i] = cvalue;
136 2024 : untoasted_free[i] = true;
137 : }
138 : }
139 : }
140 : #endif
141 :
142 40999756 : for (i = 0; i < numberOfAttributes; i++)
143 : {
144 25109027 : if (isnull[i])
145 : {
146 56655 : hasnull = true;
147 56655 : break;
148 : }
149 : }
150 :
151 15947384 : if (hasnull)
152 56655 : infomask |= INDEX_NULL_MASK;
153 :
154 15947384 : hoff = IndexInfoFindDataOffset(infomask);
155 : #ifdef TOAST_INDEX_HACK
156 15947384 : 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 15947384 : size = hoff + data_size;
163 15947384 : size = MAXALIGN(size); /* be conservative */
164 :
165 15947384 : tp = (char *) MemoryContextAllocZero(context, size);
166 15947384 : tuple = (IndexTuple) tp;
167 :
168 15947384 : 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 41057947 : for (i = 0; i < numberOfAttributes; i++)
182 : {
183 25110563 : if (untoasted_free[i])
184 2265 : 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 15947384 : if (tupmask & HEAP_HASVARWIDTH)
195 2003441 : 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 15947384 : 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 15947384 : infomask |= size;
213 :
214 : /*
215 : * initialize metadata
216 : */
217 15947384 : tuple->t_info = infomask;
218 15947384 : 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 29246385 : 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 29246385 : 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 29246385 : bool hasnulls = IndexTupleHasNulls(tup);
241 : int i;
242 :
243 : /* Did someone forget to call TupleDescFinalize()? */
244 : Assert(tupleDesc->firstNonCachedOffsetAttr >= 0);
245 :
246 29246385 : attnum--;
247 :
248 29246385 : data_off = IndexInfoFindDataOffset(tup->t_info);
249 29246385 : 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 29246385 : if (hasnulls)
257 : {
258 64238 : bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
259 64238 : firstNullAttr = first_null_attr(bp, attnum);
260 : }
261 : else
262 29182147 : firstNullAttr = attnum;
263 :
264 29246385 : 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 5484752 : startAttr = Min(tupleDesc->firstNonCachedOffsetAttr - 1, firstNullAttr - 1);
272 5484752 : off = TupleDescCompactAttr(tupleDesc, startAttr)->attcacheoff;
273 : }
274 : else
275 : {
276 : /* Otherwise, start at the beginning... */
277 23761633 : startAttr = 0;
278 23761633 : 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 29246385 : if (IndexTupleHasVarwidths(tup))
288 : {
289 : /* Calculate the offset up until the first NULL */
290 38639441 : for (i = startAttr; i < firstNullAttr; i++)
291 : {
292 9440003 : cattr = TupleDescCompactAttr(tupleDesc, i);
293 :
294 9440003 : off = att_pointer_alignby(off,
295 : cattr->attalignby,
296 : cattr->attlen,
297 : tp + off);
298 9440003 : off = att_addlength_pointer(off, cattr->attlen, tp + off);
299 : }
300 :
301 : /* Calculate the offset for any remaining columns. */
302 29199446 : 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 48207 : 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 47399 : 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 29246385 : cattr = TupleDescCompactAttr(tupleDesc, attnum);
349 29246385 : off = att_pointer_alignby(off, cattr->attalignby,
350 : cattr->attlen, tp + off);
351 29246385 : 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 2777246 : index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
365 : Datum *values, bool *isnull)
366 : {
367 : char *tp; /* ptr to tuple data */
368 : bits8 *bp; /* ptr to null bitmap in tuple */
369 :
370 : /* XXX "knows" t_bits are just after fixed tuple header! */
371 2777246 : bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
372 :
373 2777246 : tp = (char *) tup + IndexInfoFindDataOffset(tup->t_info);
374 :
375 2777246 : index_deform_tuple_internal(tupleDescriptor, values, isnull,
376 2777246 : tp, bp, IndexTupleHasNulls(tup));
377 2777246 : }
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 2815696 : index_deform_tuple_internal(TupleDesc tupleDescriptor,
388 : Datum *values, bool *isnull,
389 : char *tp, bits8 *bp, int hasnulls)
390 : {
391 : CompactAttribute *cattr;
392 2815696 : int natts = tupleDescriptor->natts; /* number of atts to extract */
393 2815696 : int attnum = 0;
394 2815696 : 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 2815696 : firstNonCacheOffsetAttr = Min(tupleDescriptor->firstNonCachedOffsetAttr, natts);
405 :
406 2815696 : if (hasnulls)
407 : {
408 2647 : firstNullAttr = first_null_attr(bp, natts);
409 2647 : firstNonCacheOffsetAttr = Min(firstNonCacheOffsetAttr, firstNullAttr);
410 : }
411 : else
412 2813049 : firstNullAttr = natts;
413 :
414 2815696 : 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 3844921 : isnull[attnum] = false;
424 3844921 : 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 7689842 : values[attnum] = fetch_att_noerr(tp + cattr->attcacheoff, cattr->attbyval,
433 3844921 : cattr->attlen);
434 3844921 : } while (++attnum < firstNonCacheOffsetAttr);
435 :
436 2778841 : off = cattr->attcacheoff + cattr->attlen;
437 : }
438 :
439 2862491 : for (; attnum < firstNullAttr; attnum++)
440 : {
441 46795 : isnull[attnum] = false;
442 46795 : cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
443 :
444 : /* align 'off', fetch the datum, and increment off beyond the datum */
445 46795 : values[attnum] = align_fetch_then_add(tp,
446 : &off,
447 46795 : cattr->attbyval,
448 46795 : cattr->attlen,
449 46795 : cattr->attalignby);
450 : }
451 :
452 2820779 : for (; attnum < natts; attnum++)
453 : {
454 : Assert(hasnulls);
455 :
456 5083 : if (att_isnull(attnum, bp))
457 : {
458 2663 : values[attnum] = (Datum) 0;
459 2663 : isnull[attnum] = true;
460 2663 : continue;
461 : }
462 :
463 2420 : isnull[attnum] = false;
464 2420 : cattr = TupleDescCompactAttr(tupleDescriptor, attnum);
465 :
466 : /* align 'off', fetch the attr's value, and increment off beyond it */
467 2420 : values[attnum] = align_fetch_then_add(tp,
468 : &off,
469 2420 : cattr->attbyval,
470 2420 : cattr->attlen,
471 2420 : cattr->attalignby);
472 : }
473 2815696 : }
474 :
475 : /*
476 : * Create a palloc'd copy of an index tuple.
477 : */
478 : IndexTuple
479 3266037 : CopyIndexTuple(IndexTuple source)
480 : {
481 : IndexTuple result;
482 : Size size;
483 :
484 3266037 : size = IndexTupleSize(source);
485 3266037 : result = (IndexTuple) palloc(size);
486 3266037 : memcpy(result, source, size);
487 3266037 : 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 37369 : 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 37369 : if (leavenatts == sourceDescriptor->natts)
520 21984 : return CopyIndexTuple(source);
521 :
522 : /* Create temporary truncated tuple descriptor */
523 15385 : truncdesc = CreateTupleDescTruncatedCopy(sourceDescriptor, leavenatts);
524 :
525 : /* Deform, form copy of tuple with fewer attributes */
526 15385 : index_deform_tuple(source, truncdesc, values, isnull);
527 15385 : truncated = index_form_tuple(truncdesc, values, isnull);
528 15385 : 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 15385 : pfree(truncdesc);
536 :
537 15385 : return truncated;
538 : }
|