LCOV - code coverage report
Current view: top level - src/backend/utils/adt - expandedrecord.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 84.1 % 471 396
Test Date: 2026-03-10 21:14:59 Functions: 85.0 % 20 17
Legend: Lines:     hit not hit

            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-2026, 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 *
      69          580 : make_expanded_record_from_typeid(Oid type_id, int32 typmod,
      70              :                                  MemoryContext parentcontext)
      71              : {
      72              :     ExpandedRecordHeader *erh;
      73          580 :     int         flags = 0;
      74              :     TupleDesc   tupdesc;
      75              :     uint64      tupdesc_id;
      76              :     MemoryContext objcxt;
      77              :     char       *chunk;
      78              : 
      79          580 :     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          550 :         typentry = lookup_type_cache(type_id,
      88              :                                      TYPECACHE_TUPDESC |
      89              :                                      TYPECACHE_DOMAIN_BASE_INFO);
      90          550 :         if (typentry->typtype == TYPTYPE_DOMAIN)
      91              :         {
      92           36 :             flags |= ER_FLAG_IS_DOMAIN;
      93           36 :             typentry = lookup_type_cache(typentry->domainBaseType,
      94              :                                          TYPECACHE_TUPDESC);
      95              :         }
      96          550 :         if (typentry->tupDesc == NULL)
      97            0 :             ereport(ERROR,
      98              :                     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      99              :                      errmsg("type %s is not composite",
     100              :                             format_type_be(type_id))));
     101          550 :         tupdesc = typentry->tupDesc;
     102          550 :         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          580 :     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          580 :         MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
     132          580 :                            + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
     133              : 
     134              :     /* Ensure all header fields are initialized to 0/null */
     135          580 :     memset(erh, 0, sizeof(ExpandedRecordHeader));
     136              : 
     137          580 :     EOH_init_header(&erh->hdr, &ER_methods, objcxt);
     138          580 :     erh->er_magic = ER_MAGIC;
     139              : 
     140              :     /* Set up dvalues/dnulls, with no valid contents as yet */
     141          580 :     chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
     142          580 :     erh->dvalues = (Datum *) chunk;
     143          580 :     erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
     144          580 :     erh->nfields = tupdesc->natts;
     145              : 
     146              :     /* Fill in composite-type identification info */
     147          580 :     erh->er_decltypeid = type_id;
     148          580 :     erh->er_typeid = tupdesc->tdtypeid;
     149          580 :     erh->er_typmod = tupdesc->tdtypmod;
     150          580 :     erh->er_tupdesc_id = tupdesc_id;
     151              : 
     152          580 :     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          580 :     if (tupdesc->tdrefcount >= 0)
     161              :     {
     162              :         /* Register callback to release the refcount */
     163          580 :         erh->er_mcb.func = ER_mc_callback;
     164          580 :         erh->er_mcb.arg = erh;
     165          580 :         MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
     166              :                                            &erh->er_mcb);
     167              : 
     168              :         /* And save the pointer */
     169          580 :         erh->er_tupdesc = tupdesc;
     170          580 :         tupdesc->tdrefcount++;
     171              : 
     172              :         /* If we called lookup_rowtype_tupdesc, release the pin it took */
     173          580 :         if (type_id == RECORDOID)
     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              :          */
     182            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              : 
     190          580 :     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        10543 : 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        10543 :     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         7738 :         typentry = lookup_type_cache(tupdesc->tdtypeid, TYPECACHE_TUPDESC);
     228         7738 :         if (typentry->tupDesc == NULL)
     229            0 :             ereport(ERROR,
     230              :                     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     231              :                      errmsg("type %s is not composite",
     232              :                             format_type_be(tupdesc->tdtypeid))));
     233         7738 :         tupdesc = typentry->tupDesc;
     234         7738 :         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         2805 :         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        10543 :     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        10543 :         MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
     263        10543 :                            + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
     264              : 
     265              :     /* Ensure all header fields are initialized to 0/null */
     266        10543 :     memset(erh, 0, sizeof(ExpandedRecordHeader));
     267              : 
     268        10543 :     EOH_init_header(&erh->hdr, &ER_methods, objcxt);
     269        10543 :     erh->er_magic = ER_MAGIC;
     270              : 
     271              :     /* Set up dvalues/dnulls, with no valid contents as yet */
     272        10543 :     chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
     273        10543 :     erh->dvalues = (Datum *) chunk;
     274        10543 :     erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
     275        10543 :     erh->nfields = tupdesc->natts;
     276              : 
     277              :     /* Fill in composite-type identification info */
     278        10543 :     erh->er_decltypeid = erh->er_typeid = tupdesc->tdtypeid;
     279        10543 :     erh->er_typmod = tupdesc->tdtypmod;
     280        10543 :     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        10543 :     if (tupdesc->tdrefcount >= 0)
     289              :     {
     290              :         /* Register callback to release the refcount */
     291         7738 :         erh->er_mcb.func = ER_mc_callback;
     292         7738 :         erh->er_mcb.arg = erh;
     293         7738 :         MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
     294              :                                            &erh->er_mcb);
     295              : 
     296              :         /* And save the pointer */
     297         7738 :         erh->er_tupdesc = tupdesc;
     298         7738 :         tupdesc->tdrefcount++;
     299              :     }
     300              :     else
     301              :     {
     302              :         /* Just copy it */
     303         2805 :         oldcxt = MemoryContextSwitchTo(objcxt);
     304         2805 :         erh->er_tupdesc = CreateTupleDescCopy(tupdesc);
     305         2805 :         erh->flags |= ER_FLAG_TUPDESC_ALLOCED;
     306         2805 :         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        10543 :     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         7900 : make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh,
     330              :                                     MemoryContext parentcontext)
     331              : {
     332              :     ExpandedRecordHeader *erh;
     333         7900 :     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         7900 :     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         7900 :         MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
     355         7900 :                            + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
     356              : 
     357              :     /* Ensure all header fields are initialized to 0/null */
     358         7900 :     memset(erh, 0, sizeof(ExpandedRecordHeader));
     359              : 
     360         7900 :     EOH_init_header(&erh->hdr, &ER_methods, objcxt);
     361         7900 :     erh->er_magic = ER_MAGIC;
     362              : 
     363              :     /* Set up dvalues/dnulls, with no valid contents as yet */
     364         7900 :     chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
     365         7900 :     erh->dvalues = (Datum *) chunk;
     366         7900 :     erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
     367         7900 :     erh->nfields = tupdesc->natts;
     368              : 
     369              :     /* Fill in composite-type identification info */
     370         7900 :     erh->er_decltypeid = olderh->er_decltypeid;
     371         7900 :     erh->er_typeid = olderh->er_typeid;
     372         7900 :     erh->er_typmod = olderh->er_typmod;
     373         7900 :     erh->er_tupdesc_id = olderh->er_tupdesc_id;
     374              : 
     375              :     /* The only flag bit that transfers over is IS_DOMAIN */
     376         7900 :     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         7900 :     if (tupdesc->tdrefcount >= 0)
     385              :     {
     386              :         /* Register callback to release the refcount */
     387         7900 :         erh->er_mcb.func = ER_mc_callback;
     388         7900 :         erh->er_mcb.arg = erh;
     389         7900 :         MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
     390              :                                            &erh->er_mcb);
     391              : 
     392              :         /* And save the pointer */
     393         7900 :         erh->er_tupdesc = tupdesc;
     394         7900 :         tupdesc->tdrefcount++;
     395              :     }
     396            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              : 
     418         7900 :     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        15878 : 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        15878 :     if (erh->flags & ER_FLAG_IS_DOMAIN)
     460           33 :         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        15870 :     if (expand_external && tuple)
     468              :     {
     469              :         /* Assert caller didn't ask for unsupported case */
     470              :         Assert(copy);
     471         2382 :         if (HeapTupleHasExternal(tuple))
     472              :         {
     473            3 :             oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
     474            3 :             tuple = toast_flatten_tuple(tuple, erh->er_tupdesc);
     475            3 :             MemoryContextSwitchTo(oldcxt);
     476              :         }
     477              :         else
     478         2379 :             expand_external = false;    /* need not clean up below */
     479              :     }
     480              : 
     481              :     /*
     482              :      * Initialize new flags, keeping only non-data status bits.
     483              :      */
     484        15870 :     oldflags = erh->flags;
     485        15870 :     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        15870 :     if (copy && tuple)
     492              :     {
     493         5676 :         oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
     494         5676 :         newtuple = heap_copytuple(tuple);
     495         5676 :         newflags |= ER_FLAG_FVALUE_ALLOCED;
     496         5676 :         MemoryContextSwitchTo(oldcxt);
     497              : 
     498              :         /* We can now flush anything that detoasting might have leaked. */
     499         5676 :         if (expand_external)
     500            3 :             MemoryContextReset(erh->er_short_term_cxt);
     501              :     }
     502              :     else
     503        10194 :         newtuple = tuple;
     504              : 
     505              :     /* Make copies of fields we're about to overwrite */
     506        15870 :     oldtuple = erh->fvalue;
     507        15870 :     oldfstartptr = erh->fstartptr;
     508        15870 :     oldfendptr = erh->fendptr;
     509              : 
     510              :     /*
     511              :      * It's now safe to update the expanded record's state.
     512              :      */
     513        15870 :     if (newtuple)
     514              :     {
     515              :         /* Save flat representation */
     516        15857 :         erh->fvalue = newtuple;
     517        15857 :         erh->fstartptr = (char *) newtuple->t_data;
     518        15857 :         erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len;
     519        15857 :         newflags |= ER_FLAG_FVALUE_VALID;
     520              : 
     521              :         /* Remember if we have any out-of-line field values */
     522        15857 :         if (HeapTupleHasExternal(newtuple))
     523           99 :             newflags |= ER_FLAG_HAVE_EXTERNAL;
     524              :     }
     525              :     else
     526              :     {
     527           13 :         erh->fvalue = NULL;
     528           13 :         erh->fstartptr = erh->fendptr = NULL;
     529              :     }
     530              : 
     531        15870 :     erh->flags = newflags;
     532              : 
     533              :     /* Reset flat-size info; we don't bother to make it valid now */
     534        15870 :     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        15870 :     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 :                 !(TupleDescCompactAttr(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        15870 :     if (oldflags & ER_FLAG_FVALUE_ALLOCED)
     562         2988 :         heap_freetuple(oldtuple);
     563              : 
     564              :     /* We won't make a new deconstructed representation until/unless needed */
     565        15870 : }
     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
     580            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              :     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
     652         1717 : ER_get_flat_size(ExpandedObjectHeader *eohptr)
     653              : {
     654         1717 :     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         1717 :     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         1717 :     if (erh->flags & ER_FLAG_FVALUE_VALID &&
     681         1566 :         !(erh->flags & ER_FLAG_HAVE_EXTERNAL))
     682         1542 :         return erh->fvalue->t_len;
     683              : 
     684              :     /* If we have a cached size value, believe that */
     685          175 :     if (erh->flat_size)
     686           14 :         return erh->flat_size;
     687              : 
     688              :     /* If we haven't yet deconstructed the tuple, do that */
     689          161 :     if (!(erh->flags & ER_FLAG_DVALUES_VALID))
     690           24 :         deconstruct_expanded_record(erh);
     691              : 
     692              :     /* Tuple descriptor must be valid by now */
     693          161 :     tupdesc = erh->er_tupdesc;
     694              : 
     695              :     /*
     696              :      * Composite datums mustn't contain any out-of-line values.
     697              :      */
     698          161 :     if (erh->flags & ER_FLAG_HAVE_EXTERNAL)
     699              :     {
     700          120 :         for (i = 0; i < erh->nfields; i++)
     701              :         {
     702           96 :             CompactAttribute *attr = TupleDescCompactAttr(tupdesc, i);
     703              : 
     704           96 :             if (!erh->dnulls[i] &&
     705          144 :                 !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,
     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              :          */
     725           24 :         erh->flags &= ~ER_FLAG_HAVE_EXTERNAL;
     726              :     }
     727              : 
     728              :     /* Test if we currently have any null values */
     729          161 :     hasnull = false;
     730          493 :     for (i = 0; i < erh->nfields; i++)
     731              :     {
     732          356 :         if (erh->dnulls[i])
     733              :         {
     734           24 :             hasnull = true;
     735           24 :             break;
     736              :         }
     737              :     }
     738              : 
     739              :     /* Determine total space needed */
     740          161 :     len = offsetof(HeapTupleHeaderData, t_bits);
     741              : 
     742          161 :     if (hasnull)
     743           24 :         len += BITMAPLEN(tupdesc->natts);
     744              : 
     745          161 :     hoff = len = MAXALIGN(len); /* align user data safely */
     746              : 
     747          161 :     data_len = heap_compute_data_size(tupdesc, erh->dvalues, erh->dnulls);
     748              : 
     749          161 :     len += data_len;
     750              : 
     751              :     /* Cache for next time */
     752          161 :     erh->flat_size = len;
     753          161 :     erh->data_len = data_len;
     754          161 :     erh->hoff = hoff;
     755          161 :     erh->hasnull = hasnull;
     756              : 
     757          161 :     return len;
     758              : }
     759              : 
     760              : /*
     761              :  * flatten_into method for expanded records
     762              :  */
     763              : static void
     764         1715 : ER_flatten_into(ExpandedObjectHeader *eohptr,
     765              :                 void *result, Size allocated_size)
     766              : {
     767         1715 :     ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr;
     768         1715 :     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         1715 :     if (erh->flags & ER_FLAG_FVALUE_VALID &&
     775         1542 :         !(erh->flags & ER_FLAG_HAVE_EXTERNAL))
     776              :     {
     777              :         Assert(allocated_size == erh->fvalue->t_len);
     778         1542 :         memcpy(tuphdr, erh->fvalue->t_data, allocated_size);
     779              :         /* The original flattened value might not have datum header fields */
     780         1542 :         HeapTupleHeaderSetDatumLength(tuphdr, allocated_size);
     781         1542 :         HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid);
     782         1542 :         HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod);
     783         1542 :         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          173 :     tupdesc = expanded_record_get_tupdesc(erh);
     791              : 
     792              :     /* We must ensure that any pad space is zero-filled */
     793          173 :     memset(tuphdr, 0, allocated_size);
     794              : 
     795              :     /* Set up header fields of composite Datum */
     796          173 :     HeapTupleHeaderSetDatumLength(tuphdr, allocated_size);
     797          173 :     HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid);
     798          173 :     HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod);
     799              :     /* We also make sure that t_ctid is invalid unless explicitly set */
     800          173 :     ItemPointerSetInvalid(&(tuphdr->t_ctid));
     801              : 
     802          173 :     HeapTupleHeaderSetNatts(tuphdr, tupdesc->natts);
     803          173 :     tuphdr->t_hoff = erh->hoff;
     804              : 
     805              :     /* And fill the data area from dvalues/dnulls */
     806          173 :     heap_fill_tuple(tupdesc,
     807          173 :                     erh->dvalues,
     808          173 :                     erh->dnulls,
     809          173 :                     (char *) tuphdr + erh->hoff,
     810              :                     erh->data_len,
     811              :                     &tuphdr->t_infomask,
     812          173 :                     (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
     824            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 = 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 */
     857            0 :         ReleaseTupleDesc(tupdesc);
     858              :     }
     859              :     else
     860              :     {
     861              :         /* Just remember the pointer */
     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
     884         6338 : expanded_record_get_tuple(ExpandedRecordHeader *erh)
     885              : {
     886              :     /* Easy case if we still have original tuple */
     887         6338 :     if (erh->flags & ER_FLAG_FVALUE_VALID)
     888         5307 :         return erh->fvalue;
     889              : 
     890              :     /* Else just build a tuple from datums */
     891         1031 :     if (erh->flags & ER_FLAG_DVALUES_VALID)
     892         1031 :         return heap_form_tuple(erh->er_tupdesc, erh->dvalues, erh->dnulls);
     893              : 
     894              :     /* Expanded record is empty */
     895            0 :     return NULL;
     896              : }
     897              : 
     898              : /*
     899              :  * Memory context reset callback for cleaning up external resources
     900              :  */
     901              : static void
     902        16218 : ER_mc_callback(void *arg)
     903              : {
     904        16218 :     ExpandedRecordHeader *erh = (ExpandedRecordHeader *) arg;
     905        16218 :     TupleDesc   tupdesc = erh->er_tupdesc;
     906              : 
     907              :     /* Release our privately-managed tupdesc refcount, if any */
     908        16218 :     if (tupdesc)
     909              :     {
     910        16218 :         erh->er_tupdesc = NULL; /* just for luck */
     911        16218 :         if (tupdesc->tdrefcount > 0)
     912              :         {
     913        16218 :             if (--tupdesc->tdrefcount == 0)
     914            0 :                 FreeTupleDesc(tupdesc);
     915              :         }
     916              :     }
     917        16218 : }
     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 *
     927            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              :         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
     952        12192 : deconstruct_expanded_record(ExpandedRecordHeader *erh)
     953              : {
     954              :     TupleDesc   tupdesc;
     955              :     Datum      *dvalues;
     956              :     bool       *dnulls;
     957              :     int         nfields;
     958              : 
     959        12192 :     if (erh->flags & ER_FLAG_DVALUES_VALID)
     960           15 :         return;                 /* already valid, nothing to do */
     961              : 
     962              :     /* We'll need the tuple descriptor */
     963        12177 :     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        12177 :     nfields = tupdesc->natts;
     972        12177 :     if (erh->dvalues == NULL || erh->nfields != nfields)
     973            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              :     {
     990        12177 :         dvalues = erh->dvalues;
     991        12177 :         dnulls = erh->dnulls;
     992              :     }
     993              : 
     994        12177 :     if (erh->flags & ER_FLAG_FVALUE_VALID)
     995              :     {
     996              :         /* Deconstruct tuple */
     997        11288 :         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          889 :         memset(dvalues, 0, nfields * sizeof(Datum));
    1003          889 :         memset(dnulls, true, nfields * sizeof(bool));
    1004              :     }
    1005              : 
    1006              :     /* Mark the dvalues as valid */
    1007        12177 :     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         3577 : 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         3577 :     tupdesc = expanded_record_get_tupdesc(erh);
    1026              : 
    1027              :     /* First, check user-defined attributes */
    1028        14296 :     for (fno = 0; fno < tupdesc->natts; fno++)
    1029              :     {
    1030        14282 :         attr = TupleDescAttr(tupdesc, fno);
    1031        14282 :         if (namestrcmp(&attr->attname, fieldname) == 0 &&
    1032         3563 :             !attr->attisdropped)
    1033              :         {
    1034         3563 :             finfo->fnumber = attr->attnum;
    1035         3563 :             finfo->ftypeid = attr->atttypid;
    1036         3563 :             finfo->ftypmod = attr->atttypmod;
    1037         3563 :             finfo->fcollation = attr->attcollation;
    1038         3563 :             return true;
    1039              :         }
    1040              :     }
    1041              : 
    1042              :     /* How about system attributes? */
    1043           14 :     sysattr = SystemAttributeByName(fieldname);
    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;
    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        11149 : expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber,
    1064              :                             bool *isnull)
    1065              : {
    1066        11149 :     if (fnumber > 0)
    1067              :     {
    1068              :         /* Empty record has null fields */
    1069        11145 :         if (ExpandedRecordIsEmpty(erh))
    1070              :         {
    1071           27 :             *isnull = true;
    1072           27 :             return (Datum) 0;
    1073              :         }
    1074              :         /* Make sure we have deconstructed form */
    1075        11118 :         deconstruct_expanded_record(erh);
    1076              :         /* Out-of-range field number reads as null */
    1077        11118 :         if (unlikely(fnumber > erh->nfields))
    1078              :         {
    1079            0 :             *isnull = true;
    1080            0 :             return (Datum) 0;
    1081              :         }
    1082        11118 :         *isnull = erh->dnulls[fnumber - 1];
    1083        11118 :         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              :         {
    1090            0 :             *isnull = true;
    1091            0 :             return (Datum) 0;
    1092              :         }
    1093              :         /* heap_getsysattr doesn't actually use tupdesc, so just pass null */
    1094            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         1306 : 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              :     CompactAttribute *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         1306 :     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         1303 :     if (!(erh->flags & ER_FLAG_DVALUES_VALID))
    1135          199 :         deconstruct_expanded_record(erh);
    1136              : 
    1137              :     /* Tuple descriptor must be valid by now */
    1138         1303 :     tupdesc = erh->er_tupdesc;
    1139              :     Assert(erh->nfields == tupdesc->natts);
    1140              : 
    1141              :     /* Caller error if fnumber is system column or nonexistent column */
    1142         1303 :     if (unlikely(fnumber <= 0 || fnumber > erh->nfields))
    1143            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              :      */
    1149         1303 :     attr = TupleDescCompactAttr(tupdesc, fnumber - 1);
    1150         1303 :     if (!isnull && !attr->attbyval)
    1151              :     {
    1152              :         MemoryContext oldcxt;
    1153              : 
    1154              :         /* If requested, detoast any external value */
    1155          862 :         if (expand_external)
    1156              :         {
    1157          102 :             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));
    1162           31 :                 newValue = PointerGetDatum(detoast_external_attr((varlena *) DatumGetPointer(newValue)));
    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 */
    1170          862 :         oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
    1171          862 :         newValue = datumCopy(newValue, false, attr->attlen);
    1172          862 :         MemoryContextSwitchTo(oldcxt);
    1173              : 
    1174              :         /* We can now flush anything that detoasting might have leaked */
    1175          862 :         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 */
    1179          862 :         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         1724 :         if (attr->attlen == -1 &&
    1188          862 :             VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
    1189            0 :             erh->flags |= ER_FLAG_HAVE_EXTERNAL;
    1190              :     }
    1191              : 
    1192              :     /*
    1193              :      * We're ready to make irreversible changes.
    1194              :      */
    1195         1303 :     dvalues = erh->dvalues;
    1196         1303 :     dnulls = erh->dnulls;
    1197              : 
    1198              :     /* Flattened value will no longer represent record accurately */
    1199         1303 :     erh->flags &= ~ER_FLAG_FVALUE_VALID;
    1200              :     /* And we don't know the flattened size either */
    1201         1303 :     erh->flat_size = 0;
    1202              : 
    1203              :     /* Grab old field value for pfree'ing, if needed. */
    1204         1303 :     if (!attr->attbyval && !dnulls[fnumber - 1])
    1205          760 :         oldValue = (char *) DatumGetPointer(dvalues[fnumber - 1]);
    1206              :     else
    1207          543 :         oldValue = NULL;
    1208              : 
    1209              :     /* And finally we can insert the new field. */
    1210         1303 :     dvalues[fnumber - 1] = newValue;
    1211         1303 :     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         1303 :     if (oldValue && !(erh->flags & ER_FLAG_IS_DUMMY))
    1225              :     {
    1226              :         /* Don't try to pfree a part of the original flat record */
    1227          760 :         if (oldValue < erh->fstartptr || oldValue >= erh->fendptr)
    1228           14 :             pfree(oldValue);
    1229              :     }
    1230         1303 : }
    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              :     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              :     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 :         CompactAttribute *attr = TupleDescCompactAttr(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? */
    1302           26 :                 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 */
    1308            1 :                         newValue = PointerGetDatum(detoast_external_attr((varlena *) DatumGetPointer(newValue)));
    1309              :                     }
    1310              :                     else
    1311              :                     {
    1312              :                         /* Just copy the value */
    1313            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 */
    1322           12 :                     newValue = datumCopy(newValue, false, attr->attlen);
    1323              :                 }
    1324              : 
    1325              :                 /* Remember that we have field(s) that need to be pfree'd */
    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              : 
    1337            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. */
    1345          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 */
    1357            9 :         MemoryContextSwitchTo(get_short_term_cxt(erh));
    1358              : 
    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
    1379           88 : get_short_term_cxt(ExpandedRecordHeader *erh)
    1380              : {
    1381           88 :     if (erh->er_short_term_cxt == NULL)
    1382           67 :         erh->er_short_term_cxt =
    1383           67 :             AllocSetContextCreate(erh->hdr.eoh_context,
    1384              :                                   "expanded record short-term context",
    1385              :                                   ALLOCSET_SMALL_SIZES);
    1386              :     else
    1387           21 :         MemoryContextReset(erh->er_short_term_cxt);
    1388           88 :     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
    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 */
    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              :      */
    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              :          */
    1439           18 :         EOH_init_header(&erh->hdr, &ER_methods, main_erh->er_short_term_cxt);
    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))
    1530            0 :         elog(ERROR, "cannot assign to field %d of expanded record", fnumber);
    1531              : 
    1532              :     /* Insert proposed new value into dummy field array */
    1533           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 :         CompactAttribute *attr = TupleDescCompactAttr(erh->er_tupdesc, fnumber - 1);
    1545              : 
    1546           17 :         if (!attr->attbyval && attr->attlen == -1 &&
    1547            6 :             VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
    1548            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              :      */
    1555           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              :      */
    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. */
    1569            9 :     MemoryContextReset(erh->er_short_term_cxt);
    1570            9 : }
    1571              : 
    1572              : /*
    1573              :  * Precheck domain constraints for a set_tuple operation
    1574              :  */
    1575              : static pg_noinline void
    1576           33 : 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           33 :     if (tuple == NULL)
    1583              :     {
    1584              :         /* We run domain_check in a short-lived context to limit cruft */
    1585           17 :         oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
    1586              : 
    1587           17 :         domain_check((Datum) 0, true,
    1588              :                      erh->er_decltypeid,
    1589              :                      &erh->er_domaininfo,
    1590              :                      erh->hdr.eoh_context);
    1591              : 
    1592           13 :         MemoryContextSwitchTo(oldcxt);
    1593              : 
    1594              :         /* We might as well clean up cruft immediately. */
    1595           13 :         MemoryContextReset(erh->er_short_term_cxt);
    1596              : 
    1597           13 :         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            0 :         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           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              :      */
    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. */
    1632           12 :     MemoryContextReset(erh->er_short_term_cxt);
    1633              : }
        

Generated by: LCOV version 2.0-1