Line data Source code
1 : /*
2 : * brin_tuple.c
3 : * Method implementations for tuples in BRIN indexes.
4 : *
5 : * Intended usage is that code outside this file only deals with
6 : * BrinMemTuples, and convert to and from the on-disk representation through
7 : * functions in this file.
8 : *
9 : * NOTES
10 : *
11 : * A BRIN tuple is similar to a heap tuple, with a few key differences. The
12 : * first interesting difference is that the tuple header is much simpler, only
13 : * containing its total length and a small area for flags. Also, the stored
14 : * data does not match the relation tuple descriptor exactly: for each
15 : * attribute in the descriptor, the index tuple carries an arbitrary number
16 : * of values, depending on the opclass.
17 : *
18 : * Also, for each column of the index relation there are two null bits: one
19 : * (hasnulls) stores whether any tuple within the page range has that column
20 : * set to null; the other one (allnulls) stores whether the column values are
21 : * all null. If allnulls is true, then the tuple data area does not contain
22 : * values for that column at all; whereas it does if the hasnulls is set.
23 : * Note the size of the null bitmask may not be the same as that of the
24 : * datum array.
25 : *
26 : * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
27 : * Portions Copyright (c) 1994, Regents of the University of California
28 : *
29 : * IDENTIFICATION
30 : * src/backend/access/brin/brin_tuple.c
31 : */
32 : #include "postgres.h"
33 :
34 : #include "access/brin_tuple.h"
35 : #include "access/detoast.h"
36 : #include "access/heaptoast.h"
37 : #include "access/htup_details.h"
38 : #include "access/toast_internals.h"
39 : #include "access/tupdesc.h"
40 : #include "access/tupmacs.h"
41 : #include "utils/datum.h"
42 : #include "utils/memutils.h"
43 :
44 :
45 : /*
46 : * This enables de-toasting of index entries. Needed until VACUUM is
47 : * smart enough to rebuild indexes from scratch.
48 : */
49 : #define TOAST_INDEX_HACK
50 :
51 :
52 : static inline void brin_deconstruct_tuple(BrinDesc *brdesc,
53 : char *tp, bits8 *nullbits, bool nulls,
54 : Datum *values, bool *allnulls, bool *hasnulls);
55 :
56 :
57 : /*
58 : * Return a tuple descriptor used for on-disk storage of BRIN tuples.
59 : */
60 : static TupleDesc
61 111358 : brtuple_disk_tupdesc(BrinDesc *brdesc)
62 : {
63 : /* We cache these in the BrinDesc */
64 111358 : if (brdesc->bd_disktdesc == NULL)
65 : {
66 : int i;
67 : int j;
68 1384 : AttrNumber attno = 1;
69 : TupleDesc tupdesc;
70 : MemoryContext oldcxt;
71 :
72 : /* make sure it's in the bdesc's context */
73 1384 : oldcxt = MemoryContextSwitchTo(brdesc->bd_context);
74 :
75 1384 : tupdesc = CreateTemplateTupleDesc(brdesc->bd_totalstored);
76 :
77 32186 : for (i = 0; i < brdesc->bd_tupdesc->natts; i++)
78 : {
79 96478 : for (j = 0; j < brdesc->bd_info[i]->oi_nstored; j++)
80 65676 : TupleDescInitEntry(tupdesc, attno++, NULL,
81 65676 : brdesc->bd_info[i]->oi_typcache[j]->type_id,
82 : -1, 0);
83 : }
84 :
85 1384 : MemoryContextSwitchTo(oldcxt);
86 :
87 1384 : brdesc->bd_disktdesc = tupdesc;
88 : }
89 :
90 111358 : return brdesc->bd_disktdesc;
91 : }
92 :
93 : /*
94 : * Generate a new on-disk tuple to be inserted in a BRIN index.
95 : *
96 : * See brin_form_placeholder_tuple if you touch this.
97 : */
98 : BrinTuple *
99 1574 : brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple,
100 : Size *size)
101 : {
102 : Datum *values;
103 : bool *nulls;
104 1574 : bool anynulls = false;
105 : BrinTuple *rettuple;
106 : int keyno;
107 : int idxattno;
108 1574 : uint16 phony_infomask = 0;
109 : bits8 *phony_nullbitmap;
110 : Size len,
111 : hoff,
112 : data_len;
113 : int i;
114 :
115 : #ifdef TOAST_INDEX_HACK
116 : Datum *untoasted_values;
117 1574 : int nuntoasted = 0;
118 : #endif
119 :
120 : Assert(brdesc->bd_totalstored > 0);
121 :
122 1574 : values = (Datum *) palloc(sizeof(Datum) * brdesc->bd_totalstored);
123 1574 : nulls = (bool *) palloc0(sizeof(bool) * brdesc->bd_totalstored);
124 : phony_nullbitmap = (bits8 *)
125 1574 : palloc(sizeof(bits8) * BITMAPLEN(brdesc->bd_totalstored));
126 :
127 : #ifdef TOAST_INDEX_HACK
128 1574 : untoasted_values = (Datum *) palloc(sizeof(Datum) * brdesc->bd_totalstored);
129 : #endif
130 :
131 : /*
132 : * Set up the values/nulls arrays for heap_fill_tuple
133 : */
134 1574 : idxattno = 0;
135 39986 : for (keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
136 : {
137 : int datumno;
138 :
139 : /*
140 : * "allnulls" is set when there's no nonnull value in any row in the
141 : * column; when this happens, there is no data to store. Thus set the
142 : * nullable bits for all data elements of this column and we're done.
143 : */
144 38412 : if (tuple->bt_columns[keyno].bv_allnulls)
145 : {
146 80 : for (datumno = 0;
147 252 : datumno < brdesc->bd_info[keyno]->oi_nstored;
148 172 : datumno++)
149 172 : nulls[idxattno++] = true;
150 80 : anynulls = true;
151 80 : continue;
152 : }
153 :
154 : /*
155 : * The "hasnulls" bit is set when there are some null values in the
156 : * data. We still need to store a real value, but the presence of
157 : * this means we need a null bitmap.
158 : */
159 38332 : if (tuple->bt_columns[keyno].bv_hasnulls)
160 2300 : anynulls = true;
161 :
162 : /*
163 : * Now obtain the values of each stored datum. Note that some values
164 : * might be toasted, and we cannot rely on the original heap values
165 : * sticking around forever, so we must detoast them. Also try to
166 : * compress them.
167 : */
168 38332 : for (datumno = 0;
169 120080 : datumno < brdesc->bd_info[keyno]->oi_nstored;
170 81748 : datumno++)
171 : {
172 81748 : Datum value = tuple->bt_columns[keyno].bv_values[datumno];
173 :
174 : #ifdef TOAST_INDEX_HACK
175 :
176 : /* We must look at the stored type, not at the index descriptor. */
177 81748 : TypeCacheEntry *atttype = brdesc->bd_info[keyno]->oi_typcache[datumno];
178 :
179 : /* Do we need to free the value at the end? */
180 81748 : bool free_value = false;
181 :
182 : /* For non-varlena types we don't need to do anything special */
183 81748 : if (atttype->typlen != -1)
184 : {
185 57612 : values[idxattno++] = value;
186 57612 : continue;
187 : }
188 :
189 : /*
190 : * Do nothing if value is not of varlena type. We don't need to
191 : * care about NULL values here, thanks to bv_allnulls above.
192 : *
193 : * If value is stored EXTERNAL, must fetch it so we are not
194 : * depending on outside storage.
195 : *
196 : * XXX Is this actually true? Could it be that the summary is
197 : * NULL even for range with non-NULL data? E.g. degenerate bloom
198 : * filter may be thrown away, etc.
199 : */
200 24136 : if (VARATT_IS_EXTERNAL(DatumGetPointer(value)))
201 : {
202 16 : value = PointerGetDatum(detoast_external_attr((struct varlena *)
203 : DatumGetPointer(value)));
204 16 : free_value = true;
205 : }
206 :
207 : /*
208 : * If value is above size target, and is of a compressible datatype,
209 : * try to compress it in-line.
210 : */
211 24136 : if (!VARATT_IS_EXTENDED(DatumGetPointer(value)) &&
212 260 : VARSIZE(DatumGetPointer(value)) > TOAST_INDEX_TARGET &&
213 32 : (atttype->typstorage == TYPSTORAGE_EXTENDED ||
214 0 : atttype->typstorage == TYPSTORAGE_MAIN))
215 : {
216 32 : Datum cvalue = toast_compress_datum(value);
217 :
218 32 : if (DatumGetPointer(cvalue) != NULL)
219 : {
220 : /* successful compression */
221 0 : if (free_value)
222 0 : pfree(DatumGetPointer(value));
223 :
224 0 : value = cvalue;
225 0 : free_value = true;
226 : }
227 : }
228 :
229 : /*
230 : * If we untoasted / compressed the value, we need to free it
231 : * after forming the index tuple.
232 : */
233 24136 : if (free_value)
234 16 : untoasted_values[nuntoasted++] = value;
235 :
236 : #endif
237 :
238 24136 : values[idxattno++] = value;
239 : }
240 : }
241 :
242 : /* Assert we did not overrun temp arrays */
243 : Assert(idxattno <= brdesc->bd_totalstored);
244 :
245 : /* compute total space needed */
246 1574 : len = SizeOfBrinTuple;
247 1574 : if (anynulls)
248 : {
249 : /*
250 : * We need a double-length bitmap on an on-disk BRIN index tuple; the
251 : * first half stores the "allnulls" bits, the second stores
252 : * "hasnulls".
253 : */
254 114 : len += BITMAPLEN(brdesc->bd_tupdesc->natts * 2);
255 : }
256 :
257 1574 : len = hoff = MAXALIGN(len);
258 :
259 1574 : data_len = heap_compute_data_size(brtuple_disk_tupdesc(brdesc),
260 : values, nulls);
261 1574 : len += data_len;
262 :
263 1574 : len = MAXALIGN(len);
264 :
265 1574 : rettuple = palloc0(len);
266 1574 : rettuple->bt_blkno = blkno;
267 1574 : rettuple->bt_info = hoff;
268 :
269 : /* Assert that hoff fits in the space available */
270 : Assert((rettuple->bt_info & BRIN_OFFSET_MASK) == hoff);
271 :
272 : /*
273 : * The infomask and null bitmap as computed by heap_fill_tuple are useless
274 : * to us. However, that function will not accept a null infomask; and we
275 : * need to pass a valid null bitmap so that it will correctly skip
276 : * outputting null attributes in the data area.
277 : */
278 1574 : heap_fill_tuple(brtuple_disk_tupdesc(brdesc),
279 : values,
280 : nulls,
281 : (char *) rettuple + hoff,
282 : data_len,
283 : &phony_infomask,
284 : phony_nullbitmap);
285 :
286 : /* done with these */
287 1574 : pfree(values);
288 1574 : pfree(nulls);
289 1574 : pfree(phony_nullbitmap);
290 :
291 : #ifdef TOAST_INDEX_HACK
292 1590 : for (i = 0; i < nuntoasted; i++)
293 16 : pfree(DatumGetPointer(untoasted_values[i]));
294 : #endif
295 :
296 : /*
297 : * Now fill in the real null bitmasks. allnulls first.
298 : */
299 1574 : if (anynulls)
300 : {
301 : bits8 *bitP;
302 : int bitmask;
303 :
304 114 : rettuple->bt_info |= BRIN_NULLS_MASK;
305 :
306 : /*
307 : * Note that we reverse the sense of null bits in this module: we
308 : * store a 1 for a null attribute rather than a 0. So we must reverse
309 : * the sense of the att_isnull test in brin_deconstruct_tuple as well.
310 : */
311 114 : bitP = ((bits8 *) ((char *) rettuple + SizeOfBrinTuple)) - 1;
312 114 : bitmask = HIGHBIT;
313 2954 : for (keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
314 : {
315 2840 : if (bitmask != HIGHBIT)
316 2444 : bitmask <<= 1;
317 : else
318 : {
319 396 : bitP += 1;
320 396 : *bitP = 0x0;
321 396 : bitmask = 1;
322 : }
323 :
324 2840 : if (!tuple->bt_columns[keyno].bv_allnulls)
325 2760 : continue;
326 :
327 80 : *bitP |= bitmask;
328 : }
329 : /* hasnulls bits follow */
330 2954 : for (keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
331 : {
332 2840 : if (bitmask != HIGHBIT)
333 2464 : bitmask <<= 1;
334 : else
335 : {
336 376 : bitP += 1;
337 376 : *bitP = 0x0;
338 376 : bitmask = 1;
339 : }
340 :
341 2840 : if (!tuple->bt_columns[keyno].bv_hasnulls)
342 540 : continue;
343 :
344 2300 : *bitP |= bitmask;
345 : }
346 : }
347 :
348 1574 : if (tuple->bt_placeholder)
349 0 : rettuple->bt_info |= BRIN_PLACEHOLDER_MASK;
350 :
351 1574 : *size = len;
352 1574 : return rettuple;
353 : }
354 :
355 : /*
356 : * Generate a new on-disk tuple with no data values, marked as placeholder.
357 : *
358 : * This is a cut-down version of brin_form_tuple.
359 : */
360 : BrinTuple *
361 42 : brin_form_placeholder_tuple(BrinDesc *brdesc, BlockNumber blkno, Size *size)
362 : {
363 : Size len;
364 : Size hoff;
365 : BrinTuple *rettuple;
366 : int keyno;
367 : bits8 *bitP;
368 : int bitmask;
369 :
370 : /* compute total space needed: always add nulls */
371 42 : len = SizeOfBrinTuple;
372 42 : len += BITMAPLEN(brdesc->bd_tupdesc->natts * 2);
373 42 : len = hoff = MAXALIGN(len);
374 :
375 42 : rettuple = palloc0(len);
376 42 : rettuple->bt_blkno = blkno;
377 42 : rettuple->bt_info = hoff;
378 42 : rettuple->bt_info |= BRIN_NULLS_MASK | BRIN_PLACEHOLDER_MASK;
379 :
380 42 : bitP = ((bits8 *) ((char *) rettuple + SizeOfBrinTuple)) - 1;
381 42 : bitmask = HIGHBIT;
382 : /* set allnulls true for all attributes */
383 896 : for (keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
384 : {
385 854 : if (bitmask != HIGHBIT)
386 728 : bitmask <<= 1;
387 : else
388 : {
389 126 : bitP += 1;
390 126 : *bitP = 0x0;
391 126 : bitmask = 1;
392 : }
393 :
394 854 : *bitP |= bitmask;
395 : }
396 : /* no need to set hasnulls */
397 :
398 42 : *size = len;
399 42 : return rettuple;
400 : }
401 :
402 : /*
403 : * Free a tuple created by brin_form_tuple
404 : */
405 : void
406 84 : brin_free_tuple(BrinTuple *tuple)
407 : {
408 84 : pfree(tuple);
409 84 : }
410 :
411 : /*
412 : * Given a brin tuple of size len, create a copy of it. If 'dest' is not
413 : * NULL, its size is destsz, and can be used as output buffer; if the tuple
414 : * to be copied does not fit, it is enlarged by repalloc, and the size is
415 : * updated to match. This avoids palloc/free cycles when many brin tuples
416 : * are being processed in loops.
417 : */
418 : BrinTuple *
419 100112 : brin_copy_tuple(BrinTuple *tuple, Size len, BrinTuple *dest, Size *destsz)
420 : {
421 100112 : if (!destsz || *destsz == 0)
422 100112 : dest = palloc(len);
423 0 : else if (len > *destsz)
424 : {
425 0 : dest = repalloc(dest, len);
426 0 : *destsz = len;
427 : }
428 :
429 100112 : memcpy(dest, tuple, len);
430 :
431 100112 : return dest;
432 : }
433 :
434 : /*
435 : * Return whether two BrinTuples are bitwise identical.
436 : */
437 : bool
438 950 : brin_tuples_equal(const BrinTuple *a, Size alen, const BrinTuple *b, Size blen)
439 : {
440 950 : if (alen != blen)
441 0 : return false;
442 950 : if (memcmp(a, b, alen) != 0)
443 0 : return false;
444 950 : return true;
445 : }
446 :
447 : /*
448 : * Create a new BrinMemTuple from scratch, and initialize it to an empty
449 : * state.
450 : *
451 : * Note: we don't provide any means to free a deformed tuple, so make sure to
452 : * use a temporary memory context.
453 : */
454 : BrinMemTuple *
455 10070 : brin_new_memtuple(BrinDesc *brdesc)
456 : {
457 : BrinMemTuple *dtup;
458 : long basesize;
459 :
460 10070 : basesize = MAXALIGN(sizeof(BrinMemTuple) +
461 : sizeof(BrinValues) * brdesc->bd_tupdesc->natts);
462 10070 : dtup = palloc0(basesize + sizeof(Datum) * brdesc->bd_totalstored);
463 :
464 10070 : dtup->bt_values = palloc(sizeof(Datum) * brdesc->bd_totalstored);
465 10070 : dtup->bt_allnulls = palloc(sizeof(bool) * brdesc->bd_tupdesc->natts);
466 10070 : dtup->bt_hasnulls = palloc(sizeof(bool) * brdesc->bd_tupdesc->natts);
467 :
468 10070 : dtup->bt_context = AllocSetContextCreate(CurrentMemoryContext,
469 : "brin dtuple",
470 : ALLOCSET_DEFAULT_SIZES);
471 :
472 10070 : brin_memtuple_initialize(dtup, brdesc);
473 :
474 10070 : return dtup;
475 : }
476 :
477 : /*
478 : * Reset a BrinMemTuple to initial state. We return the same tuple, for
479 : * notational convenience.
480 : */
481 : BrinMemTuple *
482 109962 : brin_memtuple_initialize(BrinMemTuple *dtuple, BrinDesc *brdesc)
483 : {
484 : int i;
485 : char *currdatum;
486 :
487 109962 : MemoryContextReset(dtuple->bt_context);
488 :
489 109962 : currdatum = (char *) dtuple +
490 109962 : MAXALIGN(sizeof(BrinMemTuple) +
491 : sizeof(BrinValues) * brdesc->bd_tupdesc->natts);
492 3162980 : for (i = 0; i < brdesc->bd_tupdesc->natts; i++)
493 : {
494 3053018 : dtuple->bt_columns[i].bv_attno = i + 1;
495 3053018 : dtuple->bt_columns[i].bv_allnulls = true;
496 3053018 : dtuple->bt_columns[i].bv_hasnulls = false;
497 3053018 : dtuple->bt_columns[i].bv_values = (Datum *) currdatum;
498 3053018 : currdatum += sizeof(Datum) * brdesc->bd_info[i]->oi_nstored;
499 : }
500 :
501 109962 : return dtuple;
502 : }
503 :
504 : /*
505 : * Convert a BrinTuple back to a BrinMemTuple. This is the reverse of
506 : * brin_form_tuple.
507 : *
508 : * As an optimization, the caller can pass a previously allocated 'dMemtuple'.
509 : * This avoids having to allocate it here, which can be useful when this
510 : * function is called many times in a loop. It is caller's responsibility
511 : * that the given BrinMemTuple matches what we need here.
512 : *
513 : * Note we don't need the "on disk tupdesc" here; we rely on our own routine to
514 : * deconstruct the tuple from the on-disk format.
515 : */
516 : BrinMemTuple *
517 108210 : brin_deform_tuple(BrinDesc *brdesc, BrinTuple *tuple, BrinMemTuple *dMemtuple)
518 : {
519 : BrinMemTuple *dtup;
520 : Datum *values;
521 : bool *allnulls;
522 : bool *hasnulls;
523 : char *tp;
524 : bits8 *nullbits;
525 : int keyno;
526 : int valueno;
527 : MemoryContext oldcxt;
528 :
529 108210 : dtup = dMemtuple ? brin_memtuple_initialize(dMemtuple, brdesc) :
530 9006 : brin_new_memtuple(brdesc);
531 :
532 108210 : if (BrinTupleIsPlaceholder(tuple))
533 0 : dtup->bt_placeholder = true;
534 108210 : dtup->bt_blkno = tuple->bt_blkno;
535 :
536 108210 : values = dtup->bt_values;
537 108210 : allnulls = dtup->bt_allnulls;
538 108210 : hasnulls = dtup->bt_hasnulls;
539 :
540 108210 : tp = (char *) tuple + BrinTupleDataOffset(tuple);
541 :
542 108210 : if (BrinTupleHasNulls(tuple))
543 7016 : nullbits = (bits8 *) ((char *) tuple + SizeOfBrinTuple);
544 : else
545 101194 : nullbits = NULL;
546 108210 : brin_deconstruct_tuple(brdesc,
547 108210 : tp, nullbits, BrinTupleHasNulls(tuple),
548 : values, allnulls, hasnulls);
549 :
550 : /*
551 : * Iterate to assign each of the values to the corresponding item in the
552 : * values array of each column. The copies occur in the tuple's context.
553 : */
554 108210 : oldcxt = MemoryContextSwitchTo(dtup->bt_context);
555 3117588 : for (valueno = 0, keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
556 : {
557 : int i;
558 :
559 3009378 : if (allnulls[keyno])
560 : {
561 16 : valueno += brdesc->bd_info[keyno]->oi_nstored;
562 16 : continue;
563 : }
564 :
565 : /*
566 : * We would like to skip datumCopy'ing the values datum in some cases,
567 : * caller permitting ...
568 : */
569 9428254 : for (i = 0; i < brdesc->bd_info[keyno]->oi_nstored; i++)
570 6418892 : dtup->bt_columns[keyno].bv_values[i] =
571 19256676 : datumCopy(values[valueno++],
572 6418892 : brdesc->bd_info[keyno]->oi_typcache[i]->typbyval,
573 6418892 : brdesc->bd_info[keyno]->oi_typcache[i]->typlen);
574 :
575 3009362 : dtup->bt_columns[keyno].bv_hasnulls = hasnulls[keyno];
576 3009362 : dtup->bt_columns[keyno].bv_allnulls = false;
577 : }
578 :
579 108210 : MemoryContextSwitchTo(oldcxt);
580 :
581 108210 : return dtup;
582 : }
583 :
584 : /*
585 : * brin_deconstruct_tuple
586 : * Guts of attribute extraction from an on-disk BRIN tuple.
587 : *
588 : * Its arguments are:
589 : * brdesc BRIN descriptor for the stored tuple
590 : * tp pointer to the tuple data area
591 : * nullbits pointer to the tuple nulls bitmask
592 : * nulls "has nulls" bit in tuple infomask
593 : * values output values, array of size brdesc->bd_totalstored
594 : * allnulls output "allnulls", size brdesc->bd_tupdesc->natts
595 : * hasnulls output "hasnulls", size brdesc->bd_tupdesc->natts
596 : *
597 : * Output arrays must have been allocated by caller.
598 : */
599 : static inline void
600 108210 : brin_deconstruct_tuple(BrinDesc *brdesc,
601 : char *tp, bits8 *nullbits, bool nulls,
602 : Datum *values, bool *allnulls, bool *hasnulls)
603 : {
604 : int attnum;
605 : int stored;
606 : TupleDesc diskdsc;
607 : long off;
608 :
609 : /*
610 : * First iterate to natts to obtain both null flags for each attribute.
611 : * Note that we reverse the sense of the att_isnull test, because we store
612 : * 1 for a null value (rather than a 1 for a not null value as is the
613 : * att_isnull convention used elsewhere.) See brin_form_tuple.
614 : */
615 3117588 : for (attnum = 0; attnum < brdesc->bd_tupdesc->natts; attnum++)
616 : {
617 : /*
618 : * the "all nulls" bit means that all values in the page range for
619 : * this column are nulls. Therefore there are no values in the tuple
620 : * data area.
621 : */
622 3009378 : allnulls[attnum] = nulls && !att_isnull(attnum, nullbits);
623 :
624 : /*
625 : * the "has nulls" bit means that some tuples have nulls, but others
626 : * have not-null values. Therefore we know the tuple contains data
627 : * for this column.
628 : *
629 : * The hasnulls bits follow the allnulls bits in the same bitmask.
630 : */
631 6018756 : hasnulls[attnum] =
632 3009378 : nulls && !att_isnull(brdesc->bd_tupdesc->natts + attnum, nullbits);
633 : }
634 :
635 : /*
636 : * Iterate to obtain each attribute's stored values. Note that since we
637 : * may reuse attribute entries for more than one column, we cannot cache
638 : * offsets here.
639 : */
640 108210 : diskdsc = brtuple_disk_tupdesc(brdesc);
641 108210 : stored = 0;
642 108210 : off = 0;
643 3117588 : for (attnum = 0; attnum < brdesc->bd_tupdesc->natts; attnum++)
644 : {
645 : int datumno;
646 :
647 3009378 : if (allnulls[attnum])
648 : {
649 16 : stored += brdesc->bd_info[attnum]->oi_nstored;
650 16 : continue;
651 : }
652 :
653 3009362 : for (datumno = 0;
654 9428254 : datumno < brdesc->bd_info[attnum]->oi_nstored;
655 6418892 : datumno++)
656 : {
657 6418892 : Form_pg_attribute thisatt = TupleDescAttr(diskdsc, stored);
658 :
659 6418892 : if (thisatt->attlen == -1)
660 : {
661 1900800 : off = att_align_pointer(off, thisatt->attalign, -1,
662 : tp + off);
663 : }
664 : else
665 : {
666 : /* not varlena, so safe to use att_align_nominal */
667 4518092 : off = att_align_nominal(off, thisatt->attalign);
668 : }
669 :
670 6418892 : values[stored++] = fetchatt(thisatt, tp + off);
671 :
672 6418892 : off = att_addlength_pointer(off, thisatt->attlen, tp + off);
673 : }
674 : }
675 108210 : }
|