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