Age Owner Branch data TLA Line data Source code
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-2024, 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 *
2252 tgl@sss.pgh.pa.us 69 :CBC 327 : make_expanded_record_from_typeid(Oid type_id, int32 typmod,
70 : : MemoryContext parentcontext)
71 : : {
72 : : ExpandedRecordHeader *erh;
73 : 327 : int flags = 0;
74 : : TupleDesc tupdesc;
75 : : uint64 tupdesc_id;
76 : : MemoryContext objcxt;
77 : : char *chunk;
78 : :
79 [ + + ]: 327 : 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 : 297 : typentry = lookup_type_cache(type_id,
88 : : TYPECACHE_TUPDESC |
89 : : TYPECACHE_DOMAIN_BASE_INFO);
90 [ + + ]: 297 : if (typentry->typtype == TYPTYPE_DOMAIN)
91 : : {
92 : 35 : flags |= ER_FLAG_IS_DOMAIN;
93 : 35 : typentry = lookup_type_cache(typentry->domainBaseType,
94 : : TYPECACHE_TUPDESC);
95 : : }
96 [ - + ]: 297 : if (typentry->tupDesc == NULL)
2252 tgl@sss.pgh.pa.us 97 [ # # ]:UBC 0 : ereport(ERROR,
98 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
99 : : errmsg("type %s is not composite",
100 : : format_type_be(type_id))));
2252 tgl@sss.pgh.pa.us 101 :CBC 297 : tupdesc = typentry->tupDesc;
102 : 297 : tupdesc_id = typentry->tupDesc_identifier;
103 : : }
104 : : else
105 : : {
106 : : /*
107 : : * For RECORD types, get the tupdesc and identifier from typcache.
108 : : */
109 : 30 : tupdesc = lookup_rowtype_tupdesc(type_id, typmod);
110 : 30 : 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 : 327 : 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 : 327 : MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
132 : 327 : + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
133 : :
134 : : /* Ensure all header fields are initialized to 0/null */
135 : 327 : memset(erh, 0, sizeof(ExpandedRecordHeader));
136 : :
137 : 327 : EOH_init_header(&erh->hdr, &ER_methods, objcxt);
138 : 327 : erh->er_magic = ER_MAGIC;
139 : :
140 : : /* Set up dvalues/dnulls, with no valid contents as yet */
141 : 327 : chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
142 : 327 : erh->dvalues = (Datum *) chunk;
143 : 327 : erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
144 : 327 : erh->nfields = tupdesc->natts;
145 : :
146 : : /* Fill in composite-type identification info */
147 : 327 : erh->er_decltypeid = type_id;
148 : 327 : erh->er_typeid = tupdesc->tdtypeid;
149 : 327 : erh->er_typmod = tupdesc->tdtypmod;
150 : 327 : erh->er_tupdesc_id = tupdesc_id;
151 : :
152 : 327 : 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 [ + - ]: 327 : if (tupdesc->tdrefcount >= 0)
161 : : {
162 : : /* Register callback to release the refcount */
163 : 327 : erh->er_mcb.func = ER_mc_callback;
164 : 327 : erh->er_mcb.arg = (void *) erh;
165 : 327 : MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
166 : : &erh->er_mcb);
167 : :
168 : : /* And save the pointer */
169 : 327 : erh->er_tupdesc = tupdesc;
170 : 327 : tupdesc->tdrefcount++;
171 : :
172 : : /* If we called lookup_rowtype_tupdesc, release the pin it took */
173 [ + + ]: 327 : if (type_id == RECORDOID)
851 174 [ + - ]: 30 : ReleaseTupleDesc(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 : : */
2252 tgl@sss.pgh.pa.us 182 :UBC 0 : 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 : :
2252 tgl@sss.pgh.pa.us 190 :CBC 327 : 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 *
205 : 10247 : 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 [ + + ]: 10247 : 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 : 7537 : typentry = lookup_type_cache(tupdesc->tdtypeid, TYPECACHE_TUPDESC);
228 [ - + ]: 7537 : if (typentry->tupDesc == NULL)
2252 tgl@sss.pgh.pa.us 229 [ # # ]:UBC 0 : ereport(ERROR,
230 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
231 : : errmsg("type %s is not composite",
232 : : format_type_be(tupdesc->tdtypeid))));
2252 tgl@sss.pgh.pa.us 233 :CBC 7537 : tupdesc = typentry->tupDesc;
234 : 7537 : 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 : 2710 : 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 : 10247 : 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 : 10247 : MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
263 : 10247 : + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
264 : :
265 : : /* Ensure all header fields are initialized to 0/null */
266 : 10247 : memset(erh, 0, sizeof(ExpandedRecordHeader));
267 : :
268 : 10247 : EOH_init_header(&erh->hdr, &ER_methods, objcxt);
269 : 10247 : erh->er_magic = ER_MAGIC;
270 : :
271 : : /* Set up dvalues/dnulls, with no valid contents as yet */
272 : 10247 : chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
273 : 10247 : erh->dvalues = (Datum *) chunk;
274 : 10247 : erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
275 : 10247 : erh->nfields = tupdesc->natts;
276 : :
277 : : /* Fill in composite-type identification info */
278 : 10247 : erh->er_decltypeid = erh->er_typeid = tupdesc->tdtypeid;
279 : 10247 : erh->er_typmod = tupdesc->tdtypmod;
280 : 10247 : 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 [ + + ]: 10247 : if (tupdesc->tdrefcount >= 0)
289 : : {
290 : : /* Register callback to release the refcount */
291 : 7537 : erh->er_mcb.func = ER_mc_callback;
292 : 7537 : erh->er_mcb.arg = (void *) erh;
293 : 7537 : MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
294 : : &erh->er_mcb);
295 : :
296 : : /* And save the pointer */
297 : 7537 : erh->er_tupdesc = tupdesc;
298 : 7537 : tupdesc->tdrefcount++;
299 : : }
300 : : else
301 : : {
302 : : /* Just copy it */
303 : 2710 : oldcxt = MemoryContextSwitchTo(objcxt);
304 : 2710 : erh->er_tupdesc = CreateTupleDescCopy(tupdesc);
305 : 2710 : erh->flags |= ER_FLAG_TUPDESC_ALLOCED;
306 : 2710 : 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 : 10247 : 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 *
329 : 7699 : make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh,
330 : : MemoryContext parentcontext)
331 : : {
332 : : ExpandedRecordHeader *erh;
333 : 7699 : 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 : 7699 : 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 : 7699 : MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
355 : 7699 : + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
356 : :
357 : : /* Ensure all header fields are initialized to 0/null */
358 : 7699 : memset(erh, 0, sizeof(ExpandedRecordHeader));
359 : :
360 : 7699 : EOH_init_header(&erh->hdr, &ER_methods, objcxt);
361 : 7699 : erh->er_magic = ER_MAGIC;
362 : :
363 : : /* Set up dvalues/dnulls, with no valid contents as yet */
364 : 7699 : chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
365 : 7699 : erh->dvalues = (Datum *) chunk;
366 : 7699 : erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
367 : 7699 : erh->nfields = tupdesc->natts;
368 : :
369 : : /* Fill in composite-type identification info */
370 : 7699 : erh->er_decltypeid = olderh->er_decltypeid;
371 : 7699 : erh->er_typeid = olderh->er_typeid;
372 : 7699 : erh->er_typmod = olderh->er_typmod;
373 : 7699 : erh->er_tupdesc_id = olderh->er_tupdesc_id;
374 : :
375 : : /* The only flag bit that transfers over is IS_DOMAIN */
376 : 7699 : 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 [ + - ]: 7699 : if (tupdesc->tdrefcount >= 0)
385 : : {
386 : : /* Register callback to release the refcount */
387 : 7699 : erh->er_mcb.func = ER_mc_callback;
388 : 7699 : erh->er_mcb.arg = (void *) erh;
389 : 7699 : MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
390 : : &erh->er_mcb);
391 : :
392 : : /* And save the pointer */
393 : 7699 : erh->er_tupdesc = tupdesc;
394 : 7699 : tupdesc->tdrefcount++;
395 : : }
2252 tgl@sss.pgh.pa.us 396 [ # # ]:UBC 0 : else if (olderh->flags & ER_FLAG_TUPDESC_ALLOCED)
397 : : {
398 : : /* We need to make our own copy of the tupdesc */
399 : 0 : oldcxt = MemoryContextSwitchTo(objcxt);
400 : 0 : erh->er_tupdesc = CreateTupleDescCopy(tupdesc);
401 : 0 : erh->flags |= ER_FLAG_TUPDESC_ALLOCED;
402 : 0 : 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 : 0 : 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 : :
2252 tgl@sss.pgh.pa.us 418 :CBC 7699 : 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
440 : 15224 : 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 [ - + ]: 15224 : Assert(!(erh->flags & ER_FLAG_IS_DUMMY));
455 : :
456 : : /*
457 : : * Before performing the assignment, see if result will satisfy domain.
458 : : */
459 [ + + ]: 15224 : if (erh->flags & ER_FLAG_IS_DOMAIN)
460 : 32 : 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 : : */
2160 467 [ + + + - ]: 15216 : if (expand_external && tuple)
468 : : {
469 : : /* Assert caller didn't ask for unsupported case */
470 [ - + ]: 2358 : Assert(copy);
471 [ + + ]: 2358 : if (HeapTupleHasExternal(tuple))
472 : : {
473 : 8 : oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
474 : 8 : tuple = toast_flatten_tuple(tuple, erh->er_tupdesc);
475 : 8 : MemoryContextSwitchTo(oldcxt);
476 : : }
477 : : else
478 : 2350 : expand_external = false; /* need not clean up below */
479 : : }
480 : :
481 : : /*
482 : : * Initialize new flags, keeping only non-data status bits.
483 : : */
2252 484 : 15216 : oldflags = erh->flags;
485 : 15216 : 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 [ + + + - ]: 15216 : if (copy && tuple)
492 : : {
493 : 5250 : oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
494 : 5250 : newtuple = heap_copytuple(tuple);
495 : 5250 : newflags |= ER_FLAG_FVALUE_ALLOCED;
496 : 5250 : MemoryContextSwitchTo(oldcxt);
497 : :
498 : : /* We can now flush anything that detoasting might have leaked. */
2160 499 [ + + ]: 5250 : if (expand_external)
500 : 8 : MemoryContextReset(erh->er_short_term_cxt);
501 : : }
502 : : else
2252 503 : 9966 : newtuple = tuple;
504 : :
505 : : /* Make copies of fields we're about to overwrite */
506 : 15216 : oldtuple = erh->fvalue;
507 : 15216 : oldfstartptr = erh->fstartptr;
508 : 15216 : oldfendptr = erh->fendptr;
509 : :
510 : : /*
511 : : * It's now safe to update the expanded record's state.
512 : : */
513 [ + + ]: 15216 : if (newtuple)
514 : : {
515 : : /* Save flat representation */
516 : 15204 : erh->fvalue = newtuple;
517 : 15204 : erh->fstartptr = (char *) newtuple->t_data;
518 : 15204 : erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len;
519 : 15204 : newflags |= ER_FLAG_FVALUE_VALID;
520 : :
521 : : /* Remember if we have any out-of-line field values */
522 [ + + ]: 15204 : if (HeapTupleHasExternal(newtuple))
523 : 103 : newflags |= ER_FLAG_HAVE_EXTERNAL;
524 : : }
525 : : else
526 : : {
527 : 12 : erh->fvalue = NULL;
528 : 12 : erh->fstartptr = erh->fendptr = NULL;
529 : : }
530 : :
531 : 15216 : erh->flags = newflags;
532 : :
533 : : /* Reset flat-size info; we don't bother to make it valid now */
534 : 15216 : 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 [ + + ]: 15216 : if (oldflags & ER_FLAG_DVALUES_ALLOCED)
543 : : {
544 : 24 : TupleDesc tupdesc = erh->er_tupdesc;
545 : : int i;
546 : :
547 [ + + ]: 120 : for (i = 0; i < erh->nfields; i++)
548 : : {
549 [ + + ]: 96 : if (!erh->dnulls[i] &&
550 [ + + ]: 84 : !(TupleDescAttr(tupdesc, i)->attbyval))
551 : : {
552 : 60 : char *oldValue = (char *) DatumGetPointer(erh->dvalues[i]);
553 : :
554 [ + + + + ]: 60 : if (oldValue < oldfstartptr || oldValue >= oldfendptr)
555 : 30 : pfree(oldValue);
556 : : }
557 : : }
558 : : }
559 : :
560 : : /* Likewise free the old tuple, if it was locally allocated */
561 [ + + ]: 15216 : if (oldflags & ER_FLAG_FVALUE_ALLOCED)
562 : 2872 : heap_freetuple(oldtuple);
563 : :
564 : : /* We won't make a new deconstructed representation until/unless needed */
565 : 15216 : }
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
2252 tgl@sss.pgh.pa.us 580 :UBC 0 : 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 : 0 : 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 : 0 : MemoryContextAllocZero(objcxt, sizeof(ExpandedRecordHeader));
601 : :
602 : 0 : EOH_init_header(&erh->hdr, &ER_methods, objcxt);
603 : 0 : 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 : 0 : tuphdr = DatumGetHeapTupleHeader(recorddatum);
611 : :
612 : 0 : tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
613 : 0 : ItemPointerSetInvalid(&(tmptup.t_self));
614 : 0 : tmptup.t_tableOid = InvalidOid;
615 : 0 : tmptup.t_data = tuphdr;
616 : :
617 : 0 : oldcxt = MemoryContextSwitchTo(objcxt);
618 : 0 : newtuple = heap_copytuple(&tmptup);
619 : 0 : erh->flags |= ER_FLAG_FVALUE_ALLOCED;
620 : 0 : MemoryContextSwitchTo(oldcxt);
621 : :
622 : : /* Fill in composite-type identification info */
623 : 0 : erh->er_decltypeid = erh->er_typeid = HeapTupleHeaderGetTypeId(tuphdr);
624 : 0 : erh->er_typmod = HeapTupleHeaderGetTypMod(tuphdr);
625 : :
626 : : /* remember we have a flat representation */
627 : 0 : erh->fvalue = newtuple;
628 : 0 : erh->fstartptr = (char *) newtuple->t_data;
629 : 0 : erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len;
630 : 0 : erh->flags |= ER_FLAG_FVALUE_VALID;
631 : :
632 : : /* Shouldn't need to set ER_FLAG_HAVE_EXTERNAL */
633 [ # # ]: 0 : 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 : 0 : 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
2252 tgl@sss.pgh.pa.us 652 :CBC 1381 : ER_get_flat_size(ExpandedObjectHeader *eohptr)
653 : : {
654 : 1381 : 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 [ - + ]: 1381 : 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 [ + + ]: 1381 : if (erh->er_typeid == RECORDOID &&
669 [ + + ]: 34 : erh->er_typmod < 0)
670 : : {
671 : 9 : tupdesc = expanded_record_get_tupdesc(erh);
672 : 9 : assign_record_type_typmod(tupdesc);
673 : 9 : 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 [ + + ]: 1381 : if (erh->flags & ER_FLAG_FVALUE_VALID &&
681 [ + + ]: 1233 : !(erh->flags & ER_FLAG_HAVE_EXTERNAL))
682 : 1209 : return erh->fvalue->t_len;
683 : :
684 : : /* If we have a cached size value, believe that */
685 [ + + ]: 172 : if (erh->flat_size)
686 : 14 : return erh->flat_size;
687 : :
688 : : /* If we haven't yet deconstructed the tuple, do that */
689 [ + + ]: 158 : if (!(erh->flags & ER_FLAG_DVALUES_VALID))
690 : 24 : deconstruct_expanded_record(erh);
691 : :
692 : : /* Tuple descriptor must be valid by now */
693 : 158 : tupdesc = erh->er_tupdesc;
694 : :
695 : : /*
696 : : * Composite datums mustn't contain any out-of-line values.
697 : : */
698 [ + + ]: 158 : if (erh->flags & ER_FLAG_HAVE_EXTERNAL)
699 : : {
700 [ + + ]: 120 : for (i = 0; i < erh->nfields; i++)
701 : : {
702 : 96 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
703 : :
704 [ + + ]: 96 : if (!erh->dnulls[i] &&
705 [ + + + - ]: 84 : !attr->attbyval && attr->attlen == -1 &&
706 [ + + ]: 60 : 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 : 30 : expanded_record_set_field_internal(erh, i + 1,
2160 713 : 30 : 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 : : */
2252 725 : 24 : erh->flags &= ~ER_FLAG_HAVE_EXTERNAL;
726 : : }
727 : :
728 : : /* Test if we currently have any null values */
729 : 158 : hasnull = false;
730 [ + + ]: 484 : for (i = 0; i < erh->nfields; i++)
731 : : {
732 [ + + ]: 350 : if (erh->dnulls[i])
733 : : {
734 : 24 : hasnull = true;
735 : 24 : break;
736 : : }
737 : : }
738 : :
739 : : /* Determine total space needed */
740 : 158 : len = offsetof(HeapTupleHeaderData, t_bits);
741 : :
742 [ + + ]: 158 : if (hasnull)
743 : 24 : len += BITMAPLEN(tupdesc->natts);
744 : :
745 : 158 : hoff = len = MAXALIGN(len); /* align user data safely */
746 : :
747 : 158 : data_len = heap_compute_data_size(tupdesc, erh->dvalues, erh->dnulls);
748 : :
749 : 158 : len += data_len;
750 : :
751 : : /* Cache for next time */
752 : 158 : erh->flat_size = len;
753 : 158 : erh->data_len = data_len;
754 : 158 : erh->hoff = hoff;
755 : 158 : erh->hasnull = hasnull;
756 : :
757 : 158 : return len;
758 : : }
759 : :
760 : : /*
761 : : * flatten_into method for expanded records
762 : : */
763 : : static void
764 : 1379 : ER_flatten_into(ExpandedObjectHeader *eohptr,
765 : : void *result, Size allocated_size)
766 : : {
767 : 1379 : ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr;
768 : 1379 : HeapTupleHeader tuphdr = (HeapTupleHeader) result;
769 : : TupleDesc tupdesc;
770 : :
771 [ - + ]: 1379 : Assert(erh->er_magic == ER_MAGIC);
772 : :
773 : : /* Easy if we have a valid flattened value without out-of-line fields */
774 [ + + ]: 1379 : if (erh->flags & ER_FLAG_FVALUE_VALID &&
775 [ + - ]: 1209 : !(erh->flags & ER_FLAG_HAVE_EXTERNAL))
776 : : {
777 [ - + ]: 1209 : Assert(allocated_size == erh->fvalue->t_len);
778 : 1209 : memcpy(tuphdr, erh->fvalue->t_data, allocated_size);
779 : : /* The original flattened value might not have datum header fields */
780 : 1209 : HeapTupleHeaderSetDatumLength(tuphdr, allocated_size);
781 : 1209 : HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid);
782 : 1209 : HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod);
783 : 1209 : return;
784 : : }
785 : :
786 : : /* Else allocation should match previous get_flat_size result */
787 [ - + ]: 170 : Assert(allocated_size == erh->flat_size);
788 : :
789 : : /* We'll need the tuple descriptor */
790 : 170 : tupdesc = expanded_record_get_tupdesc(erh);
791 : :
792 : : /* We must ensure that any pad space is zero-filled */
793 : 170 : memset(tuphdr, 0, allocated_size);
794 : :
795 : : /* Set up header fields of composite Datum */
796 : 170 : HeapTupleHeaderSetDatumLength(tuphdr, allocated_size);
797 : 170 : HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid);
798 : 170 : HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod);
799 : : /* We also make sure that t_ctid is invalid unless explicitly set */
800 : 170 : ItemPointerSetInvalid(&(tuphdr->t_ctid));
801 : :
802 : 170 : HeapTupleHeaderSetNatts(tuphdr, tupdesc->natts);
803 : 170 : tuphdr->t_hoff = erh->hoff;
804 : :
805 : : /* And fill the data area from dvalues/dnulls */
806 : 170 : heap_fill_tuple(tupdesc,
2252 tgl@sss.pgh.pa.us 807 :GIC 170 : erh->dvalues,
808 : 170 : erh->dnulls,
2252 tgl@sss.pgh.pa.us 809 :CBC 170 : (char *) tuphdr + erh->hoff,
810 : : erh->data_len,
811 : : &tuphdr->t_infomask,
812 [ + + ]: 170 : (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
2252 tgl@sss.pgh.pa.us 824 :UBC 0 : 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 [ # # ]: 0 : if (erh->er_tupdesc)
830 : 0 : return erh->er_tupdesc;
831 : :
832 : : /* Lookup the composite type's tupdesc using the typcache */
833 : 0 : 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 [ # # ]: 0 : if (tupdesc->tdrefcount >= 0)
842 : : {
843 : : /* Register callback if we didn't already */
844 [ # # ]: 0 : if (erh->er_mcb.arg == NULL)
845 : : {
846 : 0 : erh->er_mcb.func = ER_mc_callback;
847 : 0 : erh->er_mcb.arg = (void *) erh;
848 : 0 : MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
849 : : &erh->er_mcb);
850 : : }
851 : :
852 : : /* Remember our own pointer */
853 : 0 : erh->er_tupdesc = tupdesc;
854 : 0 : tupdesc->tdrefcount++;
855 : :
856 : : /* Release the pin lookup_rowtype_tupdesc acquired */
851 857 [ # # ]: 0 : ReleaseTupleDesc(tupdesc);
858 : : }
859 : : else
860 : : {
861 : : /* Just remember the pointer */
2252 862 : 0 : erh->er_tupdesc = tupdesc;
863 : : }
864 : :
865 : : /* In either case, fetch the process-global ID for this tupdesc */
866 : 0 : erh->er_tupdesc_id = assign_record_type_identifier(tupdesc->tdtypeid,
867 : : tupdesc->tdtypmod);
868 : :
869 : 0 : 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
2252 tgl@sss.pgh.pa.us 884 :CBC 6179 : expanded_record_get_tuple(ExpandedRecordHeader *erh)
885 : : {
886 : : /* Easy case if we still have original tuple */
887 [ + + ]: 6179 : if (erh->flags & ER_FLAG_FVALUE_VALID)
888 : 5277 : return erh->fvalue;
889 : :
890 : : /* Else just build a tuple from datums */
891 [ + - ]: 902 : if (erh->flags & ER_FLAG_DVALUES_VALID)
892 : 902 : return heap_form_tuple(erh->er_tupdesc, erh->dvalues, erh->dnulls);
893 : :
894 : : /* Expanded record is empty */
2252 tgl@sss.pgh.pa.us 895 :UBC 0 : return NULL;
896 : : }
897 : :
898 : : /*
899 : : * Memory context reset callback for cleaning up external resources
900 : : */
901 : : static void
2252 tgl@sss.pgh.pa.us 902 :CBC 15563 : ER_mc_callback(void *arg)
903 : : {
904 : 15563 : ExpandedRecordHeader *erh = (ExpandedRecordHeader *) arg;
905 : 15563 : TupleDesc tupdesc = erh->er_tupdesc;
906 : :
907 : : /* Release our privately-managed tupdesc refcount, if any */
908 [ + - ]: 15563 : if (tupdesc)
909 : : {
910 : 15563 : erh->er_tupdesc = NULL; /* just for luck */
911 [ + - ]: 15563 : if (tupdesc->tdrefcount > 0)
912 : : {
913 [ - + ]: 15563 : if (--tupdesc->tdrefcount == 0)
2252 tgl@sss.pgh.pa.us 914 :UBC 0 : FreeTupleDesc(tupdesc);
915 : : }
916 : : }
2252 tgl@sss.pgh.pa.us 917 :CBC 15563 : }
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 *
2252 tgl@sss.pgh.pa.us 927 :UBC 0 : DatumGetExpandedRecord(Datum d)
928 : : {
929 : : /* If it's a writable expanded record already, just return it */
930 [ # # # # ]: 0 : if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
931 : : {
932 : 0 : ExpandedRecordHeader *erh = (ExpandedRecordHeader *) DatumGetEOHP(d);
933 : :
934 [ # # ]: 0 : Assert(erh->er_magic == ER_MAGIC);
935 : 0 : return erh;
936 : : }
937 : :
938 : : /* Else expand the hard way */
939 : 0 : d = make_expanded_record_from_datum(d, CurrentMemoryContext);
940 : 0 : 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
2252 tgl@sss.pgh.pa.us 952 :CBC 11877 : deconstruct_expanded_record(ExpandedRecordHeader *erh)
953 : : {
954 : : TupleDesc tupdesc;
955 : : Datum *dvalues;
956 : : bool *dnulls;
957 : : int nfields;
958 : :
959 [ + + ]: 11877 : if (erh->flags & ER_FLAG_DVALUES_VALID)
960 : 15 : return; /* already valid, nothing to do */
961 : :
962 : : /* We'll need the tuple descriptor */
963 : 11862 : 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 : 11862 : nfields = tupdesc->natts;
972 [ + - - + ]: 11862 : if (erh->dvalues == NULL || erh->nfields != nfields)
2252 tgl@sss.pgh.pa.us 973 :UBC 0 : {
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 : 0 : chunk = MemoryContextAlloc(erh->hdr.eoh_context,
981 : : nfields * (sizeof(Datum) + sizeof(bool)));
982 : 0 : dvalues = (Datum *) chunk;
983 : 0 : dnulls = (bool *) (chunk + nfields * sizeof(Datum));
984 : 0 : erh->dvalues = dvalues;
985 : 0 : erh->dnulls = dnulls;
986 : 0 : erh->nfields = nfields;
987 : : }
988 : : else
989 : : {
2252 tgl@sss.pgh.pa.us 990 :CBC 11862 : dvalues = erh->dvalues;
991 : 11862 : dnulls = erh->dnulls;
992 : : }
993 : :
994 [ + + ]: 11862 : if (erh->flags & ER_FLAG_FVALUE_VALID)
995 : : {
996 : : /* Deconstruct tuple */
997 : 11010 : 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 : 852 : memset(dvalues, 0, nfields * sizeof(Datum));
1003 : 852 : memset(dnulls, true, nfields * sizeof(bool));
1004 : : }
1005 : :
1006 : : /* Mark the dvalues as valid */
1007 : 11862 : 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
1017 : 3093 : 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 : 3093 : tupdesc = expanded_record_get_tupdesc(erh);
1026 : :
1027 : : /* First, check user-defined attributes */
1028 [ + + ]: 11092 : for (fno = 0; fno < tupdesc->natts; fno++)
1029 : : {
1030 : 11078 : attr = TupleDescAttr(tupdesc, fno);
1031 [ + + ]: 11078 : if (namestrcmp(&attr->attname, fieldname) == 0 &&
1032 [ + - ]: 3079 : !attr->attisdropped)
1033 : : {
1034 : 3079 : finfo->fnumber = attr->attnum;
1035 : 3079 : finfo->ftypeid = attr->atttypid;
1036 : 3079 : finfo->ftypmod = attr->atttypmod;
1037 : 3079 : finfo->fcollation = attr->attcollation;
1038 : 3079 : return true;
1039 : : }
1040 : : }
1041 : :
1042 : : /* How about system attributes? */
1972 andres@anarazel.de 1043 : 14 : sysattr = SystemAttributeByName(fieldname);
2007 1044 [ + + ]: 14 : if (sysattr != NULL)
1045 : : {
1046 : 2 : finfo->fnumber = sysattr->attnum;
1047 : 2 : finfo->ftypeid = sysattr->atttypid;
1048 : 2 : finfo->ftypmod = sysattr->atttypmod;
1049 : 2 : finfo->fcollation = sysattr->attcollation;
2252 tgl@sss.pgh.pa.us 1050 : 2 : return true;
1051 : : }
1052 : :
1053 : 12 : 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
1063 : 10880 : expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber,
1064 : : bool *isnull)
1065 : : {
1066 [ + + ]: 10880 : if (fnumber > 0)
1067 : : {
1068 : : /* Empty record has null fields */
1069 [ + + ]: 10876 : if (ExpandedRecordIsEmpty(erh))
1070 : : {
1071 : 27 : *isnull = true;
1072 : 27 : return (Datum) 0;
1073 : : }
1074 : : /* Make sure we have deconstructed form */
1075 : 10849 : deconstruct_expanded_record(erh);
1076 : : /* Out-of-range field number reads as null */
1077 [ - + ]: 10849 : if (unlikely(fnumber > erh->nfields))
1078 : : {
2252 tgl@sss.pgh.pa.us 1079 :UBC 0 : *isnull = true;
1080 : 0 : return (Datum) 0;
1081 : : }
2252 tgl@sss.pgh.pa.us 1082 :CBC 10849 : *isnull = erh->dnulls[fnumber - 1];
1083 : 10849 : return erh->dvalues[fnumber - 1];
1084 : : }
1085 : : else
1086 : : {
1087 : : /* System columns read as null if we haven't got flat tuple */
1088 [ - + ]: 4 : if (erh->fvalue == NULL)
1089 : : {
2252 tgl@sss.pgh.pa.us 1090 :UBC 0 : *isnull = true;
1091 : 0 : return (Datum) 0;
1092 : : }
1093 : : /* heap_getsysattr doesn't actually use tupdesc, so just pass null */
2252 tgl@sss.pgh.pa.us 1094 :CBC 4 : 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
1112 : 1162 : 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 [ - + - - ]: 1162 : Assert(!(erh->flags & ER_FLAG_IS_DUMMY) || !check_constraints);
1128 : :
1129 : : /* Before performing the assignment, see if result will satisfy domain */
1130 [ + + + - ]: 1162 : if ((erh->flags & ER_FLAG_IS_DOMAIN) && check_constraints)
1131 : 12 : check_domain_for_new_field(erh, fnumber, newValue, isnull);
1132 : :
1133 : : /* If we haven't yet deconstructed the tuple, do that */
1134 [ + + ]: 1159 : if (!(erh->flags & ER_FLAG_DVALUES_VALID))
1135 : 190 : deconstruct_expanded_record(erh);
1136 : :
1137 : : /* Tuple descriptor must be valid by now */
1138 : 1159 : tupdesc = erh->er_tupdesc;
1139 [ - + ]: 1159 : Assert(erh->nfields == tupdesc->natts);
1140 : :
1141 : : /* Caller error if fnumber is system column or nonexistent column */
1142 [ + - - + : 1159 : if (unlikely(fnumber <= 0 || fnumber > erh->nfields))
- + ]
2252 tgl@sss.pgh.pa.us 1143 [ # # ]:UBC 0 : 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 : : */
2252 tgl@sss.pgh.pa.us 1149 :CBC 1159 : attr = TupleDescAttr(tupdesc, fnumber - 1);
1150 [ + + + + ]: 1159 : if (!isnull && !attr->attbyval)
1151 : : {
1152 : : MemoryContext oldcxt;
1153 : :
1154 : : /* If requested, detoast any external value */
2160 1155 [ + + ]: 745 : if (expand_external)
1156 : : {
1157 [ + - ]: 51 : if (attr->attlen == -1 &&
1158 [ + + ]: 51 : VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
1159 : : {
1160 : : /* Detoasting should be done in short-lived context. */
1161 : 31 : oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
1654 rhaas@postgresql.org 1162 : 31 : newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
2160 tgl@sss.pgh.pa.us 1163 : 31 : MemoryContextSwitchTo(oldcxt);
1164 : : }
1165 : : else
1166 : 20 : expand_external = false; /* need not clean up below */
1167 : : }
1168 : :
1169 : : /* Copy value into record's context */
2252 1170 : 745 : oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
1171 : 745 : newValue = datumCopy(newValue, false, attr->attlen);
1172 : 745 : MemoryContextSwitchTo(oldcxt);
1173 : :
1174 : : /* We can now flush anything that detoasting might have leaked */
2160 1175 [ + + ]: 745 : if (expand_external)
1176 : 31 : MemoryContextReset(erh->er_short_term_cxt);
1177 : :
1178 : : /* Remember that we have field(s) that may need to be pfree'd */
2252 1179 : 745 : 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 [ + - ]: 745 : if (attr->attlen == -1 &&
1188 [ - + ]: 745 : VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
2252 tgl@sss.pgh.pa.us 1189 :UBC 0 : erh->flags |= ER_FLAG_HAVE_EXTERNAL;
1190 : : }
1191 : :
1192 : : /*
1193 : : * We're ready to make irreversible changes.
1194 : : */
2252 tgl@sss.pgh.pa.us 1195 :CBC 1159 : dvalues = erh->dvalues;
1196 : 1159 : dnulls = erh->dnulls;
1197 : :
1198 : : /* Flattened value will no longer represent record accurately */
1199 : 1159 : erh->flags &= ~ER_FLAG_FVALUE_VALID;
1200 : : /* And we don't know the flattened size either */
1201 : 1159 : erh->flat_size = 0;
1202 : :
1203 : : /* Grab old field value for pfree'ing, if needed. */
1204 [ + + + + ]: 1159 : if (!attr->attbyval && !dnulls[fnumber - 1])
1205 : 697 : oldValue = (char *) DatumGetPointer(dvalues[fnumber - 1]);
1206 : : else
1207 : 462 : oldValue = NULL;
1208 : :
1209 : : /* And finally we can insert the new field. */
1210 : 1159 : dvalues[fnumber - 1] = newValue;
1211 : 1159 : 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 [ + + + - ]: 1159 : if (oldValue && !(erh->flags & ER_FLAG_IS_DUMMY))
1225 : : {
1226 : : /* Don't try to pfree a part of the original flat record */
1227 [ + - + + ]: 697 : if (oldValue < erh->fstartptr || oldValue >= erh->fendptr)
1228 : 14 : pfree(oldValue);
1229 : : }
1230 : 1159 : }
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
1249 : 51 : 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 [ - + ]: 51 : Assert(!(erh->flags & ER_FLAG_IS_DUMMY));
1261 : :
1262 : : /* If we haven't yet deconstructed the tuple, do that */
1263 [ + - ]: 51 : if (!(erh->flags & ER_FLAG_DVALUES_VALID))
1264 : 51 : deconstruct_expanded_record(erh);
1265 : :
1266 : : /* Tuple descriptor must be valid by now */
1267 : 51 : tupdesc = erh->er_tupdesc;
1268 [ - + ]: 51 : Assert(erh->nfields == tupdesc->natts);
1269 : :
1270 : : /* Flattened value will no longer represent record accurately */
1271 : 51 : erh->flags &= ~ER_FLAG_FVALUE_VALID;
1272 : : /* And we don't know the flattened size either */
1273 : 51 : erh->flat_size = 0;
1274 : :
1275 : 51 : oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
1276 : :
1277 : 51 : dvalues = erh->dvalues;
1278 : 51 : dnulls = erh->dnulls;
1279 : :
1280 [ + + ]: 157 : for (fnumber = 0; fnumber < erh->nfields; fnumber++)
1281 : : {
1282 : 106 : Form_pg_attribute attr = TupleDescAttr(tupdesc, fnumber);
1283 : : Datum newValue;
1284 : : bool isnull;
1285 : :
1286 : : /* Ignore dropped columns */
1287 [ + + ]: 106 : if (attr->attisdropped)
1288 : 4 : continue;
1289 : :
1290 : 102 : newValue = newValues[fnumber];
1291 : 102 : isnull = isnulls[fnumber];
1292 : :
1293 [ + + ]: 102 : if (!attr->attbyval)
1294 : : {
1295 : : /*
1296 : : * Copy new field value into record's context, and deal with
1297 : : * detoasting, if needed.
1298 : : */
1299 [ + - ]: 13 : if (!isnull)
1300 : : {
1301 : : /* Is it an external toasted value? */
2160 1302 [ + - ]: 13 : if (attr->attlen == -1 &&
1303 [ + + ]: 13 : VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
1304 : : {
1305 [ + - ]: 1 : if (expand_external)
1306 : : {
1307 : : /* Detoast as requested while copying the value */
1654 rhaas@postgresql.org 1308 : 1 : newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
1309 : : }
1310 : : else
1311 : : {
1312 : : /* Just copy the value */
2160 tgl@sss.pgh.pa.us 1313 :UBC 0 : newValue = datumCopy(newValue, false, -1);
1314 : : /* If it's still external, remember that */
1315 [ # # ]: 0 : if (VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
1316 : 0 : erh->flags |= ER_FLAG_HAVE_EXTERNAL;
1317 : : }
1318 : : }
1319 : : else
1320 : : {
1321 : : /* Not an external value, just copy it */
2160 tgl@sss.pgh.pa.us 1322 :CBC 12 : newValue = datumCopy(newValue, false, attr->attlen);
1323 : : }
1324 : :
1325 : : /* Remember that we have field(s) that need to be pfree'd */
2252 1326 : 13 : 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 [ - + ]: 13 : if (unlikely(!dnulls[fnumber]))
1334 : : {
1335 : : char *oldValue;
1336 : :
2252 tgl@sss.pgh.pa.us 1337 :UBC 0 : oldValue = (char *) DatumGetPointer(dvalues[fnumber]);
1338 : : /* Don't try to pfree a part of the original flat record */
1339 [ # # # # ]: 0 : if (oldValue < erh->fstartptr || oldValue >= erh->fendptr)
1340 : 0 : pfree(oldValue);
1341 : : }
1342 : : }
1343 : :
1344 : : /* And finally we can insert the new field. */
2252 tgl@sss.pgh.pa.us 1345 :CBC 102 : dvalues[fnumber] = newValue;
1346 : 102 : 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 [ + + ]: 51 : if (erh->flags & ER_FLAG_IS_DOMAIN)
1355 : : {
1356 : : /* We run domain_check in a short-lived context to limit cruft */
2160 1357 : 9 : MemoryContextSwitchTo(get_short_term_cxt(erh));
1358 : :
2252 1359 : 9 : domain_check(ExpandedRecordGetRODatum(erh), false,
1360 : : erh->er_decltypeid,
1361 : : &erh->er_domaininfo,
1362 : : erh->hdr.eoh_context);
1363 : : }
1364 : :
1365 : 47 : MemoryContextSwitchTo(oldcxt);
1366 : 47 : }
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
2160 1379 : 92 : get_short_term_cxt(ExpandedRecordHeader *erh)
1380 : : {
1381 [ + + ]: 92 : if (erh->er_short_term_cxt == NULL)
1382 : 68 : erh->er_short_term_cxt =
2252 1383 : 68 : AllocSetContextCreate(erh->hdr.eoh_context,
1384 : : "expanded record short-term context",
1385 : : ALLOCSET_SMALL_SIZES);
1386 : : else
2160 1387 : 24 : MemoryContextReset(erh->er_short_term_cxt);
1388 : 92 : 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
2252 1402 : 28 : build_dummy_expanded_header(ExpandedRecordHeader *main_erh)
1403 : : {
1404 : : ExpandedRecordHeader *erh;
1405 : 28 : TupleDesc tupdesc = expanded_record_get_tupdesc(main_erh);
1406 : :
1407 : : /* Ensure we have a short-lived context */
2160 1408 : 28 : (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 : : */
2252 1415 : 28 : erh = main_erh->er_dummy_header;
1416 [ + + - + ]: 28 : if (erh == NULL || erh->nfields != tupdesc->natts)
1417 : : {
1418 : : char *chunk;
1419 : :
1420 : : erh = (ExpandedRecordHeader *)
1421 : 18 : MemoryContextAlloc(main_erh->hdr.eoh_context,
1422 : : MAXALIGN(sizeof(ExpandedRecordHeader))
1423 : 18 : + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
1424 : :
1425 : : /* Ensure all header fields are initialized to 0/null */
1426 : 18 : 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 : : */
2160 1439 : 18 : EOH_init_header(&erh->hdr, &ER_methods, main_erh->er_short_term_cxt);
2252 1440 : 18 : erh->er_magic = ER_MAGIC;
1441 : :
1442 : : /* Set up dvalues/dnulls, with no valid contents as yet */
1443 : 18 : chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
1444 : 18 : erh->dvalues = (Datum *) chunk;
1445 : 18 : erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
1446 : 18 : 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 : 18 : 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 : 28 : erh->flags = ER_FLAG_IS_DUMMY;
1467 : :
1468 : : /* Copy composite-type identification info */
1469 : 28 : erh->er_decltypeid = erh->er_typeid = main_erh->er_typeid;
1470 : 28 : erh->er_typmod = main_erh->er_typmod;
1471 : :
1472 : : /* Dummy header does not need its own tupdesc refcount */
1473 : 28 : erh->er_tupdesc = tupdesc;
1474 : 28 : 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 : 28 : erh->flat_size = 0;
1483 : :
1484 : : /* Copy over fvalue if we have it, so that system columns are available */
1485 : 28 : erh->fvalue = main_erh->fvalue;
1486 : 28 : erh->fstartptr = main_erh->fstartptr;
1487 : 28 : erh->fendptr = main_erh->fendptr;
1488 : 28 : }
1489 : :
1490 : : /*
1491 : : * Precheck domain constraints for a set_field operation
1492 : : */
1493 : : static pg_noinline void
1494 : 12 : 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 : 12 : build_dummy_expanded_header(erh);
1502 : 12 : 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 [ + + ]: 12 : if (!ExpandedRecordIsEmpty(erh))
1510 : : {
1511 : 8 : deconstruct_expanded_record(erh);
1512 : 8 : memcpy(dummy_erh->dvalues, erh->dvalues,
1513 : 8 : dummy_erh->nfields * sizeof(Datum));
1514 : 8 : memcpy(dummy_erh->dnulls, erh->dnulls,
1515 : 8 : dummy_erh->nfields * sizeof(bool));
1516 : : /* There might be some external values in there... */
1517 : 8 : dummy_erh->flags |= erh->flags & ER_FLAG_HAVE_EXTERNAL;
1518 : : }
1519 : : else
1520 : : {
1521 : 4 : memset(dummy_erh->dvalues, 0, dummy_erh->nfields * sizeof(Datum));
1522 : 4 : memset(dummy_erh->dnulls, true, dummy_erh->nfields * sizeof(bool));
1523 : : }
1524 : :
1525 : : /* Either way, we now have valid dvalues */
1526 : 12 : dummy_erh->flags |= ER_FLAG_DVALUES_VALID;
1527 : :
1528 : : /* Caller error if fnumber is system column or nonexistent column */
1529 [ + - - + : 12 : if (unlikely(fnumber <= 0 || fnumber > dummy_erh->nfields))
- + ]
2252 tgl@sss.pgh.pa.us 1530 [ # # ]:UBC 0 : elog(ERROR, "cannot assign to field %d of expanded record", fnumber);
1531 : :
1532 : : /* Insert proposed new value into dummy field array */
2252 tgl@sss.pgh.pa.us 1533 :CBC 12 : dummy_erh->dvalues[fnumber - 1] = newValue;
1534 : 12 : 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 [ + + ]: 12 : if (!isnull)
1543 : : {
1544 : 11 : Form_pg_attribute attr = TupleDescAttr(erh->er_tupdesc, fnumber - 1);
1545 : :
1546 [ + + + - ]: 11 : if (!attr->attbyval && attr->attlen == -1 &&
1547 [ - + ]: 6 : VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
2252 tgl@sss.pgh.pa.us 1548 :UBC 0 : 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 : : */
2160 tgl@sss.pgh.pa.us 1555 :CBC 12 : 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 : : */
2252 1561 : 12 : domain_check(ExpandedRecordGetRODatum(dummy_erh), false,
1562 : : erh->er_decltypeid,
1563 : : &erh->er_domaininfo,
1564 : : erh->hdr.eoh_context);
1565 : :
1566 : 9 : MemoryContextSwitchTo(oldcxt);
1567 : :
1568 : : /* We might as well clean up cruft immediately. */
2160 1569 : 9 : MemoryContextReset(erh->er_short_term_cxt);
2252 1570 : 9 : }
1571 : :
1572 : : /*
1573 : : * Precheck domain constraints for a set_tuple operation
1574 : : */
1575 : : static pg_noinline void
1576 : 32 : 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 [ + + ]: 32 : if (tuple == NULL)
1583 : : {
1584 : : /* We run domain_check in a short-lived context to limit cruft */
2160 1585 : 16 : oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
1586 : :
2252 1587 : 16 : domain_check((Datum) 0, true,
1588 : : erh->er_decltypeid,
1589 : : &erh->er_domaininfo,
1590 : : erh->hdr.eoh_context);
1591 : :
1592 : 12 : MemoryContextSwitchTo(oldcxt);
1593 : :
1594 : : /* We might as well clean up cruft immediately. */
2160 1595 : 12 : MemoryContextReset(erh->er_short_term_cxt);
1596 : :
2252 1597 : 12 : return;
1598 : : }
1599 : :
1600 : : /* Construct dummy header to contain replacement tuple */
1601 : 16 : build_dummy_expanded_header(erh);
1602 : 16 : dummy_erh = erh->er_dummy_header;
1603 : :
1604 : : /* Insert tuple, but don't bother to deconstruct its fields for now */
1605 : 16 : dummy_erh->fvalue = tuple;
1606 : 16 : dummy_erh->fstartptr = (char *) tuple->t_data;
1607 : 16 : dummy_erh->fendptr = ((char *) tuple->t_data) + tuple->t_len;
1608 : 16 : dummy_erh->flags |= ER_FLAG_FVALUE_VALID;
1609 : :
1610 : : /* Remember if we have any out-of-line field values */
1611 [ + + ]: 16 : if (HeapTupleHasExternal(tuple))
1612 : 2 : 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 : : */
2160 1618 : 16 : 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 : : */
2252 1624 : 16 : domain_check(ExpandedRecordGetRODatum(dummy_erh), false,
1625 : : erh->er_decltypeid,
1626 : : &erh->er_domaininfo,
1627 : : erh->hdr.eoh_context);
1628 : :
1629 : 12 : MemoryContextSwitchTo(oldcxt);
1630 : :
1631 : : /* We might as well clean up cruft immediately. */
2160 1632 : 12 : MemoryContextReset(erh->er_short_term_cxt);
1633 : : }
|