LCOV - code coverage report
Current view: top level - src/backend/utils/adt - expandedrecord.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 397 471 84.3 %
Date: 2024-04-24 21:11:03 Functions: 17 20 85.0 %
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-2024, PostgreSQL Global Development Group
      11             :  * Portions Copyright (c) 1994, Regents of the University of California
      12             :  *
      13             :  *
      14             :  * IDENTIFICATION
      15             :  *    src/backend/utils/adt/expandedrecord.c
      16             :  *
      17             :  *-------------------------------------------------------------------------
      18             :  */
      19             : #include "postgres.h"
      20             : 
      21             : #include "access/detoast.h"
      22             : #include "access/heaptoast.h"
      23             : #include "access/htup_details.h"
      24             : #include "catalog/heap.h"
      25             : #include "catalog/pg_type.h"
      26             : #include "utils/builtins.h"
      27             : #include "utils/datum.h"
      28             : #include "utils/expandedrecord.h"
      29             : #include "utils/memutils.h"
      30             : #include "utils/typcache.h"
      31             : 
      32             : 
      33             : /* "Methods" required for an expanded object */
      34             : static Size ER_get_flat_size(ExpandedObjectHeader *eohptr);
      35             : static void ER_flatten_into(ExpandedObjectHeader *eohptr,
      36             :                             void *result, Size allocated_size);
      37             : 
      38             : static const ExpandedObjectMethods ER_methods =
      39             : {
      40             :     ER_get_flat_size,
      41             :     ER_flatten_into
      42             : };
      43             : 
      44             : /* Other local functions */
      45             : static void ER_mc_callback(void *arg);
      46             : static MemoryContext get_short_term_cxt(ExpandedRecordHeader *erh);
      47             : static void build_dummy_expanded_header(ExpandedRecordHeader *main_erh);
      48             : static pg_noinline void check_domain_for_new_field(ExpandedRecordHeader *erh,
      49             :                                                    int fnumber,
      50             :                                                    Datum newValue, bool isnull);
      51             : static pg_noinline void check_domain_for_new_tuple(ExpandedRecordHeader *erh,
      52             :                                                    HeapTuple tuple);
      53             : 
      54             : 
      55             : /*
      56             :  * Build an expanded record of the specified composite type
      57             :  *
      58             :  * type_id can be RECORDOID, but only if a positive typmod is given.
      59             :  *
      60             :  * The expanded record is initially "empty", having a state logically
      61             :  * equivalent to a NULL composite value (not ROW(NULL, NULL, ...)).
      62             :  * Note that this might not be a valid state for a domain type;
      63             :  * if the caller needs to check that, call
      64             :  * expanded_record_set_tuple(erh, NULL, false, false).
      65             :  *
      66             :  * The expanded object will be a child of parentcontext.
      67             :  */
      68             : ExpandedRecordHeader *
      69         654 : make_expanded_record_from_typeid(Oid type_id, int32 typmod,
      70             :                                  MemoryContext parentcontext)
      71             : {
      72             :     ExpandedRecordHeader *erh;
      73         654 :     int         flags = 0;
      74             :     TupleDesc   tupdesc;
      75             :     uint64      tupdesc_id;
      76             :     MemoryContext objcxt;
      77             :     char       *chunk;
      78             : 
      79         654 :     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         594 :         typentry = lookup_type_cache(type_id,
      88             :                                      TYPECACHE_TUPDESC |
      89             :                                      TYPECACHE_DOMAIN_BASE_INFO);
      90         594 :         if (typentry->typtype == TYPTYPE_DOMAIN)
      91             :         {
      92          70 :             flags |= ER_FLAG_IS_DOMAIN;
      93          70 :             typentry = lookup_type_cache(typentry->domainBaseType,
      94             :                                          TYPECACHE_TUPDESC);
      95             :         }
      96         594 :         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         594 :         tupdesc = typentry->tupDesc;
     102         594 :         tupdesc_id = typentry->tupDesc_identifier;
     103             :     }
     104             :     else
     105             :     {
     106             :         /*
     107             :          * For RECORD types, get the tupdesc and identifier from typcache.
     108             :          */
     109          60 :         tupdesc = lookup_rowtype_tupdesc(type_id, typmod);
     110          60 :         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         654 :     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         654 :         MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
     132         654 :                            + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
     133             : 
     134             :     /* Ensure all header fields are initialized to 0/null */
     135         654 :     memset(erh, 0, sizeof(ExpandedRecordHeader));
     136             : 
     137         654 :     EOH_init_header(&erh->hdr, &ER_methods, objcxt);
     138         654 :     erh->er_magic = ER_MAGIC;
     139             : 
     140             :     /* Set up dvalues/dnulls, with no valid contents as yet */
     141         654 :     chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
     142         654 :     erh->dvalues = (Datum *) chunk;
     143         654 :     erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
     144         654 :     erh->nfields = tupdesc->natts;
     145             : 
     146             :     /* Fill in composite-type identification info */
     147         654 :     erh->er_decltypeid = type_id;
     148         654 :     erh->er_typeid = tupdesc->tdtypeid;
     149         654 :     erh->er_typmod = tupdesc->tdtypmod;
     150         654 :     erh->er_tupdesc_id = tupdesc_id;
     151             : 
     152         654 :     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         654 :     if (tupdesc->tdrefcount >= 0)
     161             :     {
     162             :         /* Register callback to release the refcount */
     163         654 :         erh->er_mcb.func = ER_mc_callback;
     164         654 :         erh->er_mcb.arg = (void *) erh;
     165         654 :         MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
     166             :                                            &erh->er_mcb);
     167             : 
     168             :         /* And save the pointer */
     169         654 :         erh->er_tupdesc = tupdesc;
     170         654 :         tupdesc->tdrefcount++;
     171             : 
     172             :         /* If we called lookup_rowtype_tupdesc, release the pin it took */
     173         654 :         if (type_id == RECORDOID)
     174          60 :             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         654 :     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       20574 : 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       20574 :     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       15154 :         typentry = lookup_type_cache(tupdesc->tdtypeid, TYPECACHE_TUPDESC);
     228       15154 :         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       15154 :         tupdesc = typentry->tupDesc;
     234       15154 :         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        5420 :         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       20574 :     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       20574 :         MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
     263       20574 :                            + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
     264             : 
     265             :     /* Ensure all header fields are initialized to 0/null */
     266       20574 :     memset(erh, 0, sizeof(ExpandedRecordHeader));
     267             : 
     268       20574 :     EOH_init_header(&erh->hdr, &ER_methods, objcxt);
     269       20574 :     erh->er_magic = ER_MAGIC;
     270             : 
     271             :     /* Set up dvalues/dnulls, with no valid contents as yet */
     272       20574 :     chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
     273       20574 :     erh->dvalues = (Datum *) chunk;
     274       20574 :     erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
     275       20574 :     erh->nfields = tupdesc->natts;
     276             : 
     277             :     /* Fill in composite-type identification info */
     278       20574 :     erh->er_decltypeid = erh->er_typeid = tupdesc->tdtypeid;
     279       20574 :     erh->er_typmod = tupdesc->tdtypmod;
     280       20574 :     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       20574 :     if (tupdesc->tdrefcount >= 0)
     289             :     {
     290             :         /* Register callback to release the refcount */
     291       15154 :         erh->er_mcb.func = ER_mc_callback;
     292       15154 :         erh->er_mcb.arg = (void *) erh;
     293       15154 :         MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
     294             :                                            &erh->er_mcb);
     295             : 
     296             :         /* And save the pointer */
     297       15154 :         erh->er_tupdesc = tupdesc;
     298       15154 :         tupdesc->tdrefcount++;
     299             :     }
     300             :     else
     301             :     {
     302             :         /* Just copy it */
     303        5420 :         oldcxt = MemoryContextSwitchTo(objcxt);
     304        5420 :         erh->er_tupdesc = CreateTupleDescCopy(tupdesc);
     305        5420 :         erh->flags |= ER_FLAG_TUPDESC_ALLOCED;
     306        5420 :         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       20574 :     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       15478 : make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh,
     330             :                                     MemoryContext parentcontext)
     331             : {
     332             :     ExpandedRecordHeader *erh;
     333       15478 :     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       15478 :     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       15478 :         MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
     355       15478 :                            + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
     356             : 
     357             :     /* Ensure all header fields are initialized to 0/null */
     358       15478 :     memset(erh, 0, sizeof(ExpandedRecordHeader));
     359             : 
     360       15478 :     EOH_init_header(&erh->hdr, &ER_methods, objcxt);
     361       15478 :     erh->er_magic = ER_MAGIC;
     362             : 
     363             :     /* Set up dvalues/dnulls, with no valid contents as yet */
     364       15478 :     chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
     365       15478 :     erh->dvalues = (Datum *) chunk;
     366       15478 :     erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
     367       15478 :     erh->nfields = tupdesc->natts;
     368             : 
     369             :     /* Fill in composite-type identification info */
     370       15478 :     erh->er_decltypeid = olderh->er_decltypeid;
     371       15478 :     erh->er_typeid = olderh->er_typeid;
     372       15478 :     erh->er_typmod = olderh->er_typmod;
     373       15478 :     erh->er_tupdesc_id = olderh->er_tupdesc_id;
     374             : 
     375             :     /* The only flag bit that transfers over is IS_DOMAIN */
     376       15478 :     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       15478 :     if (tupdesc->tdrefcount >= 0)
     385             :     {
     386             :         /* Register callback to release the refcount */
     387       15478 :         erh->er_mcb.func = ER_mc_callback;
     388       15478 :         erh->er_mcb.arg = (void *) erh;
     389       15478 :         MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
     390             :                                            &erh->er_mcb);
     391             : 
     392             :         /* And save the pointer */
     393       15478 :         erh->er_tupdesc = tupdesc;
     394       15478 :         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       15478 :     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       30524 : 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       30524 :     if (erh->flags & ER_FLAG_IS_DOMAIN)
     460          64 :         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       30508 :     if (expand_external && tuple)
     468             :     {
     469             :         /* Assert caller didn't ask for unsupported case */
     470             :         Assert(copy);
     471        4716 :         if (HeapTupleHasExternal(tuple))
     472             :         {
     473          16 :             oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
     474          16 :             tuple = toast_flatten_tuple(tuple, erh->er_tupdesc);
     475          16 :             MemoryContextSwitchTo(oldcxt);
     476             :         }
     477             :         else
     478        4700 :             expand_external = false;    /* need not clean up below */
     479             :     }
     480             : 
     481             :     /*
     482             :      * Initialize new flags, keeping only non-data status bits.
     483             :      */
     484       30508 :     oldflags = erh->flags;
     485       30508 :     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       30508 :     if (copy && tuple)
     492             :     {
     493       10500 :         oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
     494       10500 :         newtuple = heap_copytuple(tuple);
     495       10500 :         newflags |= ER_FLAG_FVALUE_ALLOCED;
     496       10500 :         MemoryContextSwitchTo(oldcxt);
     497             : 
     498             :         /* We can now flush anything that detoasting might have leaked. */
     499       10500 :         if (expand_external)
     500          16 :             MemoryContextReset(erh->er_short_term_cxt);
     501             :     }
     502             :     else
     503       20008 :         newtuple = tuple;
     504             : 
     505             :     /* Make copies of fields we're about to overwrite */
     506       30508 :     oldtuple = erh->fvalue;
     507       30508 :     oldfstartptr = erh->fstartptr;
     508       30508 :     oldfendptr = erh->fendptr;
     509             : 
     510             :     /*
     511             :      * It's now safe to update the expanded record's state.
     512             :      */
     513       30508 :     if (newtuple)
     514             :     {
     515             :         /* Save flat representation */
     516       30484 :         erh->fvalue = newtuple;
     517       30484 :         erh->fstartptr = (char *) newtuple->t_data;
     518       30484 :         erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len;
     519       30484 :         newflags |= ER_FLAG_FVALUE_VALID;
     520             : 
     521             :         /* Remember if we have any out-of-line field values */
     522       30484 :         if (HeapTupleHasExternal(newtuple))
     523         206 :             newflags |= ER_FLAG_HAVE_EXTERNAL;
     524             :     }
     525             :     else
     526             :     {
     527          24 :         erh->fvalue = NULL;
     528          24 :         erh->fstartptr = erh->fendptr = NULL;
     529             :     }
     530             : 
     531       30508 :     erh->flags = newflags;
     532             : 
     533             :     /* Reset flat-size info; we don't bother to make it valid now */
     534       30508 :     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       30508 :     if (oldflags & ER_FLAG_DVALUES_ALLOCED)
     543             :     {
     544          48 :         TupleDesc   tupdesc = erh->er_tupdesc;
     545             :         int         i;
     546             : 
     547         240 :         for (i = 0; i < erh->nfields; i++)
     548             :         {
     549         192 :             if (!erh->dnulls[i] &&
     550         168 :                 !(TupleDescAttr(tupdesc, i)->attbyval))
     551             :             {
     552         120 :                 char       *oldValue = (char *) DatumGetPointer(erh->dvalues[i]);
     553             : 
     554         120 :                 if (oldValue < oldfstartptr || oldValue >= oldfendptr)
     555          60 :                     pfree(oldValue);
     556             :             }
     557             :         }
     558             :     }
     559             : 
     560             :     /* Likewise free the old tuple, if it was locally allocated */
     561       30508 :     if (oldflags & ER_FLAG_FVALUE_ALLOCED)
     562        5744 :         heap_freetuple(oldtuple);
     563             : 
     564             :     /* We won't make a new deconstructed representation until/unless needed */
     565       30508 : }
     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        2762 : ER_get_flat_size(ExpandedObjectHeader *eohptr)
     653             : {
     654        2762 :     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        2762 :     if (erh->er_typeid == RECORDOID &&
     669          68 :         erh->er_typmod < 0)
     670             :     {
     671          18 :         tupdesc = expanded_record_get_tupdesc(erh);
     672          18 :         assign_record_type_typmod(tupdesc);
     673          18 :         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        2762 :     if (erh->flags & ER_FLAG_FVALUE_VALID &&
     681        2466 :         !(erh->flags & ER_FLAG_HAVE_EXTERNAL))
     682        2418 :         return erh->fvalue->t_len;
     683             : 
     684             :     /* If we have a cached size value, believe that */
     685         344 :     if (erh->flat_size)
     686          28 :         return erh->flat_size;
     687             : 
     688             :     /* If we haven't yet deconstructed the tuple, do that */
     689         316 :     if (!(erh->flags & ER_FLAG_DVALUES_VALID))
     690          48 :         deconstruct_expanded_record(erh);
     691             : 
     692             :     /* Tuple descriptor must be valid by now */
     693         316 :     tupdesc = erh->er_tupdesc;
     694             : 
     695             :     /*
     696             :      * Composite datums mustn't contain any out-of-line values.
     697             :      */
     698         316 :     if (erh->flags & ER_FLAG_HAVE_EXTERNAL)
     699             :     {
     700         240 :         for (i = 0; i < erh->nfields; i++)
     701             :         {
     702         192 :             Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
     703             : 
     704         192 :             if (!erh->dnulls[i] &&
     705         168 :                 !attr->attbyval && attr->attlen == -1 &&
     706         120 :                 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          60 :                 expanded_record_set_field_internal(erh, i + 1,
     713          60 :                                                    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          48 :         erh->flags &= ~ER_FLAG_HAVE_EXTERNAL;
     726             :     }
     727             : 
     728             :     /* Test if we currently have any null values */
     729         316 :     hasnull = false;
     730         968 :     for (i = 0; i < erh->nfields; i++)
     731             :     {
     732         700 :         if (erh->dnulls[i])
     733             :         {
     734          48 :             hasnull = true;
     735          48 :             break;
     736             :         }
     737             :     }
     738             : 
     739             :     /* Determine total space needed */
     740         316 :     len = offsetof(HeapTupleHeaderData, t_bits);
     741             : 
     742         316 :     if (hasnull)
     743          48 :         len += BITMAPLEN(tupdesc->natts);
     744             : 
     745         316 :     hoff = len = MAXALIGN(len); /* align user data safely */
     746             : 
     747         316 :     data_len = heap_compute_data_size(tupdesc, erh->dvalues, erh->dnulls);
     748             : 
     749         316 :     len += data_len;
     750             : 
     751             :     /* Cache for next time */
     752         316 :     erh->flat_size = len;
     753         316 :     erh->data_len = data_len;
     754         316 :     erh->hoff = hoff;
     755         316 :     erh->hasnull = hasnull;
     756             : 
     757         316 :     return len;
     758             : }
     759             : 
     760             : /*
     761             :  * flatten_into method for expanded records
     762             :  */
     763             : static void
     764        2758 : ER_flatten_into(ExpandedObjectHeader *eohptr,
     765             :                 void *result, Size allocated_size)
     766             : {
     767        2758 :     ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr;
     768        2758 :     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        2758 :     if (erh->flags & ER_FLAG_FVALUE_VALID &&
     775        2418 :         !(erh->flags & ER_FLAG_HAVE_EXTERNAL))
     776             :     {
     777             :         Assert(allocated_size == erh->fvalue->t_len);
     778        2418 :         memcpy(tuphdr, erh->fvalue->t_data, allocated_size);
     779             :         /* The original flattened value might not have datum header fields */
     780        2418 :         HeapTupleHeaderSetDatumLength(tuphdr, allocated_size);
     781        2418 :         HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid);
     782        2418 :         HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod);
     783        2418 :         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         340 :     tupdesc = expanded_record_get_tupdesc(erh);
     791             : 
     792             :     /* We must ensure that any pad space is zero-filled */
     793         340 :     memset(tuphdr, 0, allocated_size);
     794             : 
     795             :     /* Set up header fields of composite Datum */
     796         340 :     HeapTupleHeaderSetDatumLength(tuphdr, allocated_size);
     797         340 :     HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid);
     798         340 :     HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod);
     799             :     /* We also make sure that t_ctid is invalid unless explicitly set */
     800         340 :     ItemPointerSetInvalid(&(tuphdr->t_ctid));
     801             : 
     802         340 :     HeapTupleHeaderSetNatts(tuphdr, tupdesc->natts);
     803         340 :     tuphdr->t_hoff = erh->hoff;
     804             : 
     805             :     /* And fill the data area from dvalues/dnulls */
     806         340 :     heap_fill_tuple(tupdesc,
     807         340 :                     erh->dvalues,
     808         340 :                     erh->dnulls,
     809         340 :                     (char *) tuphdr + erh->hoff,
     810             :                     erh->data_len,
     811             :                     &tuphdr->t_infomask,
     812         340 :                     (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 = (void *) erh;
     848           0 :             MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
     849             :                                                &erh->er_mcb);
     850             :         }
     851             : 
     852             :         /* Remember our own pointer */
     853           0 :         erh->er_tupdesc = tupdesc;
     854           0 :         tupdesc->tdrefcount++;
     855             : 
     856             :         /* Release the pin lookup_rowtype_tupdesc acquired */
     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       12434 : expanded_record_get_tuple(ExpandedRecordHeader *erh)
     885             : {
     886             :     /* Easy case if we still have original tuple */
     887       12434 :     if (erh->flags & ER_FLAG_FVALUE_VALID)
     888       10630 :         return erh->fvalue;
     889             : 
     890             :     /* Else just build a tuple from datums */
     891        1804 :     if (erh->flags & ER_FLAG_DVALUES_VALID)
     892        1804 :         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       31286 : ER_mc_callback(void *arg)
     903             : {
     904       31286 :     ExpandedRecordHeader *erh = (ExpandedRecordHeader *) arg;
     905       31286 :     TupleDesc   tupdesc = erh->er_tupdesc;
     906             : 
     907             :     /* Release our privately-managed tupdesc refcount, if any */
     908       31286 :     if (tupdesc)
     909             :     {
     910       31286 :         erh->er_tupdesc = NULL; /* just for luck */
     911       31286 :         if (tupdesc->tdrefcount > 0)
     912             :         {
     913       31286 :             if (--tupdesc->tdrefcount == 0)
     914           0 :                 FreeTupleDesc(tupdesc);
     915             :         }
     916             :     }
     917       31286 : }
     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       23826 : deconstruct_expanded_record(ExpandedRecordHeader *erh)
     953             : {
     954             :     TupleDesc   tupdesc;
     955             :     Datum      *dvalues;
     956             :     bool       *dnulls;
     957             :     int         nfields;
     958             : 
     959       23826 :     if (erh->flags & ER_FLAG_DVALUES_VALID)
     960          30 :         return;                 /* already valid, nothing to do */
     961             : 
     962             :     /* We'll need the tuple descriptor */
     963       23796 :     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       23796 :     nfields = tupdesc->natts;
     972       23796 :     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       23796 :         dvalues = erh->dvalues;
     991       23796 :         dnulls = erh->dnulls;
     992             :     }
     993             : 
     994       23796 :     if (erh->flags & ER_FLAG_FVALUE_VALID)
     995             :     {
     996             :         /* Deconstruct tuple */
     997       22092 :         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        1704 :         memset(dvalues, 0, nfields * sizeof(Datum));
    1003        1704 :         memset(dnulls, true, nfields * sizeof(bool));
    1004             :     }
    1005             : 
    1006             :     /* Mark the dvalues as valid */
    1007       23796 :     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        6194 : 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        6194 :     tupdesc = expanded_record_get_tupdesc(erh);
    1026             : 
    1027             :     /* First, check user-defined attributes */
    1028       22200 :     for (fno = 0; fno < tupdesc->natts; fno++)
    1029             :     {
    1030       22172 :         attr = TupleDescAttr(tupdesc, fno);
    1031       22172 :         if (namestrcmp(&attr->attname, fieldname) == 0 &&
    1032        6166 :             !attr->attisdropped)
    1033             :         {
    1034        6166 :             finfo->fnumber = attr->attnum;
    1035        6166 :             finfo->ftypeid = attr->atttypid;
    1036        6166 :             finfo->ftypmod = attr->atttypmod;
    1037        6166 :             finfo->fcollation = attr->attcollation;
    1038        6166 :             return true;
    1039             :         }
    1040             :     }
    1041             : 
    1042             :     /* How about system attributes? */
    1043          28 :     sysattr = SystemAttributeByName(fieldname);
    1044          28 :     if (sysattr != NULL)
    1045             :     {
    1046           4 :         finfo->fnumber = sysattr->attnum;
    1047           4 :         finfo->ftypeid = sysattr->atttypid;
    1048           4 :         finfo->ftypmod = sysattr->atttypmod;
    1049           4 :         finfo->fcollation = sysattr->attcollation;
    1050           4 :         return true;
    1051             :     }
    1052             : 
    1053          24 :     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       21832 : expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber,
    1064             :                             bool *isnull)
    1065             : {
    1066       21832 :     if (fnumber > 0)
    1067             :     {
    1068             :         /* Empty record has null fields */
    1069       21824 :         if (ExpandedRecordIsEmpty(erh))
    1070             :         {
    1071          54 :             *isnull = true;
    1072          54 :             return (Datum) 0;
    1073             :         }
    1074             :         /* Make sure we have deconstructed form */
    1075       21770 :         deconstruct_expanded_record(erh);
    1076             :         /* Out-of-range field number reads as null */
    1077       21770 :         if (unlikely(fnumber > erh->nfields))
    1078             :         {
    1079           0 :             *isnull = true;
    1080           0 :             return (Datum) 0;
    1081             :         }
    1082       21770 :         *isnull = erh->dnulls[fnumber - 1];
    1083       21770 :         return erh->dvalues[fnumber - 1];
    1084             :     }
    1085             :     else
    1086             :     {
    1087             :         /* System columns read as null if we haven't got flat tuple */
    1088           8 :         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           8 :         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        2324 : expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
    1113             :                                    Datum newValue, bool isnull,
    1114             :                                    bool expand_external,
    1115             :                                    bool check_constraints)
    1116             : {
    1117             :     TupleDesc   tupdesc;
    1118             :     Form_pg_attribute attr;
    1119             :     Datum      *dvalues;
    1120             :     bool       *dnulls;
    1121             :     char       *oldValue;
    1122             : 
    1123             :     /*
    1124             :      * Shouldn't ever be trying to assign new data to a dummy header, except
    1125             :      * in the case of an internal call for field inlining.
    1126             :      */
    1127             :     Assert(!(erh->flags & ER_FLAG_IS_DUMMY) || !check_constraints);
    1128             : 
    1129             :     /* Before performing the assignment, see if result will satisfy domain */
    1130        2324 :     if ((erh->flags & ER_FLAG_IS_DOMAIN) && check_constraints)
    1131          24 :         check_domain_for_new_field(erh, fnumber, newValue, isnull);
    1132             : 
    1133             :     /* If we haven't yet deconstructed the tuple, do that */
    1134        2318 :     if (!(erh->flags & ER_FLAG_DVALUES_VALID))
    1135         380 :         deconstruct_expanded_record(erh);
    1136             : 
    1137             :     /* Tuple descriptor must be valid by now */
    1138        2318 :     tupdesc = erh->er_tupdesc;
    1139             :     Assert(erh->nfields == tupdesc->natts);
    1140             : 
    1141             :     /* Caller error if fnumber is system column or nonexistent column */
    1142        2318 :     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        2318 :     attr = TupleDescAttr(tupdesc, fnumber - 1);
    1150        2318 :     if (!isnull && !attr->attbyval)
    1151             :     {
    1152             :         MemoryContext oldcxt;
    1153             : 
    1154             :         /* If requested, detoast any external value */
    1155        1490 :         if (expand_external)
    1156             :         {
    1157         102 :             if (attr->attlen == -1 &&
    1158         102 :                 VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
    1159             :             {
    1160             :                 /* Detoasting should be done in short-lived context. */
    1161          62 :                 oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
    1162          62 :                 newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
    1163          62 :                 MemoryContextSwitchTo(oldcxt);
    1164             :             }
    1165             :             else
    1166          40 :                 expand_external = false;    /* need not clean up below */
    1167             :         }
    1168             : 
    1169             :         /* Copy value into record's context */
    1170        1490 :         oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
    1171        1490 :         newValue = datumCopy(newValue, false, attr->attlen);
    1172        1490 :         MemoryContextSwitchTo(oldcxt);
    1173             : 
    1174             :         /* We can now flush anything that detoasting might have leaked */
    1175        1490 :         if (expand_external)
    1176          62 :             MemoryContextReset(erh->er_short_term_cxt);
    1177             : 
    1178             :         /* Remember that we have field(s) that may need to be pfree'd */
    1179        1490 :         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        1490 :         if (attr->attlen == -1 &&
    1188        1490 :             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        2318 :     dvalues = erh->dvalues;
    1196        2318 :     dnulls = erh->dnulls;
    1197             : 
    1198             :     /* Flattened value will no longer represent record accurately */
    1199        2318 :     erh->flags &= ~ER_FLAG_FVALUE_VALID;
    1200             :     /* And we don't know the flattened size either */
    1201        2318 :     erh->flat_size = 0;
    1202             : 
    1203             :     /* Grab old field value for pfree'ing, if needed. */
    1204        2318 :     if (!attr->attbyval && !dnulls[fnumber - 1])
    1205        1394 :         oldValue = (char *) DatumGetPointer(dvalues[fnumber - 1]);
    1206             :     else
    1207         924 :         oldValue = NULL;
    1208             : 
    1209             :     /* And finally we can insert the new field. */
    1210        2318 :     dvalues[fnumber - 1] = newValue;
    1211        2318 :     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        2318 :     if (oldValue && !(erh->flags & ER_FLAG_IS_DUMMY))
    1225             :     {
    1226             :         /* Don't try to pfree a part of the original flat record */
    1227        1394 :         if (oldValue < erh->fstartptr || oldValue >= erh->fendptr)
    1228          28 :             pfree(oldValue);
    1229             :     }
    1230        2318 : }
    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         102 : 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         102 :     if (!(erh->flags & ER_FLAG_DVALUES_VALID))
    1264         102 :         deconstruct_expanded_record(erh);
    1265             : 
    1266             :     /* Tuple descriptor must be valid by now */
    1267         102 :     tupdesc = erh->er_tupdesc;
    1268             :     Assert(erh->nfields == tupdesc->natts);
    1269             : 
    1270             :     /* Flattened value will no longer represent record accurately */
    1271         102 :     erh->flags &= ~ER_FLAG_FVALUE_VALID;
    1272             :     /* And we don't know the flattened size either */
    1273         102 :     erh->flat_size = 0;
    1274             : 
    1275         102 :     oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
    1276             : 
    1277         102 :     dvalues = erh->dvalues;
    1278         102 :     dnulls = erh->dnulls;
    1279             : 
    1280         314 :     for (fnumber = 0; fnumber < erh->nfields; fnumber++)
    1281             :     {
    1282         212 :         Form_pg_attribute attr = TupleDescAttr(tupdesc, fnumber);
    1283             :         Datum       newValue;
    1284             :         bool        isnull;
    1285             : 
    1286             :         /* Ignore dropped columns */
    1287         212 :         if (attr->attisdropped)
    1288           8 :             continue;
    1289             : 
    1290         204 :         newValue = newValues[fnumber];
    1291         204 :         isnull = isnulls[fnumber];
    1292             : 
    1293         204 :         if (!attr->attbyval)
    1294             :         {
    1295             :             /*
    1296             :              * Copy new field value into record's context, and deal with
    1297             :              * detoasting, if needed.
    1298             :              */
    1299          26 :             if (!isnull)
    1300             :             {
    1301             :                 /* Is it an external toasted value? */
    1302          26 :                 if (attr->attlen == -1 &&
    1303          26 :                     VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
    1304             :                 {
    1305           2 :                     if (expand_external)
    1306             :                     {
    1307             :                         /* Detoast as requested while copying the value */
    1308           2 :                         newValue = PointerGetDatum(detoast_external_attr((struct 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          24 :                     newValue = datumCopy(newValue, false, attr->attlen);
    1323             :                 }
    1324             : 
    1325             :                 /* Remember that we have field(s) that need to be pfree'd */
    1326          26 :                 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          26 :             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         204 :         dvalues[fnumber] = newValue;
    1346         204 :         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         102 :     if (erh->flags & ER_FLAG_IS_DOMAIN)
    1355             :     {
    1356             :         /* We run domain_check in a short-lived context to limit cruft */
    1357          18 :         MemoryContextSwitchTo(get_short_term_cxt(erh));
    1358             : 
    1359          18 :         domain_check(ExpandedRecordGetRODatum(erh), false,
    1360             :                      erh->er_decltypeid,
    1361             :                      &erh->er_domaininfo,
    1362             :                      erh->hdr.eoh_context);
    1363             :     }
    1364             : 
    1365          94 :     MemoryContextSwitchTo(oldcxt);
    1366          94 : }
    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         184 : get_short_term_cxt(ExpandedRecordHeader *erh)
    1380             : {
    1381         184 :     if (erh->er_short_term_cxt == NULL)
    1382         136 :         erh->er_short_term_cxt =
    1383         136 :             AllocSetContextCreate(erh->hdr.eoh_context,
    1384             :                                   "expanded record short-term context",
    1385             :                                   ALLOCSET_SMALL_SIZES);
    1386             :     else
    1387          48 :         MemoryContextReset(erh->er_short_term_cxt);
    1388         184 :     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          56 : build_dummy_expanded_header(ExpandedRecordHeader *main_erh)
    1403             : {
    1404             :     ExpandedRecordHeader *erh;
    1405          56 :     TupleDesc   tupdesc = expanded_record_get_tupdesc(main_erh);
    1406             : 
    1407             :     /* Ensure we have a short-lived context */
    1408          56 :     (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          56 :     erh = main_erh->er_dummy_header;
    1416          56 :     if (erh == NULL || erh->nfields != tupdesc->natts)
    1417             :     {
    1418             :         char       *chunk;
    1419             : 
    1420             :         erh = (ExpandedRecordHeader *)
    1421          36 :             MemoryContextAlloc(main_erh->hdr.eoh_context,
    1422             :                                MAXALIGN(sizeof(ExpandedRecordHeader))
    1423          36 :                                + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
    1424             : 
    1425             :         /* Ensure all header fields are initialized to 0/null */
    1426          36 :         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          36 :         EOH_init_header(&erh->hdr, &ER_methods, main_erh->er_short_term_cxt);
    1440          36 :         erh->er_magic = ER_MAGIC;
    1441             : 
    1442             :         /* Set up dvalues/dnulls, with no valid contents as yet */
    1443          36 :         chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
    1444          36 :         erh->dvalues = (Datum *) chunk;
    1445          36 :         erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
    1446          36 :         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          36 :         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          56 :     erh->flags = ER_FLAG_IS_DUMMY;
    1467             : 
    1468             :     /* Copy composite-type identification info */
    1469          56 :     erh->er_decltypeid = erh->er_typeid = main_erh->er_typeid;
    1470          56 :     erh->er_typmod = main_erh->er_typmod;
    1471             : 
    1472             :     /* Dummy header does not need its own tupdesc refcount */
    1473          56 :     erh->er_tupdesc = tupdesc;
    1474          56 :     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          56 :     erh->flat_size = 0;
    1483             : 
    1484             :     /* Copy over fvalue if we have it, so that system columns are available */
    1485          56 :     erh->fvalue = main_erh->fvalue;
    1486          56 :     erh->fstartptr = main_erh->fstartptr;
    1487          56 :     erh->fendptr = main_erh->fendptr;
    1488          56 : }
    1489             : 
    1490             : /*
    1491             :  * Precheck domain constraints for a set_field operation
    1492             :  */
    1493             : static pg_noinline void
    1494          24 : 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          24 :     build_dummy_expanded_header(erh);
    1502          24 :     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          24 :     if (!ExpandedRecordIsEmpty(erh))
    1510             :     {
    1511          16 :         deconstruct_expanded_record(erh);
    1512          16 :         memcpy(dummy_erh->dvalues, erh->dvalues,
    1513          16 :                dummy_erh->nfields * sizeof(Datum));
    1514          16 :         memcpy(dummy_erh->dnulls, erh->dnulls,
    1515          16 :                dummy_erh->nfields * sizeof(bool));
    1516             :         /* There might be some external values in there... */
    1517          16 :         dummy_erh->flags |= erh->flags & ER_FLAG_HAVE_EXTERNAL;
    1518             :     }
    1519             :     else
    1520             :     {
    1521           8 :         memset(dummy_erh->dvalues, 0, dummy_erh->nfields * sizeof(Datum));
    1522           8 :         memset(dummy_erh->dnulls, true, dummy_erh->nfields * sizeof(bool));
    1523             :     }
    1524             : 
    1525             :     /* Either way, we now have valid dvalues */
    1526          24 :     dummy_erh->flags |= ER_FLAG_DVALUES_VALID;
    1527             : 
    1528             :     /* Caller error if fnumber is system column or nonexistent column */
    1529          24 :     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          24 :     dummy_erh->dvalues[fnumber - 1] = newValue;
    1534          24 :     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          24 :     if (!isnull)
    1543             :     {
    1544          22 :         Form_pg_attribute attr = TupleDescAttr(erh->er_tupdesc, fnumber - 1);
    1545             : 
    1546          22 :         if (!attr->attbyval && attr->attlen == -1 &&
    1547          12 :             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          24 :     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          24 :     domain_check(ExpandedRecordGetRODatum(dummy_erh), false,
    1562             :                  erh->er_decltypeid,
    1563             :                  &erh->er_domaininfo,
    1564             :                  erh->hdr.eoh_context);
    1565             : 
    1566          18 :     MemoryContextSwitchTo(oldcxt);
    1567             : 
    1568             :     /* We might as well clean up cruft immediately. */
    1569          18 :     MemoryContextReset(erh->er_short_term_cxt);
    1570          18 : }
    1571             : 
    1572             : /*
    1573             :  * Precheck domain constraints for a set_tuple operation
    1574             :  */
    1575             : static pg_noinline void
    1576          64 : 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          64 :     if (tuple == NULL)
    1583             :     {
    1584             :         /* We run domain_check in a short-lived context to limit cruft */
    1585          32 :         oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
    1586             : 
    1587          32 :         domain_check((Datum) 0, true,
    1588             :                      erh->er_decltypeid,
    1589             :                      &erh->er_domaininfo,
    1590             :                      erh->hdr.eoh_context);
    1591             : 
    1592          24 :         MemoryContextSwitchTo(oldcxt);
    1593             : 
    1594             :         /* We might as well clean up cruft immediately. */
    1595          24 :         MemoryContextReset(erh->er_short_term_cxt);
    1596             : 
    1597          24 :         return;
    1598             :     }
    1599             : 
    1600             :     /* Construct dummy header to contain replacement tuple */
    1601          32 :     build_dummy_expanded_header(erh);
    1602          32 :     dummy_erh = erh->er_dummy_header;
    1603             : 
    1604             :     /* Insert tuple, but don't bother to deconstruct its fields for now */
    1605          32 :     dummy_erh->fvalue = tuple;
    1606          32 :     dummy_erh->fstartptr = (char *) tuple->t_data;
    1607          32 :     dummy_erh->fendptr = ((char *) tuple->t_data) + tuple->t_len;
    1608          32 :     dummy_erh->flags |= ER_FLAG_FVALUE_VALID;
    1609             : 
    1610             :     /* Remember if we have any out-of-line field values */
    1611          32 :     if (HeapTupleHasExternal(tuple))
    1612           4 :         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          32 :     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          32 :     domain_check(ExpandedRecordGetRODatum(dummy_erh), false,
    1625             :                  erh->er_decltypeid,
    1626             :                  &erh->er_domaininfo,
    1627             :                  erh->hdr.eoh_context);
    1628             : 
    1629          24 :     MemoryContextSwitchTo(oldcxt);
    1630             : 
    1631             :     /* We might as well clean up cruft immediately. */
    1632          24 :     MemoryContextReset(erh->er_short_term_cxt);
    1633             : }

Generated by: LCOV version 1.14