1 /*-------------------------------------------------------------------------
2 *
3 * expandedrecord.c
4 * Functions for manipulating composite expanded objects.
5 *
6 * This module supports "expanded objects" (cf. expandeddatum.h) that can
7 * store values of named composite types, domains over named composite types,
8 * and record types (registered or anonymous).
9 *
10 * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
11 * Portions Copyright (c) 1994, Regents of the University of California
12 *
13 *
14 * IDENTIFICATION
15 * src/backend/utils/adt/expandedrecord.c
16 *
17 *-------------------------------------------------------------------------
18 */
19 #include "postgres.h"
20
21 #include "access/htup_details.h"
22 #include "access/tuptoaster.h"
23 #include "catalog/heap.h"
24 #include "catalog/pg_type.h"
25 #include "utils/builtins.h"
26 #include "utils/datum.h"
27 #include "utils/expandedrecord.h"
28 #include "utils/memutils.h"
29 #include "utils/typcache.h"
30
31
32 /* "Methods" required for an expanded object */
33 static Size ER_get_flat_size(ExpandedObjectHeader *eohptr);
34 static void ER_flatten_into(ExpandedObjectHeader *eohptr,
35 void *result, Size allocated_size);
36
37 static const ExpandedObjectMethods ER_methods =
38 {
39 ER_get_flat_size,
40 ER_flatten_into
41 };
42
43 /* Other local functions */
44 static void ER_mc_callback(void *arg);
45 static MemoryContext get_short_term_cxt(ExpandedRecordHeader *erh);
46 static void build_dummy_expanded_header(ExpandedRecordHeader *main_erh);
47 static pg_noinline void check_domain_for_new_field(ExpandedRecordHeader *erh,
48 int fnumber,
49 Datum newValue, bool isnull);
50 static pg_noinline void check_domain_for_new_tuple(ExpandedRecordHeader *erh,
51 HeapTuple tuple);
52
53
54 /*
55 * Build an expanded record of the specified composite type
56 *
57 * type_id can be RECORDOID, but only if a positive typmod is given.
58 *
59 * The expanded record is initially "empty", having a state logically
60 * equivalent to a NULL composite value (not ROW(NULL, NULL, ...)).
61 * Note that this might not be a valid state for a domain type;
62 * if the caller needs to check that, call
63 * expanded_record_set_tuple(erh, NULL, false, false).
64 *
65 * The expanded object will be a child of parentcontext.
66 */
67 ExpandedRecordHeader *
make_expanded_record_from_typeid(Oid type_id,int32 typmod,MemoryContext parentcontext)68 make_expanded_record_from_typeid(Oid type_id, int32 typmod,
69 MemoryContext parentcontext)
70 {
71 ExpandedRecordHeader *erh;
72 int flags = 0;
73 TupleDesc tupdesc;
74 uint64 tupdesc_id;
75 MemoryContext objcxt;
76 char *chunk;
77
78 if (type_id != RECORDOID)
79 {
80 /*
81 * Consult the typcache to see if it's a domain over composite, and in
82 * any case to get the tupdesc and tupdesc identifier.
83 */
84 TypeCacheEntry *typentry;
85
86 typentry = lookup_type_cache(type_id,
87 TYPECACHE_TUPDESC |
88 TYPECACHE_DOMAIN_BASE_INFO);
89 if (typentry->typtype == TYPTYPE_DOMAIN)
90 {
91 flags |= ER_FLAG_IS_DOMAIN;
92 typentry = lookup_type_cache(typentry->domainBaseType,
93 TYPECACHE_TUPDESC);
94 }
95 if (typentry->tupDesc == NULL)
96 ereport(ERROR,
97 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
98 errmsg("type %s is not composite",
99 format_type_be(type_id))));
100 tupdesc = typentry->tupDesc;
101 tupdesc_id = typentry->tupDesc_identifier;
102 }
103 else
104 {
105 /*
106 * For RECORD types, get the tupdesc and identifier from typcache.
107 */
108 tupdesc = lookup_rowtype_tupdesc(type_id, typmod);
109 tupdesc_id = assign_record_type_identifier(type_id, typmod);
110 }
111
112 /*
113 * Allocate private context for expanded object. We use a regular-size
114 * context, not a small one, to improve the odds that we can fit a tupdesc
115 * into it without needing an extra malloc block. (This code path doesn't
116 * ever need to copy a tupdesc into the expanded record, but let's be
117 * consistent with the other ways of making an expanded record.)
118 */
119 objcxt = AllocSetContextCreate(parentcontext,
120 "expanded record",
121 ALLOCSET_DEFAULT_SIZES);
122
123 /*
124 * Since we already know the number of fields in the tupdesc, we can
125 * allocate the dvalues/dnulls arrays along with the record header. This
126 * is useless if we never need those arrays, but it costs almost nothing,
127 * and it will save a palloc cycle if we do need them.
128 */
129 erh = (ExpandedRecordHeader *)
130 MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
131 + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
132
133 /* Ensure all header fields are initialized to 0/null */
134 memset(erh, 0, sizeof(ExpandedRecordHeader));
135
136 EOH_init_header(&erh->hdr, &ER_methods, objcxt);
137 erh->er_magic = ER_MAGIC;
138
139 /* Set up dvalues/dnulls, with no valid contents as yet */
140 chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
141 erh->dvalues = (Datum *) chunk;
142 erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
143 erh->nfields = tupdesc->natts;
144
145 /* Fill in composite-type identification info */
146 erh->er_decltypeid = type_id;
147 erh->er_typeid = tupdesc->tdtypeid;
148 erh->er_typmod = tupdesc->tdtypmod;
149 erh->er_tupdesc_id = tupdesc_id;
150
151 erh->flags = flags;
152
153 /*
154 * If what we got from the typcache is a refcounted tupdesc, we need to
155 * acquire our own refcount on it. We manage the refcount with a memory
156 * context callback rather than assuming that the CurrentResourceOwner is
157 * longer-lived than this expanded object.
158 */
159 if (tupdesc->tdrefcount >= 0)
160 {
161 /* Register callback to release the refcount */
162 erh->er_mcb.func = ER_mc_callback;
163 erh->er_mcb.arg = (void *) erh;
164 MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
165 &erh->er_mcb);
166
167 /* And save the pointer */
168 erh->er_tupdesc = tupdesc;
169 tupdesc->tdrefcount++;
170
171 /* If we called lookup_rowtype_tupdesc, release the pin it took */
172 if (type_id == RECORDOID)
173 DecrTupleDescRefCount(tupdesc);
174 }
175 else
176 {
177 /*
178 * If it's not refcounted, just assume it will outlive the expanded
179 * object. (This can happen for shared record types, for instance.)
180 */
181 erh->er_tupdesc = tupdesc;
182 }
183
184 /*
185 * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the
186 * record remains logically empty.
187 */
188
189 return erh;
190 }
191
192 /*
193 * Build an expanded record of the rowtype defined by the tupdesc
194 *
195 * The tupdesc is copied if necessary (i.e., if we can't just bump its
196 * reference count instead).
197 *
198 * The expanded record is initially "empty", having a state logically
199 * equivalent to a NULL composite value (not ROW(NULL, NULL, ...)).
200 *
201 * The expanded object will be a child of parentcontext.
202 */
203 ExpandedRecordHeader *
make_expanded_record_from_tupdesc(TupleDesc tupdesc,MemoryContext parentcontext)204 make_expanded_record_from_tupdesc(TupleDesc tupdesc,
205 MemoryContext parentcontext)
206 {
207 ExpandedRecordHeader *erh;
208 uint64 tupdesc_id;
209 MemoryContext objcxt;
210 MemoryContext oldcxt;
211 char *chunk;
212
213 if (tupdesc->tdtypeid != RECORDOID)
214 {
215 /*
216 * If it's a named composite type (not RECORD), we prefer to reference
217 * the typcache's copy of the tupdesc, which is guaranteed to be
218 * refcounted (the given tupdesc might not be). In any case, we need
219 * to consult the typcache to get the correct tupdesc identifier.
220 *
221 * Note that tdtypeid couldn't be a domain type, so we need not
222 * consider that case here.
223 */
224 TypeCacheEntry *typentry;
225
226 typentry = lookup_type_cache(tupdesc->tdtypeid, TYPECACHE_TUPDESC);
227 if (typentry->tupDesc == NULL)
228 ereport(ERROR,
229 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
230 errmsg("type %s is not composite",
231 format_type_be(tupdesc->tdtypeid))));
232 tupdesc = typentry->tupDesc;
233 tupdesc_id = typentry->tupDesc_identifier;
234 }
235 else
236 {
237 /*
238 * For RECORD types, get the appropriate unique identifier (possibly
239 * freshly assigned).
240 */
241 tupdesc_id = assign_record_type_identifier(tupdesc->tdtypeid,
242 tupdesc->tdtypmod);
243 }
244
245 /*
246 * Allocate private context for expanded object. We use a regular-size
247 * context, not a small one, to improve the odds that we can fit a tupdesc
248 * into it without needing an extra malloc block.
249 */
250 objcxt = AllocSetContextCreate(parentcontext,
251 "expanded record",
252 ALLOCSET_DEFAULT_SIZES);
253
254 /*
255 * Since we already know the number of fields in the tupdesc, we can
256 * allocate the dvalues/dnulls arrays along with the record header. This
257 * is useless if we never need those arrays, but it costs almost nothing,
258 * and it will save a palloc cycle if we do need them.
259 */
260 erh = (ExpandedRecordHeader *)
261 MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
262 + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
263
264 /* Ensure all header fields are initialized to 0/null */
265 memset(erh, 0, sizeof(ExpandedRecordHeader));
266
267 EOH_init_header(&erh->hdr, &ER_methods, objcxt);
268 erh->er_magic = ER_MAGIC;
269
270 /* Set up dvalues/dnulls, with no valid contents as yet */
271 chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
272 erh->dvalues = (Datum *) chunk;
273 erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
274 erh->nfields = tupdesc->natts;
275
276 /* Fill in composite-type identification info */
277 erh->er_decltypeid = erh->er_typeid = tupdesc->tdtypeid;
278 erh->er_typmod = tupdesc->tdtypmod;
279 erh->er_tupdesc_id = tupdesc_id;
280
281 /*
282 * Copy tupdesc if needed, but we prefer to bump its refcount if possible.
283 * We manage the refcount with a memory context callback rather than
284 * assuming that the CurrentResourceOwner is longer-lived than this
285 * expanded object.
286 */
287 if (tupdesc->tdrefcount >= 0)
288 {
289 /* Register callback to release the refcount */
290 erh->er_mcb.func = ER_mc_callback;
291 erh->er_mcb.arg = (void *) erh;
292 MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
293 &erh->er_mcb);
294
295 /* And save the pointer */
296 erh->er_tupdesc = tupdesc;
297 tupdesc->tdrefcount++;
298 }
299 else
300 {
301 /* Just copy it */
302 oldcxt = MemoryContextSwitchTo(objcxt);
303 erh->er_tupdesc = CreateTupleDescCopy(tupdesc);
304 erh->flags |= ER_FLAG_TUPDESC_ALLOCED;
305 MemoryContextSwitchTo(oldcxt);
306 }
307
308 /*
309 * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the
310 * record remains logically empty.
311 */
312
313 return erh;
314 }
315
316 /*
317 * Build an expanded record of the same rowtype as the given expanded record
318 *
319 * This is faster than either of the above routines because we can bypass
320 * typcache lookup(s).
321 *
322 * The expanded record is initially "empty" --- we do not copy whatever
323 * tuple might be in the source expanded record.
324 *
325 * The expanded object will be a child of parentcontext.
326 */
327 ExpandedRecordHeader *
make_expanded_record_from_exprecord(ExpandedRecordHeader * olderh,MemoryContext parentcontext)328 make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh,
329 MemoryContext parentcontext)
330 {
331 ExpandedRecordHeader *erh;
332 TupleDesc tupdesc = expanded_record_get_tupdesc(olderh);
333 MemoryContext objcxt;
334 MemoryContext oldcxt;
335 char *chunk;
336
337 /*
338 * Allocate private context for expanded object. We use a regular-size
339 * context, not a small one, to improve the odds that we can fit a tupdesc
340 * into it without needing an extra malloc block.
341 */
342 objcxt = AllocSetContextCreate(parentcontext,
343 "expanded record",
344 ALLOCSET_DEFAULT_SIZES);
345
346 /*
347 * Since we already know the number of fields in the tupdesc, we can
348 * allocate the dvalues/dnulls arrays along with the record header. This
349 * is useless if we never need those arrays, but it costs almost nothing,
350 * and it will save a palloc cycle if we do need them.
351 */
352 erh = (ExpandedRecordHeader *)
353 MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
354 + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
355
356 /* Ensure all header fields are initialized to 0/null */
357 memset(erh, 0, sizeof(ExpandedRecordHeader));
358
359 EOH_init_header(&erh->hdr, &ER_methods, objcxt);
360 erh->er_magic = ER_MAGIC;
361
362 /* Set up dvalues/dnulls, with no valid contents as yet */
363 chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
364 erh->dvalues = (Datum *) chunk;
365 erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
366 erh->nfields = tupdesc->natts;
367
368 /* Fill in composite-type identification info */
369 erh->er_decltypeid = olderh->er_decltypeid;
370 erh->er_typeid = olderh->er_typeid;
371 erh->er_typmod = olderh->er_typmod;
372 erh->er_tupdesc_id = olderh->er_tupdesc_id;
373
374 /* The only flag bit that transfers over is IS_DOMAIN */
375 erh->flags = olderh->flags & ER_FLAG_IS_DOMAIN;
376
377 /*
378 * Copy tupdesc if needed, but we prefer to bump its refcount if possible.
379 * We manage the refcount with a memory context callback rather than
380 * assuming that the CurrentResourceOwner is longer-lived than this
381 * expanded object.
382 */
383 if (tupdesc->tdrefcount >= 0)
384 {
385 /* Register callback to release the refcount */
386 erh->er_mcb.func = ER_mc_callback;
387 erh->er_mcb.arg = (void *) erh;
388 MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
389 &erh->er_mcb);
390
391 /* And save the pointer */
392 erh->er_tupdesc = tupdesc;
393 tupdesc->tdrefcount++;
394 }
395 else if (olderh->flags & ER_FLAG_TUPDESC_ALLOCED)
396 {
397 /* We need to make our own copy of the tupdesc */
398 oldcxt = MemoryContextSwitchTo(objcxt);
399 erh->er_tupdesc = CreateTupleDescCopy(tupdesc);
400 erh->flags |= ER_FLAG_TUPDESC_ALLOCED;
401 MemoryContextSwitchTo(oldcxt);
402 }
403 else
404 {
405 /*
406 * Assume the tupdesc will outlive this expanded object, just like
407 * we're assuming it will outlive the source object.
408 */
409 erh->er_tupdesc = tupdesc;
410 }
411
412 /*
413 * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the
414 * record remains logically empty.
415 */
416
417 return erh;
418 }
419
420 /*
421 * Insert given tuple as the value of the expanded record
422 *
423 * It is caller's responsibility that the tuple matches the record's
424 * previously-assigned rowtype. (However domain constraints, if any,
425 * will be checked here.)
426 *
427 * The tuple is physically copied into the expanded record's local storage
428 * if "copy" is true, otherwise it's caller's responsibility that the tuple
429 * will live as long as the expanded record does.
430 *
431 * Out-of-line field values in the tuple are automatically inlined if
432 * "expand_external" is true, otherwise not. (The combination copy = false,
433 * expand_external = true is not sensible and not supported.)
434 *
435 * Alternatively, tuple can be NULL, in which case we just set the expanded
436 * record to be empty.
437 */
438 void
expanded_record_set_tuple(ExpandedRecordHeader * erh,HeapTuple tuple,bool copy,bool expand_external)439 expanded_record_set_tuple(ExpandedRecordHeader *erh,
440 HeapTuple tuple,
441 bool copy,
442 bool expand_external)
443 {
444 int oldflags;
445 HeapTuple oldtuple;
446 char *oldfstartptr;
447 char *oldfendptr;
448 int newflags;
449 HeapTuple newtuple;
450 MemoryContext oldcxt;
451
452 /* Shouldn't ever be trying to assign new data to a dummy header */
453 Assert(!(erh->flags & ER_FLAG_IS_DUMMY));
454
455 /*
456 * Before performing the assignment, see if result will satisfy domain.
457 */
458 if (erh->flags & ER_FLAG_IS_DOMAIN)
459 check_domain_for_new_tuple(erh, tuple);
460
461 /*
462 * If we need to get rid of out-of-line field values, do so, using the
463 * short-term context to avoid leaking whatever cruft the toast fetch
464 * might generate.
465 */
466 if (expand_external && tuple)
467 {
468 /* Assert caller didn't ask for unsupported case */
469 Assert(copy);
470 if (HeapTupleHasExternal(tuple))
471 {
472 oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
473 tuple = toast_flatten_tuple(tuple, erh->er_tupdesc);
474 MemoryContextSwitchTo(oldcxt);
475 }
476 else
477 expand_external = false; /* need not clean up below */
478 }
479
480 /*
481 * Initialize new flags, keeping only non-data status bits.
482 */
483 oldflags = erh->flags;
484 newflags = oldflags & ER_FLAGS_NON_DATA;
485
486 /*
487 * Copy tuple into local storage if needed. We must be sure this succeeds
488 * before we start to modify the expanded record's state.
489 */
490 if (copy && tuple)
491 {
492 oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
493 newtuple = heap_copytuple(tuple);
494 newflags |= ER_FLAG_FVALUE_ALLOCED;
495 MemoryContextSwitchTo(oldcxt);
496
497 /* We can now flush anything that detoasting might have leaked. */
498 if (expand_external)
499 MemoryContextReset(erh->er_short_term_cxt);
500 }
501 else
502 newtuple = tuple;
503
504 /* Make copies of fields we're about to overwrite */
505 oldtuple = erh->fvalue;
506 oldfstartptr = erh->fstartptr;
507 oldfendptr = erh->fendptr;
508
509 /*
510 * It's now safe to update the expanded record's state.
511 */
512 if (newtuple)
513 {
514 /* Save flat representation */
515 erh->fvalue = newtuple;
516 erh->fstartptr = (char *) newtuple->t_data;
517 erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len;
518 newflags |= ER_FLAG_FVALUE_VALID;
519
520 /* Remember if we have any out-of-line field values */
521 if (HeapTupleHasExternal(newtuple))
522 newflags |= ER_FLAG_HAVE_EXTERNAL;
523 }
524 else
525 {
526 erh->fvalue = NULL;
527 erh->fstartptr = erh->fendptr = NULL;
528 }
529
530 erh->flags = newflags;
531
532 /* Reset flat-size info; we don't bother to make it valid now */
533 erh->flat_size = 0;
534
535 /*
536 * Now, release any storage belonging to old field values. It's safe to
537 * do this because ER_FLAG_DVALUES_VALID is no longer set in erh->flags;
538 * even if we fail partway through, the record is valid, and at worst
539 * we've failed to reclaim some space.
540 */
541 if (oldflags & ER_FLAG_DVALUES_ALLOCED)
542 {
543 TupleDesc tupdesc = erh->er_tupdesc;
544 int i;
545
546 for (i = 0; i < erh->nfields; i++)
547 {
548 if (!erh->dnulls[i] &&
549 !(TupleDescAttr(tupdesc, i)->attbyval))
550 {
551 char *oldValue = (char *) DatumGetPointer(erh->dvalues[i]);
552
553 if (oldValue < oldfstartptr || oldValue >= oldfendptr)
554 pfree(oldValue);
555 }
556 }
557 }
558
559 /* Likewise free the old tuple, if it was locally allocated */
560 if (oldflags & ER_FLAG_FVALUE_ALLOCED)
561 heap_freetuple(oldtuple);
562
563 /* We won't make a new deconstructed representation until/unless needed */
564 }
565
566 /*
567 * make_expanded_record_from_datum: build expanded record from composite Datum
568 *
569 * This combines the functions of make_expanded_record_from_typeid and
570 * expanded_record_set_tuple. However, we do not force a lookup of the
571 * tupdesc immediately, reasoning that it might never be needed.
572 *
573 * The expanded object will be a child of parentcontext.
574 *
575 * Note: a composite datum cannot self-identify as being of a domain type,
576 * so we need not consider domain cases here.
577 */
578 Datum
make_expanded_record_from_datum(Datum recorddatum,MemoryContext parentcontext)579 make_expanded_record_from_datum(Datum recorddatum, MemoryContext parentcontext)
580 {
581 ExpandedRecordHeader *erh;
582 HeapTupleHeader tuphdr;
583 HeapTupleData tmptup;
584 HeapTuple newtuple;
585 MemoryContext objcxt;
586 MemoryContext oldcxt;
587
588 /*
589 * Allocate private context for expanded object. We use a regular-size
590 * context, not a small one, to improve the odds that we can fit a tupdesc
591 * into it without needing an extra malloc block.
592 */
593 objcxt = AllocSetContextCreate(parentcontext,
594 "expanded record",
595 ALLOCSET_DEFAULT_SIZES);
596
597 /* Set up expanded record header, initializing fields to 0/null */
598 erh = (ExpandedRecordHeader *)
599 MemoryContextAllocZero(objcxt, sizeof(ExpandedRecordHeader));
600
601 EOH_init_header(&erh->hdr, &ER_methods, objcxt);
602 erh->er_magic = ER_MAGIC;
603
604 /*
605 * Detoast and copy source record into private context, as a HeapTuple.
606 * (If we actually have to detoast the source, we'll leak some memory in
607 * the caller's context, but it doesn't seem worth worrying about.)
608 */
609 tuphdr = DatumGetHeapTupleHeader(recorddatum);
610
611 tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
612 ItemPointerSetInvalid(&(tmptup.t_self));
613 tmptup.t_tableOid = InvalidOid;
614 tmptup.t_data = tuphdr;
615
616 oldcxt = MemoryContextSwitchTo(objcxt);
617 newtuple = heap_copytuple(&tmptup);
618 erh->flags |= ER_FLAG_FVALUE_ALLOCED;
619 MemoryContextSwitchTo(oldcxt);
620
621 /* Fill in composite-type identification info */
622 erh->er_decltypeid = erh->er_typeid = HeapTupleHeaderGetTypeId(tuphdr);
623 erh->er_typmod = HeapTupleHeaderGetTypMod(tuphdr);
624
625 /* remember we have a flat representation */
626 erh->fvalue = newtuple;
627 erh->fstartptr = (char *) newtuple->t_data;
628 erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len;
629 erh->flags |= ER_FLAG_FVALUE_VALID;
630
631 /* Shouldn't need to set ER_FLAG_HAVE_EXTERNAL */
632 Assert(!HeapTupleHeaderHasExternal(tuphdr));
633
634 /*
635 * We won't look up the tupdesc till we have to, nor make a deconstructed
636 * representation. We don't have enough info to fill flat_size and
637 * friends, either.
638 */
639
640 /* return a R/W pointer to the expanded record */
641 return EOHPGetRWDatum(&erh->hdr);
642 }
643
644 /*
645 * get_flat_size method for expanded records
646 *
647 * Note: call this in a reasonably short-lived memory context, in case of
648 * memory leaks from activities such as detoasting.
649 */
650 static Size
ER_get_flat_size(ExpandedObjectHeader * eohptr)651 ER_get_flat_size(ExpandedObjectHeader *eohptr)
652 {
653 ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr;
654 TupleDesc tupdesc;
655 Size len;
656 Size data_len;
657 int hoff;
658 bool hasnull;
659 int i;
660
661 Assert(erh->er_magic == ER_MAGIC);
662
663 /*
664 * The flat representation has to be a valid composite datum. Make sure
665 * that we have a registered, not anonymous, RECORD type.
666 */
667 if (erh->er_typeid == RECORDOID &&
668 erh->er_typmod < 0)
669 {
670 tupdesc = expanded_record_get_tupdesc(erh);
671 assign_record_type_typmod(tupdesc);
672 erh->er_typmod = tupdesc->tdtypmod;
673 }
674
675 /*
676 * If we have a valid flattened value without out-of-line fields, we can
677 * just use it as-is.
678 */
679 if (erh->flags & ER_FLAG_FVALUE_VALID &&
680 !(erh->flags & ER_FLAG_HAVE_EXTERNAL))
681 return erh->fvalue->t_len;
682
683 /* If we have a cached size value, believe that */
684 if (erh->flat_size)
685 return erh->flat_size;
686
687 /* If we haven't yet deconstructed the tuple, do that */
688 if (!(erh->flags & ER_FLAG_DVALUES_VALID))
689 deconstruct_expanded_record(erh);
690
691 /* Tuple descriptor must be valid by now */
692 tupdesc = erh->er_tupdesc;
693
694 /*
695 * Composite datums mustn't contain any out-of-line values.
696 */
697 if (erh->flags & ER_FLAG_HAVE_EXTERNAL)
698 {
699 for (i = 0; i < erh->nfields; i++)
700 {
701 Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
702
703 if (!erh->dnulls[i] &&
704 !attr->attbyval && attr->attlen == -1 &&
705 VARATT_IS_EXTERNAL(DatumGetPointer(erh->dvalues[i])))
706 {
707 /*
708 * expanded_record_set_field_internal can do the actual work
709 * of detoasting. It needn't recheck domain constraints.
710 */
711 expanded_record_set_field_internal(erh, i + 1,
712 erh->dvalues[i], false,
713 true,
714 false);
715 }
716 }
717
718 /*
719 * We have now removed all external field values, so we can clear the
720 * flag about them. This won't cause ER_flatten_into() to mistakenly
721 * take the fast path, since expanded_record_set_field() will have
722 * cleared ER_FLAG_FVALUE_VALID.
723 */
724 erh->flags &= ~ER_FLAG_HAVE_EXTERNAL;
725 }
726
727 /* Test if we currently have any null values */
728 hasnull = false;
729 for (i = 0; i < erh->nfields; i++)
730 {
731 if (erh->dnulls[i])
732 {
733 hasnull = true;
734 break;
735 }
736 }
737
738 /* Determine total space needed */
739 len = offsetof(HeapTupleHeaderData, t_bits);
740
741 if (hasnull)
742 len += BITMAPLEN(tupdesc->natts);
743
744 if (tupdesc->tdhasoid)
745 len += sizeof(Oid);
746
747 hoff = len = MAXALIGN(len); /* align user data safely */
748
749 data_len = heap_compute_data_size(tupdesc, erh->dvalues, erh->dnulls);
750
751 len += data_len;
752
753 /* Cache for next time */
754 erh->flat_size = len;
755 erh->data_len = data_len;
756 erh->hoff = hoff;
757 erh->hasnull = hasnull;
758
759 return len;
760 }
761
762 /*
763 * flatten_into method for expanded records
764 */
765 static void
ER_flatten_into(ExpandedObjectHeader * eohptr,void * result,Size allocated_size)766 ER_flatten_into(ExpandedObjectHeader *eohptr,
767 void *result, Size allocated_size)
768 {
769 ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr;
770 HeapTupleHeader tuphdr = (HeapTupleHeader) result;
771 TupleDesc tupdesc;
772
773 Assert(erh->er_magic == ER_MAGIC);
774
775 /* Easy if we have a valid flattened value without out-of-line fields */
776 if (erh->flags & ER_FLAG_FVALUE_VALID &&
777 !(erh->flags & ER_FLAG_HAVE_EXTERNAL))
778 {
779 Assert(allocated_size == erh->fvalue->t_len);
780 memcpy(tuphdr, erh->fvalue->t_data, allocated_size);
781 /* The original flattened value might not have datum header fields */
782 HeapTupleHeaderSetDatumLength(tuphdr, allocated_size);
783 HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid);
784 HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod);
785 return;
786 }
787
788 /* Else allocation should match previous get_flat_size result */
789 Assert(allocated_size == erh->flat_size);
790
791 /* We'll need the tuple descriptor */
792 tupdesc = expanded_record_get_tupdesc(erh);
793
794 /* We must ensure that any pad space is zero-filled */
795 memset(tuphdr, 0, allocated_size);
796
797 /* Set up header fields of composite Datum */
798 HeapTupleHeaderSetDatumLength(tuphdr, allocated_size);
799 HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid);
800 HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod);
801 /* We also make sure that t_ctid is invalid unless explicitly set */
802 ItemPointerSetInvalid(&(tuphdr->t_ctid));
803
804 HeapTupleHeaderSetNatts(tuphdr, tupdesc->natts);
805 tuphdr->t_hoff = erh->hoff;
806
807 if (tupdesc->tdhasoid) /* else leave infomask = 0 */
808 tuphdr->t_infomask = HEAP_HASOID;
809
810 /* And fill the data area from dvalues/dnulls */
811 heap_fill_tuple(tupdesc,
812 erh->dvalues,
813 erh->dnulls,
814 (char *) tuphdr + erh->hoff,
815 erh->data_len,
816 &tuphdr->t_infomask,
817 (erh->hasnull ? tuphdr->t_bits : NULL));
818 }
819
820 /*
821 * Look up the tupdesc for the expanded record's actual type
822 *
823 * Note: code internal to this module is allowed to just fetch
824 * erh->er_tupdesc if ER_FLAG_DVALUES_VALID is set; otherwise it should call
825 * expanded_record_get_tupdesc. This function is the out-of-line portion
826 * of expanded_record_get_tupdesc.
827 */
828 TupleDesc
expanded_record_fetch_tupdesc(ExpandedRecordHeader * erh)829 expanded_record_fetch_tupdesc(ExpandedRecordHeader *erh)
830 {
831 TupleDesc tupdesc;
832
833 /* Easy if we already have it (but caller should have checked already) */
834 if (erh->er_tupdesc)
835 return erh->er_tupdesc;
836
837 /* Lookup the composite type's tupdesc using the typcache */
838 tupdesc = lookup_rowtype_tupdesc(erh->er_typeid, erh->er_typmod);
839
840 /*
841 * If it's a refcounted tupdesc rather than a statically allocated one, we
842 * want to manage the refcount with a memory context callback rather than
843 * assuming that the CurrentResourceOwner is longer-lived than this
844 * expanded object.
845 */
846 if (tupdesc->tdrefcount >= 0)
847 {
848 /* Register callback if we didn't already */
849 if (erh->er_mcb.arg == NULL)
850 {
851 erh->er_mcb.func = ER_mc_callback;
852 erh->er_mcb.arg = (void *) erh;
853 MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
854 &erh->er_mcb);
855 }
856
857 /* Remember our own pointer */
858 erh->er_tupdesc = tupdesc;
859 tupdesc->tdrefcount++;
860
861 /* Release the pin lookup_rowtype_tupdesc acquired */
862 DecrTupleDescRefCount(tupdesc);
863 }
864 else
865 {
866 /* Just remember the pointer */
867 erh->er_tupdesc = tupdesc;
868 }
869
870 /* In either case, fetch the process-global ID for this tupdesc */
871 erh->er_tupdesc_id = assign_record_type_identifier(tupdesc->tdtypeid,
872 tupdesc->tdtypmod);
873
874 return tupdesc;
875 }
876
877 /*
878 * Get a HeapTuple representing the current value of the expanded record
879 *
880 * If valid, the originally stored tuple is returned, so caller must not
881 * scribble on it. Otherwise, we return a HeapTuple created in the current
882 * memory context. In either case, no attempt has been made to inline
883 * out-of-line toasted values, so the tuple isn't usable as a composite
884 * datum.
885 *
886 * Returns NULL if expanded record is empty.
887 */
888 HeapTuple
expanded_record_get_tuple(ExpandedRecordHeader * erh)889 expanded_record_get_tuple(ExpandedRecordHeader *erh)
890 {
891 /* Easy case if we still have original tuple */
892 if (erh->flags & ER_FLAG_FVALUE_VALID)
893 return erh->fvalue;
894
895 /* Else just build a tuple from datums */
896 if (erh->flags & ER_FLAG_DVALUES_VALID)
897 return heap_form_tuple(erh->er_tupdesc, erh->dvalues, erh->dnulls);
898
899 /* Expanded record is empty */
900 return NULL;
901 }
902
903 /*
904 * Memory context reset callback for cleaning up external resources
905 */
906 static void
ER_mc_callback(void * arg)907 ER_mc_callback(void *arg)
908 {
909 ExpandedRecordHeader *erh = (ExpandedRecordHeader *) arg;
910 TupleDesc tupdesc = erh->er_tupdesc;
911
912 /* Release our privately-managed tupdesc refcount, if any */
913 if (tupdesc)
914 {
915 erh->er_tupdesc = NULL; /* just for luck */
916 if (tupdesc->tdrefcount > 0)
917 {
918 if (--tupdesc->tdrefcount == 0)
919 FreeTupleDesc(tupdesc);
920 }
921 }
922 }
923
924 /*
925 * DatumGetExpandedRecord: get a writable expanded record from an input argument
926 *
927 * Caution: if the input is a read/write pointer, this returns the input
928 * argument; so callers must be sure that their changes are "safe", that is
929 * they cannot leave the record in a corrupt state.
930 */
931 ExpandedRecordHeader *
DatumGetExpandedRecord(Datum d)932 DatumGetExpandedRecord(Datum d)
933 {
934 /* If it's a writable expanded record already, just return it */
935 if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
936 {
937 ExpandedRecordHeader *erh = (ExpandedRecordHeader *) DatumGetEOHP(d);
938
939 Assert(erh->er_magic == ER_MAGIC);
940 return erh;
941 }
942
943 /* Else expand the hard way */
944 d = make_expanded_record_from_datum(d, CurrentMemoryContext);
945 return (ExpandedRecordHeader *) DatumGetEOHP(d);
946 }
947
948 /*
949 * Create the Datum/isnull representation of an expanded record object
950 * if we didn't do so already. After calling this, it's OK to read the
951 * dvalues/dnulls arrays directly, rather than going through get_field.
952 *
953 * Note that if the object is currently empty ("null"), this will change
954 * it to represent a row of nulls.
955 */
956 void
deconstruct_expanded_record(ExpandedRecordHeader * erh)957 deconstruct_expanded_record(ExpandedRecordHeader *erh)
958 {
959 TupleDesc tupdesc;
960 Datum *dvalues;
961 bool *dnulls;
962 int nfields;
963
964 if (erh->flags & ER_FLAG_DVALUES_VALID)
965 return; /* already valid, nothing to do */
966
967 /* We'll need the tuple descriptor */
968 tupdesc = expanded_record_get_tupdesc(erh);
969
970 /*
971 * Allocate arrays in private context, if we don't have them already. We
972 * don't expect to see a change in nfields here, so while we cope if it
973 * happens, we don't bother avoiding a leak of the old arrays (which might
974 * not be separately palloc'd, anyway).
975 */
976 nfields = tupdesc->natts;
977 if (erh->dvalues == NULL || erh->nfields != nfields)
978 {
979 char *chunk;
980
981 /*
982 * To save a palloc cycle, we allocate both the Datum and isnull
983 * arrays in one palloc chunk.
984 */
985 chunk = MemoryContextAlloc(erh->hdr.eoh_context,
986 nfields * (sizeof(Datum) + sizeof(bool)));
987 dvalues = (Datum *) chunk;
988 dnulls = (bool *) (chunk + nfields * sizeof(Datum));
989 erh->dvalues = dvalues;
990 erh->dnulls = dnulls;
991 erh->nfields = nfields;
992 }
993 else
994 {
995 dvalues = erh->dvalues;
996 dnulls = erh->dnulls;
997 }
998
999 if (erh->flags & ER_FLAG_FVALUE_VALID)
1000 {
1001 /* Deconstruct tuple */
1002 heap_deform_tuple(erh->fvalue, tupdesc, dvalues, dnulls);
1003 }
1004 else
1005 {
1006 /* If record was empty, instantiate it as a row of nulls */
1007 memset(dvalues, 0, nfields * sizeof(Datum));
1008 memset(dnulls, true, nfields * sizeof(bool));
1009 }
1010
1011 /* Mark the dvalues as valid */
1012 erh->flags |= ER_FLAG_DVALUES_VALID;
1013 }
1014
1015 /*
1016 * Look up a record field by name
1017 *
1018 * If there is a field named "fieldname", fill in the contents of finfo
1019 * and return "true". Else return "false" without changing *finfo.
1020 */
1021 bool
expanded_record_lookup_field(ExpandedRecordHeader * erh,const char * fieldname,ExpandedRecordFieldInfo * finfo)1022 expanded_record_lookup_field(ExpandedRecordHeader *erh, const char *fieldname,
1023 ExpandedRecordFieldInfo *finfo)
1024 {
1025 TupleDesc tupdesc;
1026 int fno;
1027 Form_pg_attribute attr;
1028
1029 tupdesc = expanded_record_get_tupdesc(erh);
1030
1031 /* First, check user-defined attributes */
1032 for (fno = 0; fno < tupdesc->natts; fno++)
1033 {
1034 attr = TupleDescAttr(tupdesc, fno);
1035 if (namestrcmp(&attr->attname, fieldname) == 0 &&
1036 !attr->attisdropped)
1037 {
1038 finfo->fnumber = attr->attnum;
1039 finfo->ftypeid = attr->atttypid;
1040 finfo->ftypmod = attr->atttypmod;
1041 finfo->fcollation = attr->attcollation;
1042 return true;
1043 }
1044 }
1045
1046 /* How about system attributes? */
1047 attr = SystemAttributeByName(fieldname, tupdesc->tdhasoid);
1048 if (attr != NULL)
1049 {
1050 finfo->fnumber = attr->attnum;
1051 finfo->ftypeid = attr->atttypid;
1052 finfo->ftypmod = attr->atttypmod;
1053 finfo->fcollation = attr->attcollation;
1054 return true;
1055 }
1056
1057 return false;
1058 }
1059
1060 /*
1061 * Fetch value of record field
1062 *
1063 * expanded_record_get_field is the frontend for this; it handles the
1064 * easy inline-able cases.
1065 */
1066 Datum
expanded_record_fetch_field(ExpandedRecordHeader * erh,int fnumber,bool * isnull)1067 expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber,
1068 bool *isnull)
1069 {
1070 if (fnumber > 0)
1071 {
1072 /* Empty record has null fields */
1073 if (ExpandedRecordIsEmpty(erh))
1074 {
1075 *isnull = true;
1076 return (Datum) 0;
1077 }
1078 /* Make sure we have deconstructed form */
1079 deconstruct_expanded_record(erh);
1080 /* Out-of-range field number reads as null */
1081 if (unlikely(fnumber > erh->nfields))
1082 {
1083 *isnull = true;
1084 return (Datum) 0;
1085 }
1086 *isnull = erh->dnulls[fnumber - 1];
1087 return erh->dvalues[fnumber - 1];
1088 }
1089 else
1090 {
1091 /* System columns read as null if we haven't got flat tuple */
1092 if (erh->fvalue == NULL)
1093 {
1094 *isnull = true;
1095 return (Datum) 0;
1096 }
1097 /* heap_getsysattr doesn't actually use tupdesc, so just pass null */
1098 return heap_getsysattr(erh->fvalue, fnumber, NULL, isnull);
1099 }
1100 }
1101
1102 /*
1103 * Set value of record field
1104 *
1105 * If the expanded record is of domain type, the assignment will be rejected
1106 * (without changing the record's state) if the domain's constraints would
1107 * be violated.
1108 *
1109 * If expand_external is true and newValue is an out-of-line value, we'll
1110 * forcibly detoast it so that the record does not depend on external storage.
1111 *
1112 * Internal callers can pass check_constraints = false to skip application
1113 * of domain constraints. External callers should never do that.
1114 */
1115 void
expanded_record_set_field_internal(ExpandedRecordHeader * erh,int fnumber,Datum newValue,bool isnull,bool expand_external,bool check_constraints)1116 expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
1117 Datum newValue, bool isnull,
1118 bool expand_external,
1119 bool check_constraints)
1120 {
1121 TupleDesc tupdesc;
1122 Form_pg_attribute attr;
1123 Datum *dvalues;
1124 bool *dnulls;
1125 char *oldValue;
1126
1127 /*
1128 * Shouldn't ever be trying to assign new data to a dummy header, except
1129 * in the case of an internal call for field inlining.
1130 */
1131 Assert(!(erh->flags & ER_FLAG_IS_DUMMY) || !check_constraints);
1132
1133 /* Before performing the assignment, see if result will satisfy domain */
1134 if ((erh->flags & ER_FLAG_IS_DOMAIN) && check_constraints)
1135 check_domain_for_new_field(erh, fnumber, newValue, isnull);
1136
1137 /* If we haven't yet deconstructed the tuple, do that */
1138 if (!(erh->flags & ER_FLAG_DVALUES_VALID))
1139 deconstruct_expanded_record(erh);
1140
1141 /* Tuple descriptor must be valid by now */
1142 tupdesc = erh->er_tupdesc;
1143 Assert(erh->nfields == tupdesc->natts);
1144
1145 /* Caller error if fnumber is system column or nonexistent column */
1146 if (unlikely(fnumber <= 0 || fnumber > erh->nfields))
1147 elog(ERROR, "cannot assign to field %d of expanded record", fnumber);
1148
1149 /*
1150 * Copy new field value into record's context, and deal with detoasting,
1151 * if needed.
1152 */
1153 attr = TupleDescAttr(tupdesc, fnumber - 1);
1154 if (!isnull && !attr->attbyval)
1155 {
1156 MemoryContext oldcxt;
1157
1158 /* If requested, detoast any external value */
1159 if (expand_external)
1160 {
1161 if (attr->attlen == -1 &&
1162 VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
1163 {
1164 /* Detoasting should be done in short-lived context. */
1165 oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
1166 newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue)));
1167 MemoryContextSwitchTo(oldcxt);
1168 }
1169 else
1170 expand_external = false; /* need not clean up below */
1171 }
1172
1173 /* Copy value into record's context */
1174 oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
1175 newValue = datumCopy(newValue, false, attr->attlen);
1176 MemoryContextSwitchTo(oldcxt);
1177
1178 /* We can now flush anything that detoasting might have leaked */
1179 if (expand_external)
1180 MemoryContextReset(erh->er_short_term_cxt);
1181
1182 /* Remember that we have field(s) that may need to be pfree'd */
1183 erh->flags |= ER_FLAG_DVALUES_ALLOCED;
1184
1185 /*
1186 * While we're here, note whether it's an external toasted value,
1187 * because that could mean we need to inline it later. (Think not to
1188 * merge this into the previous expand_external logic: datumCopy could
1189 * by itself have made the value non-external.)
1190 */
1191 if (attr->attlen == -1 &&
1192 VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
1193 erh->flags |= ER_FLAG_HAVE_EXTERNAL;
1194 }
1195
1196 /*
1197 * We're ready to make irreversible changes.
1198 */
1199 dvalues = erh->dvalues;
1200 dnulls = erh->dnulls;
1201
1202 /* Flattened value will no longer represent record accurately */
1203 erh->flags &= ~ER_FLAG_FVALUE_VALID;
1204 /* And we don't know the flattened size either */
1205 erh->flat_size = 0;
1206
1207 /* Grab old field value for pfree'ing, if needed. */
1208 if (!attr->attbyval && !dnulls[fnumber - 1])
1209 oldValue = (char *) DatumGetPointer(dvalues[fnumber - 1]);
1210 else
1211 oldValue = NULL;
1212
1213 /* And finally we can insert the new field. */
1214 dvalues[fnumber - 1] = newValue;
1215 dnulls[fnumber - 1] = isnull;
1216
1217 /*
1218 * Free old field if needed; this keeps repeated field replacements from
1219 * bloating the record's storage. If the pfree somehow fails, it won't
1220 * corrupt the record.
1221 *
1222 * If we're updating a dummy header, we can't risk pfree'ing the old
1223 * value, because most likely the expanded record's main header still has
1224 * a pointer to it. This won't result in any sustained memory leak, since
1225 * whatever we just allocated here is in the short-lived domain check
1226 * context.
1227 */
1228 if (oldValue && !(erh->flags & ER_FLAG_IS_DUMMY))
1229 {
1230 /* Don't try to pfree a part of the original flat record */
1231 if (oldValue < erh->fstartptr || oldValue >= erh->fendptr)
1232 pfree(oldValue);
1233 }
1234 }
1235
1236 /*
1237 * Set all record field(s)
1238 *
1239 * Caller must ensure that the provided datums are of the right types
1240 * to match the record's previously assigned rowtype.
1241 *
1242 * If expand_external is true, we'll forcibly detoast out-of-line field values
1243 * so that the record does not depend on external storage.
1244 *
1245 * Unlike repeated application of expanded_record_set_field(), this does not
1246 * guarantee to leave the expanded record in a non-corrupt state in event
1247 * of an error. Typically it would only be used for initializing a new
1248 * expanded record. Also, because we expect this to be applied at most once
1249 * in the lifespan of an expanded record, we do not worry about any cruft
1250 * that detoasting might leak.
1251 */
1252 void
expanded_record_set_fields(ExpandedRecordHeader * erh,const Datum * newValues,const bool * isnulls,bool expand_external)1253 expanded_record_set_fields(ExpandedRecordHeader *erh,
1254 const Datum *newValues, const bool *isnulls,
1255 bool expand_external)
1256 {
1257 TupleDesc tupdesc;
1258 Datum *dvalues;
1259 bool *dnulls;
1260 int fnumber;
1261 MemoryContext oldcxt;
1262
1263 /* Shouldn't ever be trying to assign new data to a dummy header */
1264 Assert(!(erh->flags & ER_FLAG_IS_DUMMY));
1265
1266 /* If we haven't yet deconstructed the tuple, do that */
1267 if (!(erh->flags & ER_FLAG_DVALUES_VALID))
1268 deconstruct_expanded_record(erh);
1269
1270 /* Tuple descriptor must be valid by now */
1271 tupdesc = erh->er_tupdesc;
1272 Assert(erh->nfields == tupdesc->natts);
1273
1274 /* Flattened value will no longer represent record accurately */
1275 erh->flags &= ~ER_FLAG_FVALUE_VALID;
1276 /* And we don't know the flattened size either */
1277 erh->flat_size = 0;
1278
1279 oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
1280
1281 dvalues = erh->dvalues;
1282 dnulls = erh->dnulls;
1283
1284 for (fnumber = 0; fnumber < erh->nfields; fnumber++)
1285 {
1286 Form_pg_attribute attr = TupleDescAttr(tupdesc, fnumber);
1287 Datum newValue;
1288 bool isnull;
1289
1290 /* Ignore dropped columns */
1291 if (attr->attisdropped)
1292 continue;
1293
1294 newValue = newValues[fnumber];
1295 isnull = isnulls[fnumber];
1296
1297 if (!attr->attbyval)
1298 {
1299 /*
1300 * Copy new field value into record's context, and deal with
1301 * detoasting, if needed.
1302 */
1303 if (!isnull)
1304 {
1305 /* Is it an external toasted value? */
1306 if (attr->attlen == -1 &&
1307 VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
1308 {
1309 if (expand_external)
1310 {
1311 /* Detoast as requested while copying the value */
1312 newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue)));
1313 }
1314 else
1315 {
1316 /* Just copy the value */
1317 newValue = datumCopy(newValue, false, -1);
1318 /* If it's still external, remember that */
1319 if (VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
1320 erh->flags |= ER_FLAG_HAVE_EXTERNAL;
1321 }
1322 }
1323 else
1324 {
1325 /* Not an external value, just copy it */
1326 newValue = datumCopy(newValue, false, attr->attlen);
1327 }
1328
1329 /* Remember that we have field(s) that need to be pfree'd */
1330 erh->flags |= ER_FLAG_DVALUES_ALLOCED;
1331 }
1332
1333 /*
1334 * Free old field value, if any (not likely, since really we ought
1335 * to be inserting into an empty record).
1336 */
1337 if (unlikely(!dnulls[fnumber]))
1338 {
1339 char *oldValue;
1340
1341 oldValue = (char *) DatumGetPointer(dvalues[fnumber]);
1342 /* Don't try to pfree a part of the original flat record */
1343 if (oldValue < erh->fstartptr || oldValue >= erh->fendptr)
1344 pfree(oldValue);
1345 }
1346 }
1347
1348 /* And finally we can insert the new field. */
1349 dvalues[fnumber] = newValue;
1350 dnulls[fnumber] = isnull;
1351 }
1352
1353 /*
1354 * Because we don't guarantee atomicity of set_fields(), we can just leave
1355 * checking of domain constraints to occur as the final step; if it throws
1356 * an error, too bad.
1357 */
1358 if (erh->flags & ER_FLAG_IS_DOMAIN)
1359 {
1360 /* We run domain_check in a short-lived context to limit cruft */
1361 MemoryContextSwitchTo(get_short_term_cxt(erh));
1362
1363 domain_check(ExpandedRecordGetRODatum(erh), false,
1364 erh->er_decltypeid,
1365 &erh->er_domaininfo,
1366 erh->hdr.eoh_context);
1367 }
1368
1369 MemoryContextSwitchTo(oldcxt);
1370 }
1371
1372 /*
1373 * Construct (or reset) working memory context for short-term operations.
1374 *
1375 * This context is used for domain check evaluation and for detoasting.
1376 *
1377 * If we don't have a short-lived memory context, make one; if we have one,
1378 * reset it to get rid of any leftover cruft. (It is a tad annoying to need a
1379 * whole context for this, since it will often go unused --- but it's hard to
1380 * avoid memory leaks otherwise. We can make the context small, at least.)
1381 */
1382 static MemoryContext
get_short_term_cxt(ExpandedRecordHeader * erh)1383 get_short_term_cxt(ExpandedRecordHeader *erh)
1384 {
1385 if (erh->er_short_term_cxt == NULL)
1386 erh->er_short_term_cxt =
1387 AllocSetContextCreate(erh->hdr.eoh_context,
1388 "expanded record short-term context",
1389 ALLOCSET_SMALL_SIZES);
1390 else
1391 MemoryContextReset(erh->er_short_term_cxt);
1392 return erh->er_short_term_cxt;
1393 }
1394
1395 /*
1396 * Construct "dummy header" for checking domain constraints.
1397 *
1398 * Since we don't want to modify the state of the expanded record until
1399 * we've validated the constraints, our approach is to set up a dummy
1400 * record header containing the new field value(s) and then pass that to
1401 * domain_check. We retain the dummy header as part of the expanded
1402 * record's state to save palloc cycles, but reinitialize (most of)
1403 * its contents on each use.
1404 */
1405 static void
build_dummy_expanded_header(ExpandedRecordHeader * main_erh)1406 build_dummy_expanded_header(ExpandedRecordHeader *main_erh)
1407 {
1408 ExpandedRecordHeader *erh;
1409 TupleDesc tupdesc = expanded_record_get_tupdesc(main_erh);
1410
1411 /* Ensure we have a short-lived context */
1412 (void) get_short_term_cxt(main_erh);
1413
1414 /*
1415 * Allocate dummy header on first time through, or in the unlikely event
1416 * that the number of fields changes (in which case we just leak the old
1417 * one). Include space for its field values in the request.
1418 */
1419 erh = main_erh->er_dummy_header;
1420 if (erh == NULL || erh->nfields != tupdesc->natts)
1421 {
1422 char *chunk;
1423
1424 erh = (ExpandedRecordHeader *)
1425 MemoryContextAlloc(main_erh->hdr.eoh_context,
1426 MAXALIGN(sizeof(ExpandedRecordHeader))
1427 + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
1428
1429 /* Ensure all header fields are initialized to 0/null */
1430 memset(erh, 0, sizeof(ExpandedRecordHeader));
1431
1432 /*
1433 * We set up the dummy header with an indication that its memory
1434 * context is the short-lived context. This is so that, if any
1435 * detoasting of out-of-line values happens due to an attempt to
1436 * extract a composite datum from the dummy header, the detoasted
1437 * stuff will end up in the short-lived context and not cause a leak.
1438 * This is cheating a bit on the expanded-object protocol; but since
1439 * we never pass a R/W pointer to the dummy object to any other code,
1440 * nothing else is authorized to delete or transfer ownership of the
1441 * object's context, so it should be safe enough.
1442 */
1443 EOH_init_header(&erh->hdr, &ER_methods, main_erh->er_short_term_cxt);
1444 erh->er_magic = ER_MAGIC;
1445
1446 /* Set up dvalues/dnulls, with no valid contents as yet */
1447 chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
1448 erh->dvalues = (Datum *) chunk;
1449 erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
1450 erh->nfields = tupdesc->natts;
1451
1452 /*
1453 * The fields we just set are assumed to remain constant through
1454 * multiple uses of the dummy header to check domain constraints. All
1455 * other dummy header fields should be explicitly reset below, to
1456 * ensure there's not accidental effects of one check on the next one.
1457 */
1458
1459 main_erh->er_dummy_header = erh;
1460 }
1461
1462 /*
1463 * If anything inquires about the dummy header's declared type, it should
1464 * report the composite base type, not the domain type (since the VALUE in
1465 * a domain check constraint is of the base type not the domain). Hence
1466 * we do not transfer over the IS_DOMAIN flag, nor indeed any of the main
1467 * header's flags, since the dummy header is empty of data at this point.
1468 * But don't forget to mark header as dummy.
1469 */
1470 erh->flags = ER_FLAG_IS_DUMMY;
1471
1472 /* Copy composite-type identification info */
1473 erh->er_decltypeid = erh->er_typeid = main_erh->er_typeid;
1474 erh->er_typmod = main_erh->er_typmod;
1475
1476 /* Dummy header does not need its own tupdesc refcount */
1477 erh->er_tupdesc = tupdesc;
1478 erh->er_tupdesc_id = main_erh->er_tupdesc_id;
1479
1480 /*
1481 * It's tempting to copy over whatever we know about the flat size, but
1482 * there's no point since we're surely about to modify the dummy record's
1483 * field(s). Instead just clear anything left over from a previous usage
1484 * cycle.
1485 */
1486 erh->flat_size = 0;
1487
1488 /* Copy over fvalue if we have it, so that system columns are available */
1489 erh->fvalue = main_erh->fvalue;
1490 erh->fstartptr = main_erh->fstartptr;
1491 erh->fendptr = main_erh->fendptr;
1492 }
1493
1494 /*
1495 * Precheck domain constraints for a set_field operation
1496 */
1497 static pg_noinline void
check_domain_for_new_field(ExpandedRecordHeader * erh,int fnumber,Datum newValue,bool isnull)1498 check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber,
1499 Datum newValue, bool isnull)
1500 {
1501 ExpandedRecordHeader *dummy_erh;
1502 MemoryContext oldcxt;
1503
1504 /* Construct dummy header to contain proposed new field set */
1505 build_dummy_expanded_header(erh);
1506 dummy_erh = erh->er_dummy_header;
1507
1508 /*
1509 * If record isn't empty, just deconstruct it (if needed) and copy over
1510 * the existing field values. If it is empty, just fill fields with nulls
1511 * manually --- don't call deconstruct_expanded_record prematurely.
1512 */
1513 if (!ExpandedRecordIsEmpty(erh))
1514 {
1515 deconstruct_expanded_record(erh);
1516 memcpy(dummy_erh->dvalues, erh->dvalues,
1517 dummy_erh->nfields * sizeof(Datum));
1518 memcpy(dummy_erh->dnulls, erh->dnulls,
1519 dummy_erh->nfields * sizeof(bool));
1520 /* There might be some external values in there... */
1521 dummy_erh->flags |= erh->flags & ER_FLAG_HAVE_EXTERNAL;
1522 }
1523 else
1524 {
1525 memset(dummy_erh->dvalues, 0, dummy_erh->nfields * sizeof(Datum));
1526 memset(dummy_erh->dnulls, true, dummy_erh->nfields * sizeof(bool));
1527 }
1528
1529 /* Either way, we now have valid dvalues */
1530 dummy_erh->flags |= ER_FLAG_DVALUES_VALID;
1531
1532 /* Caller error if fnumber is system column or nonexistent column */
1533 if (unlikely(fnumber <= 0 || fnumber > dummy_erh->nfields))
1534 elog(ERROR, "cannot assign to field %d of expanded record", fnumber);
1535
1536 /* Insert proposed new value into dummy field array */
1537 dummy_erh->dvalues[fnumber - 1] = newValue;
1538 dummy_erh->dnulls[fnumber - 1] = isnull;
1539
1540 /*
1541 * The proposed new value might be external, in which case we'd better set
1542 * the flag for that in dummy_erh. (This matters in case something in the
1543 * domain check expressions tries to extract a flat value from the dummy
1544 * header.)
1545 */
1546 if (!isnull)
1547 {
1548 Form_pg_attribute attr = TupleDescAttr(erh->er_tupdesc, fnumber - 1);
1549
1550 if (!attr->attbyval && attr->attlen == -1 &&
1551 VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
1552 dummy_erh->flags |= ER_FLAG_HAVE_EXTERNAL;
1553 }
1554
1555 /*
1556 * We call domain_check in the short-lived context, so that any cruft
1557 * leaked by expression evaluation can be reclaimed.
1558 */
1559 oldcxt = MemoryContextSwitchTo(erh->er_short_term_cxt);
1560
1561 /*
1562 * And now we can apply the check. Note we use main header's domain cache
1563 * space, so that caching carries across repeated uses.
1564 */
1565 domain_check(ExpandedRecordGetRODatum(dummy_erh), false,
1566 erh->er_decltypeid,
1567 &erh->er_domaininfo,
1568 erh->hdr.eoh_context);
1569
1570 MemoryContextSwitchTo(oldcxt);
1571
1572 /* We might as well clean up cruft immediately. */
1573 MemoryContextReset(erh->er_short_term_cxt);
1574 }
1575
1576 /*
1577 * Precheck domain constraints for a set_tuple operation
1578 */
1579 static pg_noinline void
check_domain_for_new_tuple(ExpandedRecordHeader * erh,HeapTuple tuple)1580 check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple)
1581 {
1582 ExpandedRecordHeader *dummy_erh;
1583 MemoryContext oldcxt;
1584
1585 /* If we're being told to set record to empty, just see if NULL is OK */
1586 if (tuple == NULL)
1587 {
1588 /* We run domain_check in a short-lived context to limit cruft */
1589 oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
1590
1591 domain_check((Datum) 0, true,
1592 erh->er_decltypeid,
1593 &erh->er_domaininfo,
1594 erh->hdr.eoh_context);
1595
1596 MemoryContextSwitchTo(oldcxt);
1597
1598 /* We might as well clean up cruft immediately. */
1599 MemoryContextReset(erh->er_short_term_cxt);
1600
1601 return;
1602 }
1603
1604 /* Construct dummy header to contain replacement tuple */
1605 build_dummy_expanded_header(erh);
1606 dummy_erh = erh->er_dummy_header;
1607
1608 /* Insert tuple, but don't bother to deconstruct its fields for now */
1609 dummy_erh->fvalue = tuple;
1610 dummy_erh->fstartptr = (char *) tuple->t_data;
1611 dummy_erh->fendptr = ((char *) tuple->t_data) + tuple->t_len;
1612 dummy_erh->flags |= ER_FLAG_FVALUE_VALID;
1613
1614 /* Remember if we have any out-of-line field values */
1615 if (HeapTupleHasExternal(tuple))
1616 dummy_erh->flags |= ER_FLAG_HAVE_EXTERNAL;
1617
1618 /*
1619 * We call domain_check in the short-lived context, so that any cruft
1620 * leaked by expression evaluation can be reclaimed.
1621 */
1622 oldcxt = MemoryContextSwitchTo(erh->er_short_term_cxt);
1623
1624 /*
1625 * And now we can apply the check. Note we use main header's domain cache
1626 * space, so that caching carries across repeated uses.
1627 */
1628 domain_check(ExpandedRecordGetRODatum(dummy_erh), false,
1629 erh->er_decltypeid,
1630 &erh->er_domaininfo,
1631 erh->hdr.eoh_context);
1632
1633 MemoryContextSwitchTo(oldcxt);
1634
1635 /* We might as well clean up cruft immediately. */
1636 MemoryContextReset(erh->er_short_term_cxt);
1637 }
1638