Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * heaptuple.c
4 : : * This file contains heap tuple accessor and mutator routines, as well
5 : : * as various tuple utilities.
6 : : *
7 : : * Some notes about varlenas and this code:
8 : : *
9 : : * Before Postgres 8.3 varlenas always had a 4-byte length header, and
10 : : * therefore always needed 4-byte alignment (at least). This wasted space
11 : : * for short varlenas, for example CHAR(1) took 5 bytes and could need up to
12 : : * 3 additional padding bytes for alignment.
13 : : *
14 : : * Now, a short varlena (up to 126 data bytes) is reduced to a 1-byte header
15 : : * and we don't align it. To hide this from datatype-specific functions that
16 : : * don't want to deal with it, such a datum is considered "toasted" and will
17 : : * be expanded back to the normal 4-byte-header format by pg_detoast_datum.
18 : : * (In performance-critical code paths we can use pg_detoast_datum_packed
19 : : * and the appropriate access macros to avoid that overhead.) Note that this
20 : : * conversion is performed directly in heap_form_tuple, without invoking
21 : : * heaptoast.c.
22 : : *
23 : : * This change will break any code that assumes it needn't detoast values
24 : : * that have been put into a tuple but never sent to disk. Hopefully there
25 : : * are few such places.
26 : : *
27 : : * Varlenas still have alignment INT (or DOUBLE) in pg_type/pg_attribute, since
28 : : * that's the normal requirement for the untoasted format. But we ignore that
29 : : * for the 1-byte-header format. This means that the actual start position
30 : : * of a varlena datum may vary depending on which format it has. To determine
31 : : * what is stored, we have to require that alignment padding bytes be zero.
32 : : * (Postgres actually has always zeroed them, but now it's required!) Since
33 : : * the first byte of a 1-byte-header varlena can never be zero, we can examine
34 : : * the first byte after the previous datum to tell if it's a pad byte or the
35 : : * start of a 1-byte-header varlena.
36 : : *
37 : : * Note that while formerly we could rely on the first varlena column of a
38 : : * system catalog to be at the offset suggested by the C struct for the
39 : : * catalog, this is now risky: it's only safe if the preceding field is
40 : : * word-aligned, so that there will never be any padding.
41 : : *
42 : : * We don't pack varlenas whose attstorage is PLAIN, since the data type
43 : : * isn't expecting to have to detoast values. This is used in particular
44 : : * by oidvector and int2vector, which are used in the system catalogs
45 : : * and we'd like to still refer to them via C struct offsets.
46 : : *
47 : : *
48 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
49 : : * Portions Copyright (c) 1994, Regents of the University of California
50 : : *
51 : : *
52 : : * IDENTIFICATION
53 : : * src/backend/access/common/heaptuple.c
54 : : *
55 : : *-------------------------------------------------------------------------
56 : : */
57 : :
58 : : #include "postgres.h"
59 : :
60 : : #include "access/heaptoast.h"
61 : : #include "access/sysattr.h"
62 : : #include "access/tupdesc_details.h"
63 : : #include "common/hashfn.h"
64 : : #include "utils/datum.h"
65 : : #include "utils/expandeddatum.h"
66 : : #include "utils/hsearch.h"
67 : : #include "utils/memutils.h"
68 : :
69 : :
70 : : /*
71 : : * Does att's datatype allow packing into the 1-byte-header varlena format?
72 : : * While functions that use TupleDescAttr() and assign attstorage =
73 : : * TYPSTORAGE_PLAIN cannot use packed varlena headers, functions that call
74 : : * TupleDescInitEntry() use typeForm->typstorage (TYPSTORAGE_EXTENDED) and
75 : : * can use packed varlena headers, e.g.:
76 : : * CREATE TABLE test(a VARCHAR(10000) STORAGE PLAIN);
77 : : * INSERT INTO test VALUES (repeat('A',10));
78 : : * This can be verified with pageinspect.
79 : : */
80 : : #define ATT_IS_PACKABLE(att) \
81 : : ((att)->attlen == -1 && (att)->attstorage != TYPSTORAGE_PLAIN)
82 : : /* Use this if it's already known varlena */
83 : : #define VARLENA_ATT_IS_PACKABLE(att) \
84 : : ((att)->attstorage != TYPSTORAGE_PLAIN)
85 : :
86 : : /*
87 : : * Setup for caching pass-by-ref missing attributes in a way that survives
88 : : * tupleDesc destruction.
89 : : */
90 : :
91 : : typedef struct
92 : : {
93 : : int len;
94 : : Datum value;
95 : : } missing_cache_key;
96 : :
97 : : static HTAB *missing_cache = NULL;
98 : :
99 : : static uint32
236 andrew@dunslane.net 100 :UBC 0 : missing_hash(const void *key, Size keysize)
101 : : {
102 : 0 : const missing_cache_key *entry = (missing_cache_key *) key;
103 : :
104 : 0 : return hash_bytes((const unsigned char *) entry->value, entry->len);
105 : : }
106 : :
107 : : static int
108 : 0 : missing_match(const void *key1, const void *key2, Size keysize)
109 : : {
110 : 0 : const missing_cache_key *entry1 = (missing_cache_key *) key1;
111 : 0 : const missing_cache_key *entry2 = (missing_cache_key *) key2;
112 : :
113 [ # # ]: 0 : if (entry1->len != entry2->len)
114 [ # # ]: 0 : return entry1->len > entry2->len ? 1 : -1;
115 : :
116 : 0 : return memcmp(DatumGetPointer(entry1->value),
117 : 0 : DatumGetPointer(entry2->value),
118 : 0 : entry1->len);
119 : : }
120 : :
121 : : static void
122 : 0 : init_missing_cache()
123 : : {
124 : : HASHCTL hash_ctl;
125 : :
126 : 0 : hash_ctl.keysize = sizeof(missing_cache_key);
127 : 0 : hash_ctl.entrysize = sizeof(missing_cache_key);
128 : 0 : hash_ctl.hcxt = TopMemoryContext;
129 : 0 : hash_ctl.hash = missing_hash;
130 : 0 : hash_ctl.match = missing_match;
131 : 0 : missing_cache =
132 : 0 : hash_create("Missing Values Cache",
133 : : 32,
134 : : &hash_ctl,
135 : : HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE);
136 : 0 : }
137 : :
138 : : /* ----------------------------------------------------------------
139 : : * misc support routines
140 : : * ----------------------------------------------------------------
141 : : */
142 : :
143 : : /*
144 : : * Return the missing value of an attribute, or NULL if there isn't one.
145 : : */
146 : : Datum
2209 andrew@dunslane.net 147 :CBC 178 : getmissingattr(TupleDesc tupleDesc,
148 : : int attnum, bool *isnull)
149 : : {
150 : : Form_pg_attribute att;
151 : :
152 [ - + ]: 178 : Assert(attnum <= tupleDesc->natts);
153 [ - + ]: 178 : Assert(attnum > 0);
154 : :
155 : 178 : att = TupleDescAttr(tupleDesc, attnum - 1);
156 : :
157 [ + + ]: 178 : if (att->atthasmissing)
158 : : {
159 : : AttrMissing *attrmiss;
160 : :
161 [ - + ]: 27 : Assert(tupleDesc->constr);
162 [ - + ]: 27 : Assert(tupleDesc->constr->missing);
163 : :
164 : 27 : attrmiss = tupleDesc->constr->missing + (attnum - 1);
165 : :
2118 akapila@postgresql.o 166 [ + - ]: 27 : if (attrmiss->am_present)
167 : : {
168 : : missing_cache_key key;
169 : : missing_cache_key *entry;
170 : : bool found;
171 : : MemoryContext oldctx;
172 : :
2209 andrew@dunslane.net 173 : 27 : *isnull = false;
174 : :
175 : : /* no need to cache by-value attributes */
236 176 [ + - ]: 27 : if (att->attbyval)
177 : 27 : return attrmiss->am_value;
178 : :
179 : : /* set up cache if required */
236 andrew@dunslane.net 180 [ # # ]:UBC 0 : if (missing_cache == NULL)
181 : 0 : init_missing_cache();
182 : :
183 : : /* check if there's a cache entry */
184 [ # # # # ]: 0 : Assert(att->attlen > 0 || att->attlen == -1);
185 [ # # ]: 0 : if (att->attlen > 0)
186 : 0 : key.len = att->attlen;
187 : : else
188 [ # # # # : 0 : key.len = VARSIZE_ANY(attrmiss->am_value);
# # # # #
# ]
189 : 0 : key.value = attrmiss->am_value;
190 : :
191 : 0 : entry = hash_search(missing_cache, &key, HASH_ENTER, &found);
192 : :
193 [ # # ]: 0 : if (!found)
194 : : {
195 : : /* cache miss, so we need a non-transient copy of the datum */
196 : 0 : oldctx = MemoryContextSwitchTo(TopMemoryContext);
197 : 0 : entry->value =
198 : 0 : datumCopy(attrmiss->am_value, false, att->attlen);
199 : 0 : MemoryContextSwitchTo(oldctx);
200 : : }
201 : :
202 : 0 : return entry->value;
203 : : }
204 : : }
205 : :
2209 andrew@dunslane.net 206 :CBC 151 : *isnull = true;
207 : 151 : return PointerGetDatum(NULL);
208 : : }
209 : :
210 : : /*
211 : : * heap_compute_data_size
212 : : * Determine size of the data area of a tuple to be constructed
213 : : */
214 : : Size
6969 tgl@sss.pgh.pa.us 215 : 56009921 : heap_compute_data_size(TupleDesc tupleDesc,
216 : : const Datum *values,
217 : : const bool *isnull)
218 : : {
219 : 56009921 : Size data_length = 0;
220 : : int i;
221 : 56009921 : int numberOfAttributes = tupleDesc->natts;
222 : :
223 [ + + ]: 190293485 : for (i = 0; i < numberOfAttributes; i++)
224 : : {
225 : : Datum val;
226 : : Form_pg_attribute atti;
227 : :
228 [ + + ]: 134283564 : if (isnull[i])
229 : 12083704 : continue;
230 : :
6218 231 : 122199860 : val = values[i];
2429 andres@anarazel.de 232 : 122199860 : atti = TupleDescAttr(tupleDesc, i);
233 : :
3258 tgl@sss.pgh.pa.us 234 [ + + + + ]: 122199860 : if (ATT_IS_PACKABLE(atti) &&
6218 235 [ + + + + ]: 17374847 : VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
236 : : {
237 : : /*
238 : : * we're anticipating converting to a short varlena header, so
239 : : * adjust length and don't count any alignment
240 : : */
241 : 13568176 : data_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(val));
242 : : }
3258 243 [ + + ]: 108631684 : else if (atti->attlen == -1 &&
244 [ + + + + ]: 4683590 : VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val)))
245 : : {
246 : : /*
247 : : * we want to flatten the expanded value so that the constructed
248 : : * tuple doesn't depend on it
249 : : */
250 [ + + + - : 1482 : data_length = att_align_nominal(data_length, atti->attalign);
+ - - - ]
251 : 1482 : data_length += EOH_get_flat_size(DatumGetEOHP(val));
252 : : }
253 : : else
254 : : {
255 [ + + + + : 108630202 : data_length = att_align_datum(data_length, atti->attalign,
+ + + + +
+ - + ]
256 : : atti->attlen, val);
257 [ + + + + : 108630202 : data_length = att_addlength_datum(data_length, atti->attlen,
+ + + + +
- - + + +
- + ]
258 : : val);
259 : : }
260 : : }
261 : :
6969 262 : 56009921 : return data_length;
263 : : }
264 : :
265 : : /*
266 : : * Per-attribute helper for heap_fill_tuple and other routines building tuples.
267 : : *
268 : : * Fill in either a data value or a bit in the null bitmask
269 : : */
270 : : static inline void
2209 andrew@dunslane.net 271 : 123602594 : fill_val(Form_pg_attribute att,
272 : : bits8 **bit,
273 : : int *bitmask,
274 : : char **dataP,
275 : : uint16 *infomask,
276 : : Datum datum,
277 : : bool isnull)
278 : : {
279 : : Size data_length;
280 : 123602594 : char *data = *dataP;
281 : :
282 : : /*
283 : : * If we're building a null bitmap, set the appropriate bit for the
284 : : * current column value here.
285 : : */
286 [ + + ]: 123602594 : if (bit != NULL)
287 : : {
288 [ + + ]: 43909327 : if (*bitmask != HIGHBIT)
289 : 36261843 : *bitmask <<= 1;
290 : : else
291 : : {
292 : 7647484 : *bit += 1;
293 : 7647484 : **bit = 0x0;
294 : 7647484 : *bitmask = 1;
295 : : }
296 : :
297 [ + + ]: 43909327 : if (isnull)
298 : : {
299 : 11995209 : *infomask |= HEAP_HASNULL;
300 : 11995209 : return;
301 : : }
302 : :
303 : 31914118 : **bit |= *bitmask;
304 : : }
305 : :
306 : : /*
307 : : * XXX we use the att_align macros on the pointer value itself, not on an
308 : : * offset. This is a bit of a hack.
309 : : */
310 [ + + ]: 111607385 : if (att->attbyval)
311 : : {
312 : : /* pass-by-value */
313 [ + + + + : 81843787 : data = (char *) att_align_nominal(data, att->attalign);
+ + - + ]
314 : 81843787 : store_att_byval(data, datum, att->attlen);
315 : 81843787 : data_length = att->attlen;
316 : : }
317 [ + + ]: 29763598 : else if (att->attlen == -1)
318 : : {
319 : : /* varlena */
320 : 17502319 : Pointer val = DatumGetPointer(datum);
321 : :
322 : 17502319 : *infomask |= HEAP_HASVARWIDTH;
323 [ + + ]: 17502319 : if (VARATT_IS_EXTERNAL(val))
324 : : {
325 [ + - + + ]: 9182 : if (VARATT_IS_EXTERNAL_EXPANDED(val))
326 : 1482 : {
327 : : /*
328 : : * we want to flatten the expanded value so that the
329 : : * constructed tuple doesn't depend on it
330 : : */
331 : 1482 : ExpandedObjectHeader *eoh = DatumGetEOHP(datum);
332 : :
333 [ + + + - : 1482 : data = (char *) att_align_nominal(data,
+ - - - ]
334 : : att->attalign);
335 : 1482 : data_length = EOH_get_flat_size(eoh);
336 : 1482 : EOH_flatten_into(eoh, data, data_length);
337 : : }
338 : : else
339 : : {
340 : 7700 : *infomask |= HEAP_HASEXTERNAL;
341 : : /* no alignment, since it's short by definition */
342 [ + + + - : 7700 : data_length = VARSIZE_EXTERNAL(val);
- + ]
343 : 7700 : memcpy(data, val, data_length);
344 : : }
345 : : }
346 [ + + ]: 17493137 : else if (VARATT_IS_SHORT(val))
347 : : {
348 : : /* no alignment for short varlenas */
349 : 3072405 : data_length = VARSIZE_SHORT(val);
350 : 3072405 : memcpy(data, val, data_length);
351 : : }
352 [ + + ]: 14420732 : else if (VARLENA_ATT_IS_PACKABLE(att) &&
353 [ + + + + ]: 13547182 : VARATT_CAN_MAKE_SHORT(val))
354 : : {
355 : : /* convert to short varlena -- no alignment */
356 : 13103630 : data_length = VARATT_CONVERTED_SHORT_SIZE(val);
357 : 13103630 : SET_VARSIZE_SHORT(data, data_length);
358 : 13103630 : memcpy(data + 1, VARDATA(val), data_length - 1);
359 : : }
360 : : else
361 : : {
362 : : /* full 4-byte header varlena */
363 [ + + + - : 1317102 : data = (char *) att_align_nominal(data,
+ - - - ]
364 : : att->attalign);
365 : 1317102 : data_length = VARSIZE(val);
366 : 1317102 : memcpy(data, val, data_length);
367 : : }
368 : : }
369 [ + + ]: 12261279 : else if (att->attlen == -2)
370 : : {
371 : : /* cstring ... never needs alignment */
372 : 1103035 : *infomask |= HEAP_HASVARWIDTH;
1502 tgl@sss.pgh.pa.us 373 [ - + ]: 1103035 : Assert(att->attalign == TYPALIGN_CHAR);
2209 andrew@dunslane.net 374 : 1103035 : data_length = strlen(DatumGetCString(datum)) + 1;
375 : 1103035 : memcpy(data, DatumGetPointer(datum), data_length);
376 : : }
377 : : else
378 : : {
379 : : /* fixed-length pass-by-reference */
380 [ + + + + : 11158244 : data = (char *) att_align_nominal(data, att->attalign);
+ + - + ]
381 [ - + ]: 11158244 : Assert(att->attlen > 0);
382 : 11158244 : data_length = att->attlen;
383 : 11158244 : memcpy(data, DatumGetPointer(datum), data_length);
384 : : }
385 : :
386 : 111607385 : data += data_length;
387 : 111607385 : *dataP = data;
388 : : }
389 : :
390 : : /*
391 : : * heap_fill_tuple
392 : : * Load data portion of a tuple from values/isnull arrays
393 : : *
394 : : * We also fill the null bitmap (if any) and set the infomask bits
395 : : * that reflect the tuple's data contents.
396 : : *
397 : : * NOTE: it is now REQUIRED that the caller have pre-zeroed the data area.
398 : : */
399 : : void
6969 tgl@sss.pgh.pa.us 400 : 46190547 : heap_fill_tuple(TupleDesc tupleDesc,
401 : : const Datum *values, const bool *isnull,
402 : : char *data, Size data_size,
403 : : uint16 *infomask, bits8 *bit)
404 : : {
405 : : bits8 *bitP;
406 : : int bitmask;
407 : : int i;
408 : 46190547 : int numberOfAttributes = tupleDesc->natts;
409 : :
410 : : #ifdef USE_ASSERT_CHECKING
6218 411 : 46190547 : char *start = data;
412 : : #endif
413 : :
6969 414 [ + + ]: 46190547 : if (bit != NULL)
415 : : {
416 : 3377359 : bitP = &bit[-1];
6685 bruce@momjian.us 417 : 3377359 : bitmask = HIGHBIT;
418 : : }
419 : : else
420 : : {
421 : : /* just to keep compiler quiet */
6969 tgl@sss.pgh.pa.us 422 : 42813188 : bitP = NULL;
423 : 42813188 : bitmask = 0;
424 : : }
425 : :
6218 426 : 46190547 : *infomask &= ~(HEAP_HASNULL | HEAP_HASVARWIDTH | HEAP_HASEXTERNAL);
427 : :
6969 428 [ + + ]: 169793141 : for (i = 0; i < numberOfAttributes; i++)
429 : : {
2209 andrew@dunslane.net 430 : 123602594 : Form_pg_attribute attr = TupleDescAttr(tupleDesc, i);
431 : :
432 [ + + + + ]: 247205188 : fill_val(attr,
433 [ + + ]: 123602594 : bitP ? &bitP : NULL,
434 : : &bitmask,
435 : : &data,
436 : : infomask,
437 : 123602594 : values ? values[i] : PointerGetDatum(NULL),
438 [ + - + + ]: 123602594 : isnull ? isnull[i] : true);
439 : : }
440 : :
6218 tgl@sss.pgh.pa.us 441 [ - + ]: 46190547 : Assert((data - start) == data_size);
6969 442 : 46190547 : }
443 : :
444 : :
445 : : /* ----------------------------------------------------------------
446 : : * heap tuple interface
447 : : * ----------------------------------------------------------------
448 : : */
449 : :
450 : : /* ----------------
451 : : * heap_attisnull - returns true iff tuple attribute is not present
452 : : * ----------------
453 : : */
454 : : bool
2209 andrew@dunslane.net 455 : 4409395 : heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc)
456 : : {
457 : : /*
458 : : * We allow a NULL tupledesc for relations not expected to have missing
459 : : * values, such as catalog relations and indexes.
460 : : */
461 [ + + - + ]: 4409395 : Assert(!tupleDesc || attnum <= tupleDesc->natts);
6305 bruce@momjian.us 462 [ - + ]: 4409395 : if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data))
463 : : {
2209 andrew@dunslane.net 464 [ # # # # ]:UBC 0 : if (tupleDesc && TupleDescAttr(tupleDesc, attnum - 1)->atthasmissing)
465 : 0 : return false;
466 : : else
467 : 0 : return true;
468 : : }
469 : :
9716 bruce@momjian.us 470 [ + - ]:CBC 4409395 : if (attnum > 0)
471 : : {
7254 tgl@sss.pgh.pa.us 472 [ + + ]: 4409395 : if (HeapTupleNoNulls(tup))
6969 473 : 1468 : return false;
9270 vadim4o@yahoo.com 474 : 4407927 : return att_isnull(attnum - 1, tup->t_data->t_bits);
475 : : }
476 : :
7254 tgl@sss.pgh.pa.us 477 [ # # ]:UBC 0 : switch (attnum)
478 : : {
479 : 0 : case TableOidAttributeNumber:
480 : : case SelfItemPointerAttributeNumber:
481 : : case MinTransactionIdAttributeNumber:
482 : : case MinCommandIdAttributeNumber:
483 : : case MaxTransactionIdAttributeNumber:
484 : : case MaxCommandIdAttributeNumber:
485 : : /* these are never null */
486 : 0 : break;
487 : :
488 : 0 : default:
489 [ # # ]: 0 : elog(ERROR, "invalid attnum: %d", attnum);
490 : : }
491 : :
6969 492 : 0 : return false;
493 : : }
494 : :
495 : : /* ----------------
496 : : * nocachegetattr
497 : : *
498 : : * This only gets called from fastgetattr() macro, in cases where
499 : : * we can't use a cacheoffset and the value is not null.
500 : : *
501 : : * This caches attribute offsets in the attribute descriptor.
502 : : *
503 : : * An alternative way to speed things up would be to cache offsets
504 : : * with the tuple, but that seems more difficult unless you take
505 : : * the storage hit of actually putting those offsets into the
506 : : * tuple you send to disk. Yuck.
507 : : *
508 : : * This scheme will be slightly slower than that, but should
509 : : * perform well for queries which hit large #'s of tuples. After
510 : : * you cache the offsets once, examining all the other tuples using
511 : : * the same attribute descriptor will go much quicker. -cim 5/4/91
512 : : *
513 : : * NOTE: if you need to change this code, see also heap_deform_tuple.
514 : : * Also see nocache_index_getattr, which is the same code for index
515 : : * tuples.
516 : : * ----------------
517 : : */
518 : : Datum
573 pg@bowt.ie 519 :CBC 84268145 : nocachegetattr(HeapTuple tup,
520 : : int attnum,
521 : : TupleDesc tupleDesc)
522 : : {
523 : 84268145 : HeapTupleHeader td = tup->t_data;
524 : : char *tp; /* ptr to data part of tuple */
525 : 84268145 : bits8 *bp = td->t_bits; /* ptr to null bitmap in tuple */
6218 tgl@sss.pgh.pa.us 526 : 84268145 : bool slow = false; /* do we have to walk attrs? */
527 : : int off; /* current offset within data */
528 : :
529 : : /* ----------------
530 : : * Three cases:
531 : : *
532 : : * 1: No nulls and no variable-width attributes.
533 : : * 2: Has a null or a var-width AFTER att.
534 : : * 3: Has nulls or var-widths BEFORE att.
535 : : * ----------------
536 : : */
537 : :
538 : 84268145 : attnum--;
539 : :
573 pg@bowt.ie 540 [ + + ]: 84268145 : if (!HeapTupleNoNulls(tup))
541 : : {
542 : : /*
543 : : * there's a null somewhere in the tuple
544 : : *
545 : : * check to see if any preceding bits are null...
546 : : */
4753 bruce@momjian.us 547 : 63038572 : int byte = attnum >> 3;
5208 rhaas@postgresql.org 548 : 63038572 : int finalbit = attnum & 0x07;
549 : :
550 : : /* check for nulls "before" final bit of last byte */
551 [ + + ]: 63038572 : if ((~bp[byte]) & ((1 << finalbit) - 1))
552 : 2636868 : slow = true;
553 : : else
554 : : {
555 : : /* check for nulls in any "earlier" bytes */
556 : : int i;
557 : :
558 [ + + ]: 78077980 : for (i = 0; i < byte; i++)
559 : : {
560 [ + + ]: 17994641 : if (bp[i] != 0xFF)
561 : : {
562 : 318365 : slow = true;
563 : 318365 : break;
564 : : }
565 : : }
566 : : }
567 : : }
568 : :
573 pg@bowt.ie 569 : 84268145 : tp = (char *) td + td->t_hoff;
570 : :
9716 bruce@momjian.us 571 [ + + ]: 84268145 : if (!slow)
572 : : {
573 : : Form_pg_attribute att;
574 : :
575 : : /*
576 : : * If we get here, there are no nulls up to and including the target
577 : : * attribute. If we have a cached offset, we can use it.
578 : : */
2429 andres@anarazel.de 579 : 81312912 : att = TupleDescAttr(tupleDesc, attnum);
580 [ + + ]: 81312912 : if (att->attcacheoff >= 0)
581 : 58159013 : return fetchatt(att, tp + att->attcacheoff);
582 : :
583 : : /*
584 : : * Otherwise, check for non-fixed-length attrs up to and including
585 : : * target. If there aren't any, it's safe to cheaply initialize the
586 : : * cached offsets for these attrs.
587 : : */
573 pg@bowt.ie 588 [ + + ]: 23153899 : if (HeapTupleHasVarWidth(tup))
589 : : {
590 : : int j;
591 : :
9212 tgl@sss.pgh.pa.us 592 [ + + ]: 51515343 : for (j = 0; j <= attnum; j++)
593 : : {
2429 andres@anarazel.de 594 [ + + ]: 51480801 : if (TupleDescAttr(tupleDesc, j)->attlen <= 0)
595 : : {
8536 tgl@sss.pgh.pa.us 596 : 23058500 : slow = true;
9212 597 : 23058500 : break;
598 : : }
599 : : }
600 : : }
601 : : }
602 : :
9716 bruce@momjian.us 603 [ + + ]: 26109132 : if (!slow)
604 : : {
6218 tgl@sss.pgh.pa.us 605 : 95399 : int natts = tupleDesc->natts;
9544 bruce@momjian.us 606 : 95399 : int j = 1;
607 : :
608 : : /*
609 : : * If we get here, we have a tuple with no nulls or var-widths up to
610 : : * and including the target attribute, so we can use the cached offset
611 : : * ... only we don't have it yet, or we'd not have got here. Since
612 : : * it's cheap to compute offsets for fixed-width columns, we take the
613 : : * opportunity to initialize the cached offsets for *all* the leading
614 : : * fixed-width columns, in hope of avoiding future visits to this
615 : : * routine.
616 : : */
2429 andres@anarazel.de 617 : 95399 : TupleDescAttr(tupleDesc, 0)->attcacheoff = 0;
618 : :
619 : : /* we might have set some offsets in the slow path previously */
620 [ + + + + ]: 97220 : while (j < natts && TupleDescAttr(tupleDesc, j)->attcacheoff > 0)
9716 bruce@momjian.us 621 : 1821 : j++;
622 : :
2429 andres@anarazel.de 623 : 95399 : off = TupleDescAttr(tupleDesc, j - 1)->attcacheoff +
624 : 95399 : TupleDescAttr(tupleDesc, j - 1)->attlen;
625 : :
6218 tgl@sss.pgh.pa.us 626 [ + + ]: 964634 : for (; j < natts; j++)
627 : : {
2429 andres@anarazel.de 628 : 919001 : Form_pg_attribute att = TupleDescAttr(tupleDesc, j);
629 : :
630 [ + + ]: 919001 : if (att->attlen <= 0)
6218 tgl@sss.pgh.pa.us 631 : 49766 : break;
632 : :
2429 andres@anarazel.de 633 [ + + + + : 869235 : off = att_align_nominal(off, att->attalign);
+ + - + ]
634 : :
635 : 869235 : att->attcacheoff = off;
636 : :
637 : 869235 : off += att->attlen;
638 : : }
639 : :
6218 tgl@sss.pgh.pa.us 640 [ - + ]: 95399 : Assert(j > attnum);
641 : :
2429 andres@anarazel.de 642 : 95399 : off = TupleDescAttr(tupleDesc, attnum)->attcacheoff;
643 : : }
644 : : else
645 : : {
9544 bruce@momjian.us 646 : 26013733 : bool usecache = true;
647 : : int i;
648 : :
649 : : /*
650 : : * Now we know that we have to walk the tuple CAREFULLY. But we still
651 : : * might be able to cache some offsets for next time.
652 : : *
653 : : * Note - This loop is a little tricky. For each non-null attribute,
654 : : * we have to first account for alignment padding before the attr,
655 : : * then advance over the attr based on its length. Nulls have no
656 : : * storage and no alignment padding either. We can use/set
657 : : * attcacheoff until we reach either a null or a var-width attribute.
658 : : */
6218 tgl@sss.pgh.pa.us 659 : 26013733 : off = 0;
5995 bruce@momjian.us 660 : 26013733 : for (i = 0;; i++) /* loop exit is at "break" */
9716 661 : 106489775 : {
2429 andres@anarazel.de 662 : 132503508 : Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
663 : :
573 pg@bowt.ie 664 [ + + + + ]: 132503508 : if (HeapTupleHasNulls(tup) && att_isnull(i, bp))
665 : : {
7394 tgl@sss.pgh.pa.us 666 : 7950449 : usecache = false;
5995 bruce@momjian.us 667 : 7950449 : continue; /* this cannot be the target att */
668 : : }
669 : :
670 : : /* If we know the next offset, we can skip the rest */
2429 andres@anarazel.de 671 [ + + + + ]: 124553059 : if (usecache && att->attcacheoff >= 0)
672 : 71553851 : off = att->attcacheoff;
673 [ + + ]: 52999208 : else if (att->attlen == -1)
674 : : {
675 : : /*
676 : : * We can only cache the offset for a varlena attribute if the
677 : : * offset is already suitably aligned, so that there would be
678 : : * no pad bytes in any case: then the offset will be valid for
679 : : * either an aligned or unaligned value.
680 : : */
6218 tgl@sss.pgh.pa.us 681 [ + + + + ]: 10534513 : if (usecache &&
2429 andres@anarazel.de 682 [ + + - + : 1216254 : off == att_align_nominal(off, att->attalign))
+ - - - ]
683 : 41912 : att->attcacheoff = off;
684 : : else
685 : : {
686 [ + + + + : 9276347 : off = att_align_pointer(off, att->attalign, -1,
+ - + - -
- ]
687 : : tp + off);
6218 tgl@sss.pgh.pa.us 688 : 9276347 : usecache = false;
689 : : }
690 : : }
691 : : else
692 : : {
693 : : /* not varlena, so safe to use att_align_nominal */
2429 andres@anarazel.de 694 [ + + + + : 43680949 : off = att_align_nominal(off, att->attalign);
+ + - + ]
695 : :
9716 bruce@momjian.us 696 [ + + ]: 43680949 : if (usecache)
2429 andres@anarazel.de 697 : 421447 : att->attcacheoff = off;
698 : : }
699 : :
6218 tgl@sss.pgh.pa.us 700 [ + + ]: 124553059 : if (i == attnum)
701 : 26013733 : break;
702 : :
2429 andres@anarazel.de 703 [ + + + - : 98539326 : off = att_addlength_pointer(off, att->attlen, tp + off);
+ + + + +
- - + + +
- - ]
704 : :
705 [ + + + + ]: 98539326 : if (usecache && att->attlen <= 0)
9351 bruce@momjian.us 706 : 23676084 : usecache = false;
707 : : }
708 : : }
709 : :
2429 andres@anarazel.de 710 : 26109132 : return fetchatt(TupleDescAttr(tupleDesc, attnum), tp + off);
711 : : }
712 : :
713 : : /* ----------------
714 : : * heap_getsysattr
715 : : *
716 : : * Fetch the value of a system attribute for a tuple.
717 : : *
718 : : * This is a support routine for the heap_getattr macro. The macro
719 : : * has already determined that the attnum refers to a system attribute.
720 : : * ----------------
721 : : */
722 : : Datum
7318 tgl@sss.pgh.pa.us 723 : 205 : heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
724 : : {
725 : : Datum result;
726 : :
8509 727 [ - + ]: 205 : Assert(tup);
728 : :
729 : : /* Currently, no sys attribute ever reads as NULL. */
5208 rhaas@postgresql.org 730 : 205 : *isnull = false;
731 : :
8509 tgl@sss.pgh.pa.us 732 [ + + + + : 205 : switch (attnum)
+ - ]
733 : : {
734 : 2 : case SelfItemPointerAttributeNumber:
735 : : /* pass-by-reference datatype */
736 : 2 : result = PointerGetDatum(&(tup->t_self));
737 : 2 : break;
738 : 93 : case MinTransactionIdAttributeNumber:
3766 rhaas@postgresql.org 739 : 93 : result = TransactionIdGetDatum(HeapTupleHeaderGetRawXmin(tup->t_data));
8509 tgl@sss.pgh.pa.us 740 : 93 : break;
741 : 15 : case MaxTransactionIdAttributeNumber:
4099 alvherre@alvh.no-ip. 742 : 15 : result = TransactionIdGetDatum(HeapTupleHeaderGetRawXmax(tup->t_data));
8509 tgl@sss.pgh.pa.us 743 : 15 : break;
6274 744 : 93 : case MinCommandIdAttributeNumber:
745 : : case MaxCommandIdAttributeNumber:
746 : :
747 : : /*
748 : : * cmin and cmax are now both aliases for the same field, which
749 : : * can in fact also be a combo command id. XXX perhaps we should
750 : : * return the "real" cmin or cmax if possible, that is if we are
751 : : * inside the originating transaction?
752 : : */
753 : 93 : result = CommandIdGetDatum(HeapTupleHeaderGetRawCommandId(tup->t_data));
8509 754 : 93 : break;
755 : 2 : case TableOidAttributeNumber:
756 : 2 : result = ObjectIdGetDatum(tup->t_tableOid);
757 : 2 : break;
8509 tgl@sss.pgh.pa.us 758 :UBC 0 : default:
7573 759 [ # # ]: 0 : elog(ERROR, "invalid attnum: %d", attnum);
760 : : result = 0; /* keep compiler quiet */
761 : : break;
762 : : }
8509 tgl@sss.pgh.pa.us 763 :CBC 205 : return result;
764 : : }
765 : :
766 : : /* ----------------
767 : : * heap_copytuple
768 : : *
769 : : * returns a copy of an entire tuple
770 : : *
771 : : * The HeapTuple struct, tuple header, and tuple data are all allocated
772 : : * as a single palloc() block.
773 : : * ----------------
774 : : */
775 : : HeapTuple
10141 scrappy@hub.org 776 : 6842265 : heap_copytuple(HeapTuple tuple)
777 : : {
778 : : HeapTuple newTuple;
779 : :
9270 vadim4o@yahoo.com 780 [ + - - + ]: 6842265 : if (!HeapTupleIsValid(tuple) || tuple->t_data == NULL)
9357 bruce@momjian.us 781 :UBC 0 : return NULL;
782 : :
9270 vadim4o@yahoo.com 783 :CBC 6842265 : newTuple = (HeapTuple) palloc(HEAPTUPLESIZE + tuple->t_len);
784 : 6842265 : newTuple->t_len = tuple->t_len;
785 : 6842265 : newTuple->t_self = tuple->t_self;
8552 tgl@sss.pgh.pa.us 786 : 6842265 : newTuple->t_tableOid = tuple->t_tableOid;
9270 vadim4o@yahoo.com 787 : 6842265 : newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE);
8552 tgl@sss.pgh.pa.us 788 : 6842265 : memcpy((char *) newTuple->t_data, (char *) tuple->t_data, tuple->t_len);
9357 bruce@momjian.us 789 : 6842265 : return newTuple;
790 : : }
791 : :
792 : : /* ----------------
793 : : * heap_copytuple_with_tuple
794 : : *
795 : : * copy a tuple into a caller-supplied HeapTuple management struct
796 : : *
797 : : * Note that after calling this function, the "dest" HeapTuple will not be
798 : : * allocated as a single palloc() block (unlike with heap_copytuple()).
799 : : * ----------------
800 : : */
801 : : void
9270 vadim4o@yahoo.com 802 :UBC 0 : heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest)
803 : : {
804 [ # # # # ]: 0 : if (!HeapTupleIsValid(src) || src->t_data == NULL)
805 : : {
806 : 0 : dest->t_data = NULL;
807 : 0 : return;
808 : : }
809 : :
810 : 0 : dest->t_len = src->t_len;
811 : 0 : dest->t_self = src->t_self;
8552 tgl@sss.pgh.pa.us 812 : 0 : dest->t_tableOid = src->t_tableOid;
9270 vadim4o@yahoo.com 813 : 0 : dest->t_data = (HeapTupleHeader) palloc(src->t_len);
8552 tgl@sss.pgh.pa.us 814 : 0 : memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len);
815 : : }
816 : :
817 : : /*
818 : : * Expand a tuple which has fewer attributes than required. For each attribute
819 : : * not present in the sourceTuple, if there is a missing value that will be
820 : : * used. Otherwise the attribute will be set to NULL.
821 : : *
822 : : * The source tuple must have fewer attributes than the required number.
823 : : *
824 : : * Only one of targetHeapTuple and targetMinimalTuple may be supplied. The
825 : : * other argument must be NULL.
826 : : */
827 : : static void
2209 andrew@dunslane.net 828 : 0 : expand_tuple(HeapTuple *targetHeapTuple,
829 : : MinimalTuple *targetMinimalTuple,
830 : : HeapTuple sourceTuple,
831 : : TupleDesc tupleDesc)
832 : : {
833 : 0 : AttrMissing *attrmiss = NULL;
834 : : int attnum;
835 : : int firstmissingnum;
836 : 0 : bool hasNulls = HeapTupleHasNulls(sourceTuple);
837 : : HeapTupleHeader targetTHeader;
838 : 0 : HeapTupleHeader sourceTHeader = sourceTuple->t_data;
839 : 0 : int sourceNatts = HeapTupleHeaderGetNatts(sourceTHeader);
840 : 0 : int natts = tupleDesc->natts;
841 : : int sourceNullLen;
842 : : int targetNullLen;
843 : 0 : Size sourceDataLen = sourceTuple->t_len - sourceTHeader->t_hoff;
844 : : Size targetDataLen;
845 : : Size len;
846 : : int hoff;
847 : 0 : bits8 *nullBits = NULL;
848 : 0 : int bitMask = 0;
849 : : char *targetData;
850 : : uint16 *infoMask;
851 : :
852 [ # # # # : 0 : Assert((targetHeapTuple && !targetMinimalTuple)
# # # # ]
853 : : || (!targetHeapTuple && targetMinimalTuple));
854 : :
855 [ # # ]: 0 : Assert(sourceNatts < natts);
856 : :
857 [ # # ]: 0 : sourceNullLen = (hasNulls ? BITMAPLEN(sourceNatts) : 0);
858 : :
859 : 0 : targetDataLen = sourceDataLen;
860 : :
861 [ # # ]: 0 : if (tupleDesc->constr &&
862 [ # # ]: 0 : tupleDesc->constr->missing)
863 : : {
864 : : /*
865 : : * If there are missing values we want to put them into the tuple.
866 : : * Before that we have to compute the extra length for the values
867 : : * array and the variable length data.
868 : : */
869 : 0 : attrmiss = tupleDesc->constr->missing;
870 : :
871 : : /*
872 : : * Find the first item in attrmiss for which we don't have a value in
873 : : * the source. We can ignore all the missing entries before that.
874 : : */
875 : 0 : for (firstmissingnum = sourceNatts;
876 [ # # ]: 0 : firstmissingnum < natts;
877 : 0 : firstmissingnum++)
878 : : {
2118 akapila@postgresql.o 879 [ # # ]: 0 : if (attrmiss[firstmissingnum].am_present)
2209 andrew@dunslane.net 880 : 0 : break;
881 : : else
2029 882 : 0 : hasNulls = true;
883 : : }
884 : :
885 : : /*
886 : : * Now walk the missing attributes. If there is a missing value make
887 : : * space for it. Otherwise, it's going to be NULL.
888 : : */
889 : 0 : for (attnum = firstmissingnum;
890 [ # # ]: 0 : attnum < natts;
891 : 0 : attnum++)
892 : : {
893 [ # # ]: 0 : if (attrmiss[attnum].am_present)
894 : : {
895 : 0 : Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum);
896 : :
897 [ # # # # : 0 : targetDataLen = att_align_datum(targetDataLen,
# # # # #
# # # ]
898 : : att->attalign,
899 : : att->attlen,
900 : : attrmiss[attnum].am_value);
901 : :
902 [ # # # # : 0 : targetDataLen = att_addlength_pointer(targetDataLen,
# # # # #
# # # # #
# # ]
903 : : att->attlen,
904 : : attrmiss[attnum].am_value);
905 : : }
906 : : else
907 : : {
908 : : /* no missing value, so it must be null */
909 : 0 : hasNulls = true;
910 : : }
911 : : }
912 : : } /* end if have missing values */
913 : : else
914 : : {
915 : : /*
916 : : * If there are no missing values at all then NULLS must be allowed,
917 : : * since some of the attributes are known to be absent.
918 : : */
2209 919 : 0 : hasNulls = true;
920 : : }
921 : :
922 : 0 : len = 0;
923 : :
924 [ # # ]: 0 : if (hasNulls)
925 : : {
926 : 0 : targetNullLen = BITMAPLEN(natts);
927 : 0 : len += targetNullLen;
928 : : }
929 : : else
930 : 0 : targetNullLen = 0;
931 : :
932 : : /*
933 : : * Allocate and zero the space needed. Note that the tuple body and
934 : : * HeapTupleData management structure are allocated in one chunk.
935 : : */
936 [ # # ]: 0 : if (targetHeapTuple)
937 : : {
938 : 0 : len += offsetof(HeapTupleHeaderData, t_bits);
939 : 0 : hoff = len = MAXALIGN(len); /* align user data safely */
940 : 0 : len += targetDataLen;
941 : :
942 : 0 : *targetHeapTuple = (HeapTuple) palloc0(HEAPTUPLESIZE + len);
943 : 0 : (*targetHeapTuple)->t_data
944 : 0 : = targetTHeader
945 : 0 : = (HeapTupleHeader) ((char *) *targetHeapTuple + HEAPTUPLESIZE);
946 : 0 : (*targetHeapTuple)->t_len = len;
947 : 0 : (*targetHeapTuple)->t_tableOid = sourceTuple->t_tableOid;
1999 948 : 0 : (*targetHeapTuple)->t_self = sourceTuple->t_self;
949 : :
2209 950 : 0 : targetTHeader->t_infomask = sourceTHeader->t_infomask;
951 : 0 : targetTHeader->t_hoff = hoff;
952 : 0 : HeapTupleHeaderSetNatts(targetTHeader, natts);
953 : 0 : HeapTupleHeaderSetDatumLength(targetTHeader, len);
954 : 0 : HeapTupleHeaderSetTypeId(targetTHeader, tupleDesc->tdtypeid);
955 : 0 : HeapTupleHeaderSetTypMod(targetTHeader, tupleDesc->tdtypmod);
956 : : /* We also make sure that t_ctid is invalid unless explicitly set */
957 : 0 : ItemPointerSetInvalid(&(targetTHeader->t_ctid));
958 [ # # ]: 0 : if (targetNullLen > 0)
959 : 0 : nullBits = (bits8 *) ((char *) (*targetHeapTuple)->t_data
960 : : + offsetof(HeapTupleHeaderData, t_bits));
961 : 0 : targetData = (char *) (*targetHeapTuple)->t_data + hoff;
962 : 0 : infoMask = &(targetTHeader->t_infomask);
963 : : }
964 : : else
965 : : {
966 : 0 : len += SizeofMinimalTupleHeader;
967 : 0 : hoff = len = MAXALIGN(len); /* align user data safely */
968 : 0 : len += targetDataLen;
969 : :
970 : 0 : *targetMinimalTuple = (MinimalTuple) palloc0(len);
971 : 0 : (*targetMinimalTuple)->t_len = len;
972 : 0 : (*targetMinimalTuple)->t_hoff = hoff + MINIMAL_TUPLE_OFFSET;
973 : 0 : (*targetMinimalTuple)->t_infomask = sourceTHeader->t_infomask;
974 : : /* Same macro works for MinimalTuples */
975 : 0 : HeapTupleHeaderSetNatts(*targetMinimalTuple, natts);
976 [ # # ]: 0 : if (targetNullLen > 0)
977 : 0 : nullBits = (bits8 *) ((char *) *targetMinimalTuple
978 : 0 : + offsetof(MinimalTupleData, t_bits));
979 : 0 : targetData = (char *) *targetMinimalTuple + hoff;
980 : 0 : infoMask = &((*targetMinimalTuple)->t_infomask);
981 : : }
982 : :
983 [ # # ]: 0 : if (targetNullLen > 0)
984 : : {
985 [ # # ]: 0 : if (sourceNullLen > 0)
986 : : {
987 : : /* if bitmap pre-existed copy in - all is set */
988 : 0 : memcpy(nullBits,
989 : : ((char *) sourceTHeader)
990 : : + offsetof(HeapTupleHeaderData, t_bits),
991 : : sourceNullLen);
992 : 0 : nullBits += sourceNullLen - 1;
993 : : }
994 : : else
995 : : {
996 : 0 : sourceNullLen = BITMAPLEN(sourceNatts);
997 : : /* Set NOT NULL for all existing attributes */
998 : 0 : memset(nullBits, 0xff, sourceNullLen);
999 : :
1000 : 0 : nullBits += sourceNullLen - 1;
1001 : :
1002 [ # # ]: 0 : if (sourceNatts & 0x07)
1003 : : {
1004 : : /* build the mask (inverted!) */
1005 : 0 : bitMask = 0xff << (sourceNatts & 0x07);
1006 : : /* Voila */
1007 : 0 : *nullBits = ~bitMask;
1008 : : }
1009 : : }
1010 : :
1011 : 0 : bitMask = (1 << ((sourceNatts - 1) & 0x07));
1012 : : } /* End if have null bitmap */
1013 : :
1014 : 0 : memcpy(targetData,
1015 : 0 : ((char *) sourceTuple->t_data) + sourceTHeader->t_hoff,
1016 : : sourceDataLen);
1017 : :
1018 : 0 : targetData += sourceDataLen;
1019 : :
1020 : : /* Now fill in the missing values */
1021 [ # # ]: 0 : for (attnum = sourceNatts; attnum < natts; attnum++)
1022 : : {
1023 : :
1024 : 0 : Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum);
1025 : :
2118 akapila@postgresql.o 1026 [ # # # # ]: 0 : if (attrmiss && attrmiss[attnum].am_present)
1027 : : {
2209 andrew@dunslane.net 1028 : 0 : fill_val(attr,
1029 : 0 : nullBits ? &nullBits : NULL,
1030 : : &bitMask,
1031 : : &targetData,
1032 : : infoMask,
2118 akapila@postgresql.o 1033 [ # # ]: 0 : attrmiss[attnum].am_value,
1034 : : false);
1035 : : }
1036 : : else
1037 : : {
2209 andrew@dunslane.net 1038 : 0 : fill_val(attr,
1039 : : &nullBits,
1040 : : &bitMask,
1041 : : &targetData,
1042 : : infoMask,
1043 : : (Datum) 0,
1044 : : true);
1045 : : }
1046 : : } /* end loop over missing attributes */
1047 : 0 : }
1048 : :
1049 : : /*
1050 : : * Fill in the missing values for a minimal HeapTuple
1051 : : */
1052 : : MinimalTuple
1053 : 0 : minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc)
1054 : : {
1055 : : MinimalTuple minimalTuple;
1056 : :
1057 : 0 : expand_tuple(NULL, &minimalTuple, sourceTuple, tupleDesc);
1058 : 0 : return minimalTuple;
1059 : : }
1060 : :
1061 : : /*
1062 : : * Fill in the missing values for an ordinary HeapTuple
1063 : : */
1064 : : HeapTuple
1065 : 0 : heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc)
1066 : : {
1067 : : HeapTuple heapTuple;
1068 : :
1069 : 0 : expand_tuple(&heapTuple, NULL, sourceTuple, tupleDesc);
1070 : 0 : return heapTuple;
1071 : : }
1072 : :
1073 : : /* ----------------
1074 : : * heap_copy_tuple_as_datum
1075 : : *
1076 : : * copy a tuple as a composite-type Datum
1077 : : * ----------------
1078 : : */
1079 : : Datum
3636 tgl@sss.pgh.pa.us 1080 :CBC 40682 : heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc)
1081 : : {
1082 : : HeapTupleHeader td;
1083 : :
1084 : : /*
1085 : : * If the tuple contains any external TOAST pointers, we have to inline
1086 : : * those fields to meet the conventions for composite-type Datums.
1087 : : */
1088 [ - + ]: 40682 : if (HeapTupleHasExternal(tuple))
3636 tgl@sss.pgh.pa.us 1089 :UBC 0 : return toast_flatten_tuple_to_datum(tuple->t_data,
1090 : : tuple->t_len,
1091 : : tupleDesc);
1092 : :
1093 : : /*
1094 : : * Fast path for easy case: just make a palloc'd copy and insert the
1095 : : * correct composite-Datum header fields (since those may not be set if
1096 : : * the given tuple came from disk, rather than from heap_form_tuple).
1097 : : */
3636 tgl@sss.pgh.pa.us 1098 :CBC 40682 : td = (HeapTupleHeader) palloc(tuple->t_len);
1099 : 40682 : memcpy((char *) td, (char *) tuple->t_data, tuple->t_len);
1100 : :
1101 : 40682 : HeapTupleHeaderSetDatumLength(td, tuple->t_len);
1102 : 40682 : HeapTupleHeaderSetTypeId(td, tupleDesc->tdtypeid);
1103 : 40682 : HeapTupleHeaderSetTypMod(td, tupleDesc->tdtypmod);
1104 : :
1105 : 40682 : return PointerGetDatum(td);
1106 : : }
1107 : :
1108 : : /*
1109 : : * heap_form_tuple
1110 : : * construct a tuple from the given values[] and isnull[] arrays,
1111 : : * which are of the length indicated by tupleDescriptor->natts
1112 : : *
1113 : : * The result is allocated in the current memory context.
1114 : : */
1115 : : HeapTuple
6969 1116 : 11634818 : heap_form_tuple(TupleDesc tupleDescriptor,
1117 : : const Datum *values,
1118 : : const bool *isnull)
1119 : : {
1120 : : HeapTuple tuple; /* return tuple */
1121 : : HeapTupleHeader td; /* tuple data */
1122 : : Size len,
1123 : : data_len;
1124 : : int hoff;
1125 : 11634818 : bool hasnull = false;
1126 : 11634818 : int numberOfAttributes = tupleDescriptor->natts;
1127 : : int i;
1128 : :
1129 [ - + ]: 11634818 : if (numberOfAttributes > MaxTupleAttributeNumber)
6969 tgl@sss.pgh.pa.us 1130 [ # # ]:UBC 0 : ereport(ERROR,
1131 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
1132 : : errmsg("number of columns (%d) exceeds limit (%d)",
1133 : : numberOfAttributes, MaxTupleAttributeNumber)));
1134 : :
1135 : : /*
1136 : : * Check for nulls
1137 : : */
6969 tgl@sss.pgh.pa.us 1138 [ + + ]:CBC 56244709 : for (i = 0; i < numberOfAttributes; i++)
1139 : : {
1140 [ + + ]: 47627339 : if (isnull[i])
1141 : : {
3636 1142 : 3017448 : hasnull = true;
1143 : 3017448 : break;
1144 : : }
1145 : : }
1146 : :
1147 : : /*
1148 : : * Determine total space needed
1149 : : */
6969 1150 : 11634818 : len = offsetof(HeapTupleHeaderData, t_bits);
1151 : :
1152 [ + + ]: 11634818 : if (hasnull)
1153 : 3017448 : len += BITMAPLEN(numberOfAttributes);
1154 : :
1155 : 11634818 : hoff = len = MAXALIGN(len); /* align user data safely */
1156 : :
6218 1157 : 11634818 : data_len = heap_compute_data_size(tupleDescriptor, values, isnull);
1158 : :
1159 : 11634818 : len += data_len;
1160 : :
1161 : : /*
1162 : : * Allocate and zero the space needed. Note that the tuple body and
1163 : : * HeapTupleData management structure are allocated in one chunk.
1164 : : */
2531 alvherre@alvh.no-ip. 1165 : 11634818 : tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + len);
6969 tgl@sss.pgh.pa.us 1166 : 11634818 : tuple->t_data = td = (HeapTupleHeader) ((char *) tuple + HEAPTUPLESIZE);
1167 : :
1168 : : /*
1169 : : * And fill in the information. Note we fill the Datum fields even though
1170 : : * this tuple may never become a Datum. This lets HeapTupleHeaderGetDatum
1171 : : * identify the tuple type if needed.
1172 : : */
1173 : 11634818 : tuple->t_len = len;
1174 : 11634818 : ItemPointerSetInvalid(&(tuple->t_self));
1175 : 11634818 : tuple->t_tableOid = InvalidOid;
1176 : :
1177 : 11634818 : HeapTupleHeaderSetDatumLength(td, len);
1178 : 11634818 : HeapTupleHeaderSetTypeId(td, tupleDescriptor->tdtypeid);
1179 : 11634818 : HeapTupleHeaderSetTypMod(td, tupleDescriptor->tdtypmod);
1180 : : /* We also make sure that t_ctid is invalid unless explicitly set */
3259 1181 : 11634818 : ItemPointerSetInvalid(&(td->t_ctid));
1182 : :
6305 bruce@momjian.us 1183 : 11634818 : HeapTupleHeaderSetNatts(td, numberOfAttributes);
6969 tgl@sss.pgh.pa.us 1184 : 11634818 : td->t_hoff = hoff;
1185 : :
1186 [ + + ]: 11634818 : heap_fill_tuple(tupleDescriptor,
1187 : : values,
1188 : : isnull,
1189 : : (char *) td + hoff,
1190 : : data_len,
1191 : : &td->t_infomask,
1192 : : (hasnull ? td->t_bits : NULL));
1193 : :
1194 : 11634818 : return tuple;
1195 : : }
1196 : :
1197 : : /*
1198 : : * heap_modify_tuple
1199 : : * form a new tuple from an old tuple and a set of replacement values.
1200 : : *
1201 : : * The replValues, replIsnull, and doReplace arrays must be of the length
1202 : : * indicated by tupleDesc->natts. The new tuple is constructed using the data
1203 : : * from replValues/replIsnull at columns where doReplace is true, and using
1204 : : * the data from the old tuple at columns where doReplace is false.
1205 : : *
1206 : : * The result is allocated in the current memory context.
1207 : : */
1208 : : HeapTuple
1209 : 38640 : heap_modify_tuple(HeapTuple tuple,
1210 : : TupleDesc tupleDesc,
1211 : : const Datum *replValues,
1212 : : const bool *replIsnull,
1213 : : const bool *doReplace)
1214 : : {
1215 : 38640 : int numberOfAttributes = tupleDesc->natts;
1216 : : int attoff;
1217 : : Datum *values;
1218 : : bool *isnull;
1219 : : HeapTuple newTuple;
1220 : :
1221 : : /*
1222 : : * allocate and fill values and isnull arrays from either the tuple or the
1223 : : * repl information, as appropriate.
1224 : : *
1225 : : * NOTE: it's debatable whether to use heap_deform_tuple() here or just
1226 : : * heap_getattr() only the non-replaced columns. The latter could win if
1227 : : * there are many replaced columns and few non-replaced ones. However,
1228 : : * heap_deform_tuple costs only O(N) while the heap_getattr way would cost
1229 : : * O(N^2) if there are many non-replaced columns, so it seems better to
1230 : : * err on the side of linear cost.
1231 : : */
1232 : 38640 : values = (Datum *) palloc(numberOfAttributes * sizeof(Datum));
1233 : 38640 : isnull = (bool *) palloc(numberOfAttributes * sizeof(bool));
1234 : :
1235 : 38640 : heap_deform_tuple(tuple, tupleDesc, values, isnull);
1236 : :
1237 [ + + ]: 1146425 : for (attoff = 0; attoff < numberOfAttributes; attoff++)
1238 : : {
1239 [ + + ]: 1107785 : if (doReplace[attoff])
1240 : : {
1241 : 551342 : values[attoff] = replValues[attoff];
1242 : 551342 : isnull[attoff] = replIsnull[attoff];
1243 : : }
1244 : : }
1245 : :
1246 : : /*
1247 : : * create a new tuple from the values and isnull arrays
1248 : : */
1249 : 38640 : newTuple = heap_form_tuple(tupleDesc, values, isnull);
1250 : :
1251 : 38640 : pfree(values);
1252 : 38640 : pfree(isnull);
1253 : :
1254 : : /*
1255 : : * copy the identification info of the old tuple: t_ctid, t_self
1256 : : */
1257 : 38640 : newTuple->t_data->t_ctid = tuple->t_data->t_ctid;
1258 : 38640 : newTuple->t_self = tuple->t_self;
1259 : 38640 : newTuple->t_tableOid = tuple->t_tableOid;
1260 : :
1261 : 38640 : return newTuple;
1262 : : }
1263 : :
1264 : : /*
1265 : : * heap_modify_tuple_by_cols
1266 : : * form a new tuple from an old tuple and a set of replacement values.
1267 : : *
1268 : : * This is like heap_modify_tuple, except that instead of specifying which
1269 : : * column(s) to replace by a boolean map, an array of target column numbers
1270 : : * is used. This is often more convenient when a fixed number of columns
1271 : : * are to be replaced. The replCols, replValues, and replIsnull arrays must
1272 : : * be of length nCols. Target column numbers are indexed from 1.
1273 : : *
1274 : : * The result is allocated in the current memory context.
1275 : : */
1276 : : HeapTuple
2714 1277 : 18 : heap_modify_tuple_by_cols(HeapTuple tuple,
1278 : : TupleDesc tupleDesc,
1279 : : int nCols,
1280 : : const int *replCols,
1281 : : const Datum *replValues,
1282 : : const bool *replIsnull)
1283 : : {
1284 : 18 : int numberOfAttributes = tupleDesc->natts;
1285 : : Datum *values;
1286 : : bool *isnull;
1287 : : HeapTuple newTuple;
1288 : : int i;
1289 : :
1290 : : /*
1291 : : * allocate and fill values and isnull arrays from the tuple, then replace
1292 : : * selected columns from the input arrays.
1293 : : */
1294 : 18 : values = (Datum *) palloc(numberOfAttributes * sizeof(Datum));
1295 : 18 : isnull = (bool *) palloc(numberOfAttributes * sizeof(bool));
1296 : :
1297 : 18 : heap_deform_tuple(tuple, tupleDesc, values, isnull);
1298 : :
1299 [ + + ]: 36 : for (i = 0; i < nCols; i++)
1300 : : {
1301 : 18 : int attnum = replCols[i];
1302 : :
1303 [ + - - + ]: 18 : if (attnum <= 0 || attnum > numberOfAttributes)
2714 tgl@sss.pgh.pa.us 1304 [ # # ]:UBC 0 : elog(ERROR, "invalid column number %d", attnum);
2714 tgl@sss.pgh.pa.us 1305 :CBC 18 : values[attnum - 1] = replValues[i];
1306 : 18 : isnull[attnum - 1] = replIsnull[i];
1307 : : }
1308 : :
1309 : : /*
1310 : : * create a new tuple from the values and isnull arrays
1311 : : */
1312 : 18 : newTuple = heap_form_tuple(tupleDesc, values, isnull);
1313 : :
1314 : 18 : pfree(values);
1315 : 18 : pfree(isnull);
1316 : :
1317 : : /*
1318 : : * copy the identification info of the old tuple: t_ctid, t_self
1319 : : */
1320 : 18 : newTuple->t_data->t_ctid = tuple->t_data->t_ctid;
1321 : 18 : newTuple->t_self = tuple->t_self;
1322 : 18 : newTuple->t_tableOid = tuple->t_tableOid;
1323 : :
1324 : 18 : return newTuple;
1325 : : }
1326 : :
1327 : : /*
1328 : : * heap_deform_tuple
1329 : : * Given a tuple, extract data into values/isnull arrays; this is
1330 : : * the inverse of heap_form_tuple.
1331 : : *
1332 : : * Storage for the values/isnull arrays is provided by the caller;
1333 : : * it should be sized according to tupleDesc->natts not
1334 : : * HeapTupleHeaderGetNatts(tuple->t_data).
1335 : : *
1336 : : * Note that for pass-by-reference datatypes, the pointer placed
1337 : : * in the Datum will point into the given tuple.
1338 : : *
1339 : : * When all or most of a tuple's fields need to be extracted,
1340 : : * this routine will be significantly quicker than a loop around
1341 : : * heap_getattr; the loop will become O(N^2) as soon as any
1342 : : * noncacheable attribute offsets are involved.
1343 : : */
1344 : : void
6969 1345 : 2765092 : heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
1346 : : Datum *values, bool *isnull)
1347 : : {
1348 : 2765092 : HeapTupleHeader tup = tuple->t_data;
1349 : 2765092 : bool hasnulls = HeapTupleHasNulls(tuple);
1350 : 2765092 : int tdesc_natts = tupleDesc->natts;
1351 : : int natts; /* number of atts to extract */
1352 : : int attnum;
1353 : : char *tp; /* ptr to tuple data */
1354 : : uint32 off; /* offset in tuple data */
2489 1355 : 2765092 : bits8 *bp = tup->t_bits; /* ptr to null bitmap in tuple */
6969 1356 : 2765092 : bool slow = false; /* can we use/set attcacheoff? */
1357 : :
6305 bruce@momjian.us 1358 : 2765092 : natts = HeapTupleHeaderGetNatts(tup);
1359 : :
1360 : : /*
1361 : : * In inheritance situations, it is possible that the given tuple actually
1362 : : * has more fields than the caller is expecting. Don't run off the end of
1363 : : * the caller's arrays.
1364 : : */
6969 tgl@sss.pgh.pa.us 1365 : 2765092 : natts = Min(natts, tdesc_natts);
1366 : :
1367 : 2765092 : tp = (char *) tup + tup->t_hoff;
1368 : :
1369 : 2765092 : off = 0;
1370 : :
1371 [ + + ]: 10101216 : for (attnum = 0; attnum < natts; attnum++)
1372 : : {
2429 andres@anarazel.de 1373 : 7336124 : Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
1374 : :
6969 tgl@sss.pgh.pa.us 1375 [ + + + + ]: 7336124 : if (hasnulls && att_isnull(attnum, bp))
1376 : : {
1377 : 439484 : values[attnum] = (Datum) 0;
1378 : 439484 : isnull[attnum] = true;
1379 : 439484 : slow = true; /* can't use attcacheoff anymore */
1380 : 439484 : continue;
1381 : : }
1382 : :
1383 : 6896640 : isnull[attnum] = false;
1384 : :
1385 [ + + + + ]: 6896640 : if (!slow && thisatt->attcacheoff >= 0)
1386 : 6514394 : off = thisatt->attcacheoff;
6218 1387 [ + + ]: 382246 : else if (thisatt->attlen == -1)
1388 : : {
1389 : : /*
1390 : : * We can only cache the offset for a varlena attribute if the
1391 : : * offset is already suitably aligned, so that there would be no
1392 : : * pad bytes in any case: then the offset will be valid for either
1393 : : * an aligned or unaligned value.
1394 : : */
1395 [ + + + + ]: 210992 : if (!slow &&
1396 [ + + - + : 34839 : off == att_align_nominal(off, thisatt->attalign))
+ - - - ]
1397 : 23392 : thisatt->attcacheoff = off;
1398 : : else
1399 : : {
1400 [ + + + + : 152761 : off = att_align_pointer(off, thisatt->attalign, -1,
+ - + - -
- ]
1401 : : tp + off);
1402 : 152761 : slow = true;
1403 : : }
1404 : : }
1405 : : else
1406 : : {
1407 : : /* not varlena, so safe to use att_align_nominal */
1408 [ + + + + : 206093 : off = att_align_nominal(off, thisatt->attalign);
+ + - + ]
1409 : :
6969 1410 [ + + ]: 206093 : if (!slow)
1411 : 35242 : thisatt->attcacheoff = off;
1412 : : }
1413 : :
1414 : 6896640 : values[attnum] = fetchatt(thisatt, tp + off);
1415 : :
6218 1416 [ + + + + : 6896640 : off = att_addlength_pointer(off, thisatt->attlen, tp + off);
+ + + + +
- - + + +
- + ]
1417 : :
6969 1418 [ + + ]: 6896640 : if (thisatt->attlen <= 0)
1419 : 1257662 : slow = true; /* can't use attcacheoff anymore */
1420 : : }
1421 : :
1422 : : /*
1423 : : * If tuple doesn't have all the atts indicated by tupleDesc, read the
1424 : : * rest as nulls or missing values as appropriate.
1425 : : */
1426 [ + + ]: 2765104 : for (; attnum < tdesc_natts; attnum++)
2209 andrew@dunslane.net 1427 : 12 : values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]);
6969 tgl@sss.pgh.pa.us 1428 : 2765092 : }
1429 : :
1430 : : /*
1431 : : * heap_freetuple
1432 : : */
1433 : : void
8886 JanWieck@Yahoo.com 1434 : 9909844 : heap_freetuple(HeapTuple htup)
1435 : : {
1436 : 9909844 : pfree(htup);
1437 : 9909844 : }
1438 : :
1439 : :
1440 : : /*
1441 : : * heap_form_minimal_tuple
1442 : : * construct a MinimalTuple from the given values[] and isnull[] arrays,
1443 : : * which are of the length indicated by tupleDescriptor->natts
1444 : : *
1445 : : * This is exactly like heap_form_tuple() except that the result is a
1446 : : * "minimal" tuple lacking a HeapTupleData header as well as room for system
1447 : : * columns.
1448 : : *
1449 : : * The result is allocated in the current memory context.
1450 : : */
1451 : : MinimalTuple
6501 tgl@sss.pgh.pa.us 1452 : 21485251 : heap_form_minimal_tuple(TupleDesc tupleDescriptor,
1453 : : const Datum *values,
1454 : : const bool *isnull)
1455 : : {
1456 : : MinimalTuple tuple; /* return tuple */
1457 : : Size len,
1458 : : data_len;
1459 : : int hoff;
1460 : 21485251 : bool hasnull = false;
1461 : 21485251 : int numberOfAttributes = tupleDescriptor->natts;
1462 : : int i;
1463 : :
1464 [ - + ]: 21485251 : if (numberOfAttributes > MaxTupleAttributeNumber)
6501 tgl@sss.pgh.pa.us 1465 [ # # ]:UBC 0 : ereport(ERROR,
1466 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
1467 : : errmsg("number of columns (%d) exceeds limit (%d)",
1468 : : numberOfAttributes, MaxTupleAttributeNumber)));
1469 : :
1470 : : /*
1471 : : * Check for nulls
1472 : : */
6501 tgl@sss.pgh.pa.us 1473 [ + + ]:CBC 59741482 : for (i = 0; i < numberOfAttributes; i++)
1474 : : {
1475 [ + + ]: 38539792 : if (isnull[i])
1476 : : {
3636 1477 : 283561 : hasnull = true;
1478 : 283561 : break;
1479 : : }
1480 : : }
1481 : :
1482 : : /*
1483 : : * Determine total space needed
1484 : : */
3340 1485 : 21485251 : len = SizeofMinimalTupleHeader;
1486 : :
6501 1487 [ + + ]: 21485251 : if (hasnull)
1488 : 283561 : len += BITMAPLEN(numberOfAttributes);
1489 : :
1490 : 21485251 : hoff = len = MAXALIGN(len); /* align user data safely */
1491 : :
6218 1492 : 21485251 : data_len = heap_compute_data_size(tupleDescriptor, values, isnull);
1493 : :
1494 : 21485251 : len += data_len;
1495 : :
1496 : : /*
1497 : : * Allocate and zero the space needed.
1498 : : */
6501 1499 : 21485251 : tuple = (MinimalTuple) palloc0(len);
1500 : :
1501 : : /*
1502 : : * And fill in the information.
1503 : : */
1504 : 21485251 : tuple->t_len = len;
6305 bruce@momjian.us 1505 : 21485251 : HeapTupleHeaderSetNatts(tuple, numberOfAttributes);
6501 tgl@sss.pgh.pa.us 1506 : 21485251 : tuple->t_hoff = hoff + MINIMAL_TUPLE_OFFSET;
1507 : :
1508 [ + + ]: 21485251 : heap_fill_tuple(tupleDescriptor,
1509 : : values,
1510 : : isnull,
1511 : : (char *) tuple + hoff,
1512 : : data_len,
1513 : : &tuple->t_infomask,
1514 : : (hasnull ? tuple->t_bits : NULL));
1515 : :
1516 : 21485251 : return tuple;
1517 : : }
1518 : :
1519 : : /*
1520 : : * heap_free_minimal_tuple
1521 : : */
1522 : : void
1523 : 18885515 : heap_free_minimal_tuple(MinimalTuple mtup)
1524 : : {
1525 : 18885515 : pfree(mtup);
1526 : 18885515 : }
1527 : :
1528 : : /*
1529 : : * heap_copy_minimal_tuple
1530 : : * copy a MinimalTuple
1531 : : *
1532 : : * The result is allocated in the current memory context.
1533 : : */
1534 : : MinimalTuple
1535 : 2218606 : heap_copy_minimal_tuple(MinimalTuple mtup)
1536 : : {
1537 : : MinimalTuple result;
1538 : :
1539 : 2218606 : result = (MinimalTuple) palloc(mtup->t_len);
1540 : 2218606 : memcpy(result, mtup, mtup->t_len);
1541 : 2218606 : return result;
1542 : : }
1543 : :
1544 : : /*
1545 : : * heap_tuple_from_minimal_tuple
1546 : : * create a HeapTuple by copying from a MinimalTuple;
1547 : : * system columns are filled with zeroes
1548 : : *
1549 : : * The result is allocated in the current memory context.
1550 : : * The HeapTuple struct, tuple header, and tuple data are all allocated
1551 : : * as a single palloc() block.
1552 : : */
1553 : : HeapTuple
1554 : 443898 : heap_tuple_from_minimal_tuple(MinimalTuple mtup)
1555 : : {
1556 : : HeapTuple result;
1557 : 443898 : uint32 len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
1558 : :
1559 : 443898 : result = (HeapTuple) palloc(HEAPTUPLESIZE + len);
1560 : 443898 : result->t_len = len;
1561 : 443898 : ItemPointerSetInvalid(&(result->t_self));
1562 : 443898 : result->t_tableOid = InvalidOid;
1563 : 443898 : result->t_data = (HeapTupleHeader) ((char *) result + HEAPTUPLESIZE);
1564 : 443898 : memcpy((char *) result->t_data + MINIMAL_TUPLE_OFFSET, mtup, mtup->t_len);
6305 bruce@momjian.us 1565 : 443898 : memset(result->t_data, 0, offsetof(HeapTupleHeaderData, t_infomask2));
6501 tgl@sss.pgh.pa.us 1566 : 443898 : return result;
1567 : : }
1568 : :
1569 : : /*
1570 : : * minimal_tuple_from_heap_tuple
1571 : : * create a MinimalTuple by copying from a HeapTuple
1572 : : *
1573 : : * The result is allocated in the current memory context.
1574 : : */
1575 : : MinimalTuple
1576 : 2072582 : minimal_tuple_from_heap_tuple(HeapTuple htup)
1577 : : {
1578 : : MinimalTuple result;
1579 : : uint32 len;
1580 : :
1581 [ - + ]: 2072582 : Assert(htup->t_len > MINIMAL_TUPLE_OFFSET);
1582 : 2072582 : len = htup->t_len - MINIMAL_TUPLE_OFFSET;
1583 : 2072582 : result = (MinimalTuple) palloc(len);
1584 : 2072582 : memcpy(result, (char *) htup->t_data + MINIMAL_TUPLE_OFFSET, len);
1585 : 2072582 : result->t_len = len;
1586 : 2072582 : return result;
1587 : : }
1588 : :
1589 : : /*
1590 : : * This mainly exists so JIT can inline the definition, but it's also
1591 : : * sometimes useful in debugging sessions.
1592 : : */
1593 : : size_t
2211 andres@anarazel.de 1594 :UBC 0 : varsize_any(void *p)
1595 : : {
1596 [ # # # # : 0 : return VARSIZE_ANY(p);
# # # # #
# ]
1597 : : }
|