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