LCOV - code coverage report
Current view: top level - src/backend/utils/adt - expandedrecord.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 64.9 % 627 407
Test Date: 2026-01-26 10:56:24 Functions: 70.0 % 20 14
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 53.5 % 273 146

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * expandedrecord.c
       4                 :             :  *        Functions for manipulating composite expanded objects.
       5                 :             :  *
       6                 :             :  * This module supports "expanded objects" (cf. expandeddatum.h) that can
       7                 :             :  * store values of named composite types, domains over named composite types,
       8                 :             :  * and record types (registered or anonymous).
       9                 :             :  *
      10                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
      11                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
      12                 :             :  *
      13                 :             :  *
      14                 :             :  * IDENTIFICATION
      15                 :             :  *        src/backend/utils/adt/expandedrecord.c
      16                 :             :  *
      17                 :             :  *-------------------------------------------------------------------------
      18                 :             :  */
      19                 :             : #include "postgres.h"
      20                 :             : 
      21                 :             : #include "access/detoast.h"
      22                 :             : #include "access/heaptoast.h"
      23                 :             : #include "access/htup_details.h"
      24                 :             : #include "catalog/heap.h"
      25                 :             : #include "catalog/pg_type.h"
      26                 :             : #include "utils/builtins.h"
      27                 :             : #include "utils/datum.h"
      28                 :             : #include "utils/expandedrecord.h"
      29                 :             : #include "utils/memutils.h"
      30                 :             : #include "utils/typcache.h"
      31                 :             : 
      32                 :             : 
      33                 :             : /* "Methods" required for an expanded object */
      34                 :             : static Size ER_get_flat_size(ExpandedObjectHeader *eohptr);
      35                 :             : static void ER_flatten_into(ExpandedObjectHeader *eohptr,
      36                 :             :                                                         void *result, Size allocated_size);
      37                 :             : 
      38                 :             : static const ExpandedObjectMethods ER_methods =
      39                 :             : {
      40                 :             :         ER_get_flat_size,
      41                 :             :         ER_flatten_into
      42                 :             : };
      43                 :             : 
      44                 :             : /* Other local functions */
      45                 :             : static void ER_mc_callback(void *arg);
      46                 :             : static MemoryContext get_short_term_cxt(ExpandedRecordHeader *erh);
      47                 :             : static void build_dummy_expanded_header(ExpandedRecordHeader *main_erh);
      48                 :             : static pg_noinline void check_domain_for_new_field(ExpandedRecordHeader *erh,
      49                 :             :                                                                                                    int fnumber,
      50                 :             :                                                                                                    Datum newValue, bool isnull);
      51                 :             : static pg_noinline void check_domain_for_new_tuple(ExpandedRecordHeader *erh,
      52                 :             :                                                                                                    HeapTuple tuple);
      53                 :             : 
      54                 :             : 
      55                 :             : /*
      56                 :             :  * Build an expanded record of the specified composite type
      57                 :             :  *
      58                 :             :  * type_id can be RECORDOID, but only if a positive typmod is given.
      59                 :             :  *
      60                 :             :  * The expanded record is initially "empty", having a state logically
      61                 :             :  * equivalent to a NULL composite value (not ROW(NULL, NULL, ...)).
      62                 :             :  * Note that this might not be a valid state for a domain type;
      63                 :             :  * if the caller needs to check that, call
      64                 :             :  * expanded_record_set_tuple(erh, NULL, false, false).
      65                 :             :  *
      66                 :             :  * The expanded object will be a child of parentcontext.
      67                 :             :  */
      68                 :             : ExpandedRecordHeader *
      69                 :         139 : make_expanded_record_from_typeid(Oid type_id, int32 typmod,
      70                 :             :                                                                  MemoryContext parentcontext)
      71                 :             : {
      72                 :         139 :         ExpandedRecordHeader *erh;
      73                 :         139 :         int                     flags = 0;
      74                 :         139 :         TupleDesc       tupdesc;
      75                 :         139 :         uint64          tupdesc_id;
      76                 :         139 :         MemoryContext objcxt;
      77                 :         139 :         char       *chunk;
      78                 :             : 
      79         [ +  + ]:         139 :         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                 :         137 :                 TypeCacheEntry *typentry;
      86                 :             : 
      87                 :         137 :                 typentry = lookup_type_cache(type_id,
      88                 :             :                                                                          TYPECACHE_TUPDESC |
      89                 :             :                                                                          TYPECACHE_DOMAIN_BASE_INFO);
      90         [ +  - ]:         137 :                 if (typentry->typtype == TYPTYPE_DOMAIN)
      91                 :             :                 {
      92                 :           0 :                         flags |= ER_FLAG_IS_DOMAIN;
      93                 :           0 :                         typentry = lookup_type_cache(typentry->domainBaseType,
      94                 :             :                                                                                  TYPECACHE_TUPDESC);
      95                 :           0 :                 }
      96         [ +  - ]:         137 :                 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                 :         137 :                 tupdesc = typentry->tupDesc;
     102                 :         137 :                 tupdesc_id = typentry->tupDesc_identifier;
     103                 :         137 :         }
     104                 :             :         else
     105                 :             :         {
     106                 :             :                 /*
     107                 :             :                  * For RECORD types, get the tupdesc and identifier from typcache.
     108                 :             :                  */
     109                 :           2 :                 tupdesc = lookup_rowtype_tupdesc(type_id, typmod);
     110                 :           2 :                 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                 :         139 :         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                 :         139 :         erh = (ExpandedRecordHeader *)
     131                 :         278 :                 MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
     132                 :         139 :                                                    + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
     133                 :             : 
     134                 :             :         /* Ensure all header fields are initialized to 0/null */
     135                 :         139 :         memset(erh, 0, sizeof(ExpandedRecordHeader));
     136                 :             : 
     137                 :         139 :         EOH_init_header(&erh->hdr, &ER_methods, objcxt);
     138                 :         139 :         erh->er_magic = ER_MAGIC;
     139                 :             : 
     140                 :             :         /* Set up dvalues/dnulls, with no valid contents as yet */
     141                 :         139 :         chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
     142                 :         139 :         erh->dvalues = (Datum *) chunk;
     143                 :         139 :         erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
     144                 :         139 :         erh->nfields = tupdesc->natts;
     145                 :             : 
     146                 :             :         /* Fill in composite-type identification info */
     147                 :         139 :         erh->er_decltypeid = type_id;
     148                 :         139 :         erh->er_typeid = tupdesc->tdtypeid;
     149                 :         139 :         erh->er_typmod = tupdesc->tdtypmod;
     150                 :         139 :         erh->er_tupdesc_id = tupdesc_id;
     151                 :             : 
     152                 :         139 :         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         [ +  - ]:         139 :         if (tupdesc->tdrefcount >= 0)
     161                 :             :         {
     162                 :             :                 /* Register callback to release the refcount */
     163                 :         139 :                 erh->er_mcb.func = ER_mc_callback;
     164                 :         139 :                 erh->er_mcb.arg = erh;
     165                 :         278 :                 MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
     166                 :         139 :                                                                                    &erh->er_mcb);
     167                 :             : 
     168                 :             :                 /* And save the pointer */
     169                 :         139 :                 erh->er_tupdesc = tupdesc;
     170                 :         139 :                 tupdesc->tdrefcount++;
     171                 :             : 
     172                 :             :                 /* If we called lookup_rowtype_tupdesc, release the pin it took */
     173         [ +  + ]:         139 :                 if (type_id == RECORDOID)
     174         [ -  + ]:           2 :                         ReleaseTupleDesc(tupdesc);
     175                 :         139 :         }
     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                 :         278 :         return erh;
     191                 :         139 : }
     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                 :        3092 : make_expanded_record_from_tupdesc(TupleDesc tupdesc,
     206                 :             :                                                                   MemoryContext parentcontext)
     207                 :             : {
     208                 :        3092 :         ExpandedRecordHeader *erh;
     209                 :        3092 :         uint64          tupdesc_id;
     210                 :        3092 :         MemoryContext objcxt;
     211                 :        3092 :         MemoryContext oldcxt;
     212                 :        3092 :         char       *chunk;
     213                 :             : 
     214         [ +  + ]:        3092 :         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                 :        2285 :                 TypeCacheEntry *typentry;
     226                 :             : 
     227                 :        2285 :                 typentry = lookup_type_cache(tupdesc->tdtypeid, TYPECACHE_TUPDESC);
     228         [ +  - ]:        2285 :                 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                 :        2285 :                 tupdesc = typentry->tupDesc;
     234                 :        2285 :                 tupdesc_id = typentry->tupDesc_identifier;
     235                 :        2285 :         }
     236                 :             :         else
     237                 :             :         {
     238                 :             :                 /*
     239                 :             :                  * For RECORD types, get the appropriate unique identifier (possibly
     240                 :             :                  * freshly assigned).
     241                 :             :                  */
     242                 :        1614 :                 tupdesc_id = assign_record_type_identifier(tupdesc->tdtypeid,
     243                 :         807 :                                                                                                    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                 :        3092 :         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                 :        3092 :         erh = (ExpandedRecordHeader *)
     262                 :        6184 :                 MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
     263                 :        3092 :                                                    + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
     264                 :             : 
     265                 :             :         /* Ensure all header fields are initialized to 0/null */
     266                 :        3092 :         memset(erh, 0, sizeof(ExpandedRecordHeader));
     267                 :             : 
     268                 :        3092 :         EOH_init_header(&erh->hdr, &ER_methods, objcxt);
     269                 :        3092 :         erh->er_magic = ER_MAGIC;
     270                 :             : 
     271                 :             :         /* Set up dvalues/dnulls, with no valid contents as yet */
     272                 :        3092 :         chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
     273                 :        3092 :         erh->dvalues = (Datum *) chunk;
     274                 :        3092 :         erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
     275                 :        3092 :         erh->nfields = tupdesc->natts;
     276                 :             : 
     277                 :             :         /* Fill in composite-type identification info */
     278                 :        3092 :         erh->er_decltypeid = erh->er_typeid = tupdesc->tdtypeid;
     279                 :        3092 :         erh->er_typmod = tupdesc->tdtypmod;
     280                 :        3092 :         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         [ +  + ]:        3092 :         if (tupdesc->tdrefcount >= 0)
     289                 :             :         {
     290                 :             :                 /* Register callback to release the refcount */
     291                 :        2285 :                 erh->er_mcb.func = ER_mc_callback;
     292                 :        2285 :                 erh->er_mcb.arg = erh;
     293                 :        4570 :                 MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
     294                 :        2285 :                                                                                    &erh->er_mcb);
     295                 :             : 
     296                 :             :                 /* And save the pointer */
     297                 :        2285 :                 erh->er_tupdesc = tupdesc;
     298                 :        2285 :                 tupdesc->tdrefcount++;
     299                 :        2285 :         }
     300                 :             :         else
     301                 :             :         {
     302                 :             :                 /* Just copy it */
     303                 :         807 :                 oldcxt = MemoryContextSwitchTo(objcxt);
     304                 :         807 :                 erh->er_tupdesc = CreateTupleDescCopy(tupdesc);
     305                 :         807 :                 erh->flags |= ER_FLAG_TUPDESC_ALLOCED;
     306                 :         807 :                 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                 :        6184 :         return erh;
     315                 :        3092 : }
     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                 :        2285 : make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh,
     330                 :             :                                                                         MemoryContext parentcontext)
     331                 :             : {
     332                 :        2285 :         ExpandedRecordHeader *erh;
     333                 :        2285 :         TupleDesc       tupdesc = expanded_record_get_tupdesc(olderh);
     334                 :        2285 :         MemoryContext objcxt;
     335                 :        2285 :         MemoryContext oldcxt;
     336                 :        2285 :         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                 :        2285 :         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                 :        2285 :         erh = (ExpandedRecordHeader *)
     354                 :        4570 :                 MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader))
     355                 :        2285 :                                                    + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
     356                 :             : 
     357                 :             :         /* Ensure all header fields are initialized to 0/null */
     358                 :        2285 :         memset(erh, 0, sizeof(ExpandedRecordHeader));
     359                 :             : 
     360                 :        2285 :         EOH_init_header(&erh->hdr, &ER_methods, objcxt);
     361                 :        2285 :         erh->er_magic = ER_MAGIC;
     362                 :             : 
     363                 :             :         /* Set up dvalues/dnulls, with no valid contents as yet */
     364                 :        2285 :         chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
     365                 :        2285 :         erh->dvalues = (Datum *) chunk;
     366                 :        2285 :         erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
     367                 :        2285 :         erh->nfields = tupdesc->natts;
     368                 :             : 
     369                 :             :         /* Fill in composite-type identification info */
     370                 :        2285 :         erh->er_decltypeid = olderh->er_decltypeid;
     371                 :        2285 :         erh->er_typeid = olderh->er_typeid;
     372                 :        2285 :         erh->er_typmod = olderh->er_typmod;
     373                 :        2285 :         erh->er_tupdesc_id = olderh->er_tupdesc_id;
     374                 :             : 
     375                 :             :         /* The only flag bit that transfers over is IS_DOMAIN */
     376                 :        2285 :         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         [ +  - ]:        2285 :         if (tupdesc->tdrefcount >= 0)
     385                 :             :         {
     386                 :             :                 /* Register callback to release the refcount */
     387                 :        2285 :                 erh->er_mcb.func = ER_mc_callback;
     388                 :        2285 :                 erh->er_mcb.arg = erh;
     389                 :        4570 :                 MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
     390                 :        2285 :                                                                                    &erh->er_mcb);
     391                 :             : 
     392                 :             :                 /* And save the pointer */
     393                 :        2285 :                 erh->er_tupdesc = tupdesc;
     394                 :        2285 :                 tupdesc->tdrefcount++;
     395                 :        2285 :         }
     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                 :           0 :         }
     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                 :        4570 :         return erh;
     419                 :        2285 : }
     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                 :        4573 : expanded_record_set_tuple(ExpandedRecordHeader *erh,
     441                 :             :                                                   HeapTuple tuple,
     442                 :             :                                                   bool copy,
     443                 :             :                                                   bool expand_external)
     444                 :             : {
     445                 :        4573 :         int                     oldflags;
     446                 :        4573 :         HeapTuple       oldtuple;
     447                 :        4573 :         char       *oldfstartptr;
     448                 :        4573 :         char       *oldfendptr;
     449                 :        4573 :         int                     newflags;
     450                 :        4573 :         HeapTuple       newtuple;
     451                 :        4573 :         MemoryContext oldcxt;
     452                 :             : 
     453                 :             :         /* Shouldn't ever be trying to assign new data to a dummy header */
     454         [ +  - ]:        4573 :         Assert(!(erh->flags & ER_FLAG_IS_DUMMY));
     455                 :             : 
     456                 :             :         /*
     457                 :             :          * Before performing the assignment, see if result will satisfy domain.
     458                 :             :          */
     459         [ +  - ]:        4573 :         if (erh->flags & ER_FLAG_IS_DOMAIN)
     460                 :           0 :                 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   [ +  +  -  + ]:        4573 :         if (expand_external && tuple)
     468                 :             :         {
     469                 :             :                 /* Assert caller didn't ask for unsupported case */
     470         [ +  - ]:         771 :                 Assert(copy);
     471         [ -  + ]:         771 :                 if (HeapTupleHasExternal(tuple))
     472                 :             :                 {
     473                 :           0 :                         oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
     474                 :           0 :                         tuple = toast_flatten_tuple(tuple, erh->er_tupdesc);
     475                 :           0 :                         MemoryContextSwitchTo(oldcxt);
     476                 :           0 :                 }
     477                 :             :                 else
     478                 :         771 :                         expand_external = false;        /* need not clean up below */
     479                 :         771 :         }
     480                 :             : 
     481                 :             :         /*
     482                 :             :          * Initialize new flags, keeping only non-data status bits.
     483                 :             :          */
     484                 :        4573 :         oldflags = erh->flags;
     485                 :        4573 :         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   [ +  +  -  + ]:        4573 :         if (copy && tuple)
     492                 :             :         {
     493                 :        1649 :                 oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
     494                 :        1649 :                 newtuple = heap_copytuple(tuple);
     495                 :        1649 :                 newflags |= ER_FLAG_FVALUE_ALLOCED;
     496                 :        1649 :                 MemoryContextSwitchTo(oldcxt);
     497                 :             : 
     498                 :             :                 /* We can now flush anything that detoasting might have leaked. */
     499         [ +  - ]:        1649 :                 if (expand_external)
     500                 :           0 :                         MemoryContextReset(erh->er_short_term_cxt);
     501                 :        1649 :         }
     502                 :             :         else
     503                 :        2924 :                 newtuple = tuple;
     504                 :             : 
     505                 :             :         /* Make copies of fields we're about to overwrite */
     506                 :        4573 :         oldtuple = erh->fvalue;
     507                 :        4573 :         oldfstartptr = erh->fstartptr;
     508                 :        4573 :         oldfendptr = erh->fendptr;
     509                 :             : 
     510                 :             :         /*
     511                 :             :          * It's now safe to update the expanded record's state.
     512                 :             :          */
     513         [ +  - ]:        4573 :         if (newtuple)
     514                 :             :         {
     515                 :             :                 /* Save flat representation */
     516                 :        4573 :                 erh->fvalue = newtuple;
     517                 :        4573 :                 erh->fstartptr = (char *) newtuple->t_data;
     518                 :        4573 :                 erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len;
     519                 :        4573 :                 newflags |= ER_FLAG_FVALUE_VALID;
     520                 :             : 
     521                 :             :                 /* Remember if we have any out-of-line field values */
     522         [ +  + ]:        4573 :                 if (HeapTupleHasExternal(newtuple))
     523                 :          33 :                         newflags |= ER_FLAG_HAVE_EXTERNAL;
     524                 :        4573 :         }
     525                 :             :         else
     526                 :             :         {
     527                 :           0 :                 erh->fvalue = NULL;
     528                 :           0 :                 erh->fstartptr = erh->fendptr = NULL;
     529                 :             :         }
     530                 :             : 
     531                 :        4573 :         erh->flags = newflags;
     532                 :             : 
     533                 :             :         /* Reset flat-size info; we don't bother to make it valid now */
     534                 :        4573 :         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         [ +  + ]:        4573 :         if (oldflags & ER_FLAG_DVALUES_ALLOCED)
     543                 :             :         {
     544                 :           8 :                 TupleDesc       tupdesc = erh->er_tupdesc;
     545                 :           8 :                 int                     i;
     546                 :             : 
     547         [ +  + ]:          40 :                 for (i = 0; i < erh->nfields; i++)
     548                 :             :                 {
     549   [ +  +  +  + ]:          32 :                         if (!erh->dnulls[i] &&
     550                 :          28 :                                 !(TupleDescCompactAttr(tupdesc, i)->attbyval))
     551                 :             :                         {
     552                 :          20 :                                 char       *oldValue = (char *) DatumGetPointer(erh->dvalues[i]);
     553                 :             : 
     554   [ +  +  +  + ]:          20 :                                 if (oldValue < oldfstartptr || oldValue >= oldfendptr)
     555                 :          10 :                                         pfree(oldValue);
     556                 :          20 :                         }
     557                 :          32 :                 }
     558                 :           8 :         }
     559                 :             : 
     560                 :             :         /* Likewise free the old tuple, if it was locally allocated */
     561         [ +  + ]:        4573 :         if (oldflags & ER_FLAG_FVALUE_ALLOCED)
     562                 :         951 :                 heap_freetuple(oldtuple);
     563                 :             : 
     564                 :             :         /* We won't make a new deconstructed representation until/unless needed */
     565                 :        4573 : }
     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                 :           0 :         ExpandedRecordHeader *erh;
     583                 :           0 :         HeapTupleHeader tuphdr;
     584                 :           0 :         HeapTupleData tmptup;
     585                 :           0 :         HeapTuple       newtuple;
     586                 :           0 :         MemoryContext objcxt;
     587                 :           0 :         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                 :           0 :         erh = (ExpandedRecordHeader *)
     600                 :           0 :                 MemoryContextAllocZero(objcxt, sizeof(ExpandedRecordHeader));
     601                 :             : 
     602                 :           0 :         EOH_init_header(&erh->hdr, &ER_methods, objcxt);
     603                 :           0 :         erh->er_magic = ER_MAGIC;
     604                 :             : 
     605                 :             :         /*
     606                 :             :          * Detoast and copy source record into private context, as a HeapTuple.
     607                 :             :          * (If we actually have to detoast the source, we'll leak some memory in
     608                 :             :          * the caller's context, but it doesn't seem worth worrying about.)
     609                 :             :          */
     610                 :           0 :         tuphdr = DatumGetHeapTupleHeader(recorddatum);
     611                 :             : 
     612                 :           0 :         tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
     613                 :           0 :         ItemPointerSetInvalid(&(tmptup.t_self));
     614                 :           0 :         tmptup.t_tableOid = InvalidOid;
     615                 :           0 :         tmptup.t_data = tuphdr;
     616                 :             : 
     617                 :           0 :         oldcxt = MemoryContextSwitchTo(objcxt);
     618                 :           0 :         newtuple = heap_copytuple(&tmptup);
     619                 :           0 :         erh->flags |= ER_FLAG_FVALUE_ALLOCED;
     620                 :           0 :         MemoryContextSwitchTo(oldcxt);
     621                 :             : 
     622                 :             :         /* Fill in composite-type identification info */
     623                 :           0 :         erh->er_decltypeid = erh->er_typeid = HeapTupleHeaderGetTypeId(tuphdr);
     624                 :           0 :         erh->er_typmod = HeapTupleHeaderGetTypMod(tuphdr);
     625                 :             : 
     626                 :             :         /* remember we have a flat representation */
     627                 :           0 :         erh->fvalue = newtuple;
     628                 :           0 :         erh->fstartptr = (char *) newtuple->t_data;
     629                 :           0 :         erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len;
     630                 :           0 :         erh->flags |= ER_FLAG_FVALUE_VALID;
     631                 :             : 
     632                 :             :         /* Shouldn't need to set ER_FLAG_HAVE_EXTERNAL */
     633         [ #  # ]:           0 :         Assert(!HeapTupleHeaderHasExternal(tuphdr));
     634                 :             : 
     635                 :             :         /*
     636                 :             :          * We won't look up the tupdesc till we have to, nor make a deconstructed
     637                 :             :          * representation.  We don't have enough info to fill flat_size and
     638                 :             :          * friends, either.
     639                 :             :          */
     640                 :             : 
     641                 :             :         /* return a R/W pointer to the expanded record */
     642                 :           0 :         return EOHPGetRWDatum(&erh->hdr);
     643                 :           0 : }
     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                 :         405 : ER_get_flat_size(ExpandedObjectHeader *eohptr)
     653                 :             : {
     654                 :         405 :         ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr;
     655                 :         405 :         TupleDesc       tupdesc;
     656                 :         405 :         Size            len;
     657                 :         405 :         Size            data_len;
     658                 :         405 :         int                     hoff;
     659                 :         405 :         bool            hasnull;
     660                 :         405 :         int                     i;
     661                 :             : 
     662         [ +  - ]:         405 :         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   [ +  +  +  + ]:         405 :         if (erh->er_typeid == RECORDOID &&
     669                 :           4 :                 erh->er_typmod < 0)
     670                 :             :         {
     671                 :           2 :                 tupdesc = expanded_record_get_tupdesc(erh);
     672                 :           2 :                 assign_record_type_typmod(tupdesc);
     673                 :           2 :                 erh->er_typmod = tupdesc->tdtypmod;
     674                 :           2 :         }
     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   [ +  +  +  + ]:         405 :         if (erh->flags & ER_FLAG_FVALUE_VALID &&
     681                 :         373 :                 !(erh->flags & ER_FLAG_HAVE_EXTERNAL))
     682                 :         365 :                 return erh->fvalue->t_len;
     683                 :             : 
     684                 :             :         /* If we have a cached size value, believe that */
     685         [ -  + ]:          40 :         if (erh->flat_size)
     686                 :           0 :                 return erh->flat_size;
     687                 :             : 
     688                 :             :         /* If we haven't yet deconstructed the tuple, do that */
     689         [ +  + ]:          40 :         if (!(erh->flags & ER_FLAG_DVALUES_VALID))
     690                 :           8 :                 deconstruct_expanded_record(erh);
     691                 :             : 
     692                 :             :         /* Tuple descriptor must be valid by now */
     693                 :          40 :         tupdesc = erh->er_tupdesc;
     694                 :             : 
     695                 :             :         /*
     696                 :             :          * Composite datums mustn't contain any out-of-line values.
     697                 :             :          */
     698         [ +  + ]:          40 :         if (erh->flags & ER_FLAG_HAVE_EXTERNAL)
     699                 :             :         {
     700         [ +  + ]:          40 :                 for (i = 0; i < erh->nfields; i++)
     701                 :             :                 {
     702                 :          32 :                         CompactAttribute *attr = TupleDescCompactAttr(tupdesc, i);
     703                 :             : 
     704         [ +  + ]:          32 :                         if (!erh->dnulls[i] &&
     705   [ +  +  +  -  :          28 :                                 !attr->attbyval && attr->attlen == -1 &&
                   +  + ]
     706                 :          20 :                                 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                 :          20 :                                 expanded_record_set_field_internal(erh, i + 1,
     713                 :          10 :                                                                                                    erh->dvalues[i], false,
     714                 :             :                                                                                                    true,
     715                 :             :                                                                                                    false);
     716                 :          10 :                         }
     717                 :          32 :                 }
     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                 :           8 :                 erh->flags &= ~ER_FLAG_HAVE_EXTERNAL;
     726                 :           8 :         }
     727                 :             : 
     728                 :             :         /* Test if we currently have any null values */
     729                 :          40 :         hasnull = false;
     730         [ +  + ]:         125 :         for (i = 0; i < erh->nfields; i++)
     731                 :             :         {
     732         [ +  + ]:          92 :                 if (erh->dnulls[i])
     733                 :             :                 {
     734                 :           7 :                         hasnull = true;
     735                 :           7 :                         break;
     736                 :             :                 }
     737                 :          85 :         }
     738                 :             : 
     739                 :             :         /* Determine total space needed */
     740                 :          40 :         len = offsetof(HeapTupleHeaderData, t_bits);
     741                 :             : 
     742         [ +  + ]:          40 :         if (hasnull)
     743                 :           7 :                 len += BITMAPLEN(tupdesc->natts);
     744                 :             : 
     745                 :          40 :         hoff = len = MAXALIGN(len); /* align user data safely */
     746                 :             : 
     747                 :          40 :         data_len = heap_compute_data_size(tupdesc, erh->dvalues, erh->dnulls);
     748                 :             : 
     749                 :          40 :         len += data_len;
     750                 :             : 
     751                 :             :         /* Cache for next time */
     752                 :          40 :         erh->flat_size = len;
     753                 :          40 :         erh->data_len = data_len;
     754                 :          40 :         erh->hoff = hoff;
     755                 :          40 :         erh->hasnull = hasnull;
     756                 :             : 
     757                 :          40 :         return len;
     758                 :         405 : }
     759                 :             : 
     760                 :             : /*
     761                 :             :  * flatten_into method for expanded records
     762                 :             :  */
     763                 :             : static void
     764                 :         405 : ER_flatten_into(ExpandedObjectHeader *eohptr,
     765                 :             :                                 void *result, Size allocated_size)
     766                 :             : {
     767                 :         405 :         ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr;
     768                 :         405 :         HeapTupleHeader tuphdr = (HeapTupleHeader) result;
     769                 :         405 :         TupleDesc       tupdesc;
     770                 :             : 
     771         [ +  - ]:         405 :         Assert(erh->er_magic == ER_MAGIC);
     772                 :             : 
     773                 :             :         /* Easy if we have a valid flattened value without out-of-line fields */
     774   [ +  +  -  + ]:         405 :         if (erh->flags & ER_FLAG_FVALUE_VALID &&
     775                 :         365 :                 !(erh->flags & ER_FLAG_HAVE_EXTERNAL))
     776                 :             :         {
     777         [ +  - ]:         365 :                 Assert(allocated_size == erh->fvalue->t_len);
     778                 :         365 :                 memcpy(tuphdr, erh->fvalue->t_data, allocated_size);
     779                 :             :                 /* The original flattened value might not have datum header fields */
     780                 :         365 :                 HeapTupleHeaderSetDatumLength(tuphdr, allocated_size);
     781                 :         365 :                 HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid);
     782                 :         365 :                 HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod);
     783                 :         365 :                 return;
     784                 :             :         }
     785                 :             : 
     786                 :             :         /* Else allocation should match previous get_flat_size result */
     787         [ +  - ]:          40 :         Assert(allocated_size == erh->flat_size);
     788                 :             : 
     789                 :             :         /* We'll need the tuple descriptor */
     790                 :          40 :         tupdesc = expanded_record_get_tupdesc(erh);
     791                 :             : 
     792                 :             :         /* We must ensure that any pad space is zero-filled */
     793                 :          40 :         memset(tuphdr, 0, allocated_size);
     794                 :             : 
     795                 :             :         /* Set up header fields of composite Datum */
     796                 :          40 :         HeapTupleHeaderSetDatumLength(tuphdr, allocated_size);
     797                 :          40 :         HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid);
     798                 :          40 :         HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod);
     799                 :             :         /* We also make sure that t_ctid is invalid unless explicitly set */
     800                 :          40 :         ItemPointerSetInvalid(&(tuphdr->t_ctid));
     801                 :             : 
     802                 :          40 :         HeapTupleHeaderSetNatts(tuphdr, tupdesc->natts);
     803                 :          40 :         tuphdr->t_hoff = erh->hoff;
     804                 :             : 
     805                 :             :         /* And fill the data area from dvalues/dnulls */
     806                 :          80 :         heap_fill_tuple(tupdesc,
     807                 :          40 :                                         erh->dvalues,
     808                 :          40 :                                         erh->dnulls,
     809                 :          40 :                                         (char *) tuphdr + erh->hoff,
     810                 :          40 :                                         erh->data_len,
     811                 :          40 :                                         &tuphdr->t_infomask,
     812         [ +  + ]:          40 :                                         (erh->hasnull ? tuphdr->t_bits : NULL));
     813         [ -  + ]:         405 : }
     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                 :           0 :         TupleDesc       tupdesc;
     827                 :             : 
     828                 :             :         /* Easy if we already have it (but caller should have checked already) */
     829         [ #  # ]:           0 :         if (erh->er_tupdesc)
     830                 :           0 :                 return erh->er_tupdesc;
     831                 :             : 
     832                 :             :         /* Lookup the composite type's tupdesc using the typcache */
     833                 :           0 :         tupdesc = lookup_rowtype_tupdesc(erh->er_typeid, erh->er_typmod);
     834                 :             : 
     835                 :             :         /*
     836                 :             :          * If it's a refcounted tupdesc rather than a statically allocated one, we
     837                 :             :          * want to manage the refcount with a memory context callback rather than
     838                 :             :          * assuming that the CurrentResourceOwner is longer-lived than this
     839                 :             :          * expanded object.
     840                 :             :          */
     841         [ #  # ]:           0 :         if (tupdesc->tdrefcount >= 0)
     842                 :             :         {
     843                 :             :                 /* Register callback if we didn't already */
     844         [ #  # ]:           0 :                 if (erh->er_mcb.arg == NULL)
     845                 :             :                 {
     846                 :           0 :                         erh->er_mcb.func = ER_mc_callback;
     847                 :           0 :                         erh->er_mcb.arg = erh;
     848                 :           0 :                         MemoryContextRegisterResetCallback(erh->hdr.eoh_context,
     849                 :           0 :                                                                                            &erh->er_mcb);
     850                 :           0 :                 }
     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                 :           0 :         }
     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                 :           0 :                                                                                                            tupdesc->tdtypmod);
     868                 :             : 
     869                 :           0 :         return tupdesc;
     870                 :           0 : }
     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                 :        1867 : expanded_record_get_tuple(ExpandedRecordHeader *erh)
     885                 :             : {
     886                 :             :         /* Easy case if we still have original tuple */
     887         [ +  + ]:        1867 :         if (erh->flags & ER_FLAG_FVALUE_VALID)
     888                 :        1686 :                 return erh->fvalue;
     889                 :             : 
     890                 :             :         /* Else just build a tuple from datums */
     891         [ +  - ]:         181 :         if (erh->flags & ER_FLAG_DVALUES_VALID)
     892                 :         181 :                 return heap_form_tuple(erh->er_tupdesc, erh->dvalues, erh->dnulls);
     893                 :             : 
     894                 :             :         /* Expanded record is empty */
     895                 :           0 :         return NULL;
     896                 :        1867 : }
     897                 :             : 
     898                 :             : /*
     899                 :             :  * Memory context reset callback for cleaning up external resources
     900                 :             :  */
     901                 :             : static void
     902                 :        4709 : ER_mc_callback(void *arg)
     903                 :             : {
     904                 :        4709 :         ExpandedRecordHeader *erh = (ExpandedRecordHeader *) arg;
     905                 :        4709 :         TupleDesc       tupdesc = erh->er_tupdesc;
     906                 :             : 
     907                 :             :         /* Release our privately-managed tupdesc refcount, if any */
     908         [ -  + ]:        4709 :         if (tupdesc)
     909                 :             :         {
     910                 :        4709 :                 erh->er_tupdesc = NULL; /* just for luck */
     911         [ -  + ]:        4709 :                 if (tupdesc->tdrefcount > 0)
     912                 :             :                 {
     913         [ +  - ]:        4709 :                         if (--tupdesc->tdrefcount == 0)
     914                 :           0 :                                 FreeTupleDesc(tupdesc);
     915                 :        4709 :                 }
     916                 :        4709 :         }
     917                 :        4709 : }
     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         [ #  # ]:           0 :                 Assert(erh->er_magic == ER_MAGIC);
     935                 :           0 :                 return erh;
     936                 :           0 :         }
     937                 :             : 
     938                 :             :         /* Else expand the hard way */
     939                 :           0 :         d = make_expanded_record_from_datum(d, CurrentMemoryContext);
     940                 :           0 :         return (ExpandedRecordHeader *) DatumGetEOHP(d);
     941                 :           0 : }
     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                 :        3670 : deconstruct_expanded_record(ExpandedRecordHeader *erh)
     953                 :             : {
     954                 :        3670 :         TupleDesc       tupdesc;
     955                 :        3670 :         Datum      *dvalues;
     956                 :        3670 :         bool       *dnulls;
     957                 :        3670 :         int                     nfields;
     958                 :             : 
     959         [ -  + ]:        3670 :         if (erh->flags & ER_FLAG_DVALUES_VALID)
     960                 :           0 :                 return;                                 /* already valid, nothing to do */
     961                 :             : 
     962                 :             :         /* We'll need the tuple descriptor */
     963                 :        3670 :         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                 :        3670 :         nfields = tupdesc->natts;
     972   [ +  -  -  + ]:        3670 :         if (erh->dvalues == NULL || erh->nfields != nfields)
     973                 :             :         {
     974                 :           0 :                 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                 :           0 :                                                                    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                 :           0 :         }
     988                 :             :         else
     989                 :             :         {
     990                 :        3670 :                 dvalues = erh->dvalues;
     991                 :        3670 :                 dnulls = erh->dnulls;
     992                 :             :         }
     993                 :             : 
     994         [ +  + ]:        3670 :         if (erh->flags & ER_FLAG_FVALUE_VALID)
     995                 :             :         {
     996                 :             :                 /* Deconstruct tuple */
     997                 :        3402 :                 heap_deform_tuple(erh->fvalue, tupdesc, dvalues, dnulls);
     998                 :        3402 :         }
     999                 :             :         else
    1000                 :             :         {
    1001                 :             :                 /* If record was empty, instantiate it as a row of nulls */
    1002                 :         268 :                 memset(dvalues, 0, nfields * sizeof(Datum));
    1003                 :         268 :                 memset(dnulls, true, nfields * sizeof(bool));
    1004                 :             :         }
    1005                 :             : 
    1006                 :             :         /* Mark the dvalues as valid */
    1007                 :        3670 :         erh->flags |= ER_FLAG_DVALUES_VALID;
    1008         [ -  + ]:        3670 : }
    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                 :         949 : expanded_record_lookup_field(ExpandedRecordHeader *erh, const char *fieldname,
    1018                 :             :                                                          ExpandedRecordFieldInfo *finfo)
    1019                 :             : {
    1020                 :         949 :         TupleDesc       tupdesc;
    1021                 :         949 :         int                     fno;
    1022                 :         949 :         Form_pg_attribute attr;
    1023                 :         949 :         const FormData_pg_attribute *sysattr;
    1024                 :             : 
    1025                 :         949 :         tupdesc = expanded_record_get_tupdesc(erh);
    1026                 :             : 
    1027                 :             :         /* First, check user-defined attributes */
    1028         [ +  - ]:        3756 :         for (fno = 0; fno < tupdesc->natts; fno++)
    1029                 :             :         {
    1030                 :        3756 :                 attr = TupleDescAttr(tupdesc, fno);
    1031   [ +  +  +  - ]:        3756 :                 if (namestrcmp(&attr->attname, fieldname) == 0 &&
    1032                 :         949 :                         !attr->attisdropped)
    1033                 :             :                 {
    1034                 :         949 :                         finfo->fnumber = attr->attnum;
    1035                 :         949 :                         finfo->ftypeid = attr->atttypid;
    1036                 :         949 :                         finfo->ftypmod = attr->atttypmod;
    1037                 :         949 :                         finfo->fcollation = attr->attcollation;
    1038                 :         949 :                         return true;
    1039                 :             :                 }
    1040                 :        2807 :         }
    1041                 :             : 
    1042                 :             :         /* How about system attributes? */
    1043                 :           0 :         sysattr = SystemAttributeByName(fieldname);
    1044         [ #  # ]:           0 :         if (sysattr != NULL)
    1045                 :             :         {
    1046                 :           0 :                 finfo->fnumber = sysattr->attnum;
    1047                 :           0 :                 finfo->ftypeid = sysattr->atttypid;
    1048                 :           0 :                 finfo->ftypmod = sysattr->atttypmod;
    1049                 :           0 :                 finfo->fcollation = sysattr->attcollation;
    1050                 :           0 :                 return true;
    1051                 :             :         }
    1052                 :             : 
    1053                 :           0 :         return false;
    1054                 :         949 : }
    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                 :        3353 : expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber,
    1064                 :             :                                                         bool *isnull)
    1065                 :             : {
    1066         [ +  - ]:        3353 :         if (fnumber > 0)
    1067                 :             :         {
    1068                 :             :                 /* Empty record has null fields */
    1069         [ +  + ]:        3353 :                 if (ExpandedRecordIsEmpty(erh))
    1070                 :             :                 {
    1071                 :           5 :                         *isnull = true;
    1072                 :           5 :                         return (Datum) 0;
    1073                 :             :                 }
    1074                 :             :                 /* Make sure we have deconstructed form */
    1075                 :        3348 :                 deconstruct_expanded_record(erh);
    1076                 :             :                 /* Out-of-range field number reads as null */
    1077         [ -  + ]:        3348 :                 if (unlikely(fnumber > erh->nfields))
    1078                 :             :                 {
    1079                 :           0 :                         *isnull = true;
    1080                 :           0 :                         return (Datum) 0;
    1081                 :             :                 }
    1082                 :        3348 :                 *isnull = erh->dnulls[fnumber - 1];
    1083                 :        3348 :                 return erh->dvalues[fnumber - 1];
    1084                 :             :         }
    1085                 :             :         else
    1086                 :             :         {
    1087                 :             :                 /* System columns read as null if we haven't got flat tuple */
    1088         [ #  # ]:           0 :                 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                 :           0 :                 return heap_getsysattr(erh->fvalue, fnumber, NULL, isnull);
    1095                 :             :         }
    1096                 :        3353 : }
    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                 :         254 : expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
    1113                 :             :                                                                    Datum newValue, bool isnull,
    1114                 :             :                                                                    bool expand_external,
    1115                 :             :                                                                    bool check_constraints)
    1116                 :             : {
    1117                 :         254 :         TupleDesc       tupdesc;
    1118                 :         254 :         CompactAttribute *attr;
    1119                 :         254 :         Datum      *dvalues;
    1120                 :         254 :         bool       *dnulls;
    1121                 :         254 :         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   [ -  +  #  # ]:         254 :         Assert(!(erh->flags & ER_FLAG_IS_DUMMY) || !check_constraints);
    1128                 :             : 
    1129                 :             :         /* Before performing the assignment, see if result will satisfy domain */
    1130   [ -  +  #  # ]:         254 :         if ((erh->flags & ER_FLAG_IS_DOMAIN) && check_constraints)
    1131                 :           0 :                 check_domain_for_new_field(erh, fnumber, newValue, isnull);
    1132                 :             : 
    1133                 :             :         /* If we haven't yet deconstructed the tuple, do that */
    1134         [ +  + ]:         254 :         if (!(erh->flags & ER_FLAG_DVALUES_VALID))
    1135                 :          60 :                 deconstruct_expanded_record(erh);
    1136                 :             : 
    1137                 :             :         /* Tuple descriptor must be valid by now */
    1138                 :         254 :         tupdesc = erh->er_tupdesc;
    1139         [ +  - ]:         254 :         Assert(erh->nfields == tupdesc->natts);
    1140                 :             : 
    1141                 :             :         /* Caller error if fnumber is system column or nonexistent column */
    1142   [ -  +  +  - ]:         254 :         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                 :         254 :         attr = TupleDescCompactAttr(tupdesc, fnumber - 1);
    1150   [ +  +  +  + ]:         254 :         if (!isnull && !attr->attbyval)
    1151                 :             :         {
    1152                 :         126 :                 MemoryContext oldcxt;
    1153                 :             : 
    1154                 :             :                 /* If requested, detoast any external value */
    1155         [ +  + ]:         126 :                 if (expand_external)
    1156                 :             :                 {
    1157   [ +  -  -  + ]:          10 :                         if (attr->attlen == -1 &&
    1158                 :          10 :                                 VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
    1159                 :             :                         {
    1160                 :             :                                 /* Detoasting should be done in short-lived context. */
    1161                 :          10 :                                 oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
    1162                 :          10 :                                 newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
    1163                 :          10 :                                 MemoryContextSwitchTo(oldcxt);
    1164                 :          10 :                         }
    1165                 :             :                         else
    1166                 :           0 :                                 expand_external = false;        /* need not clean up below */
    1167                 :          10 :                 }
    1168                 :             : 
    1169                 :             :                 /* Copy value into record's context */
    1170                 :         126 :                 oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
    1171                 :         126 :                 newValue = datumCopy(newValue, false, attr->attlen);
    1172                 :         126 :                 MemoryContextSwitchTo(oldcxt);
    1173                 :             : 
    1174                 :             :                 /* We can now flush anything that detoasting might have leaked */
    1175         [ +  + ]:         126 :                 if (expand_external)
    1176                 :          10 :                         MemoryContextReset(erh->er_short_term_cxt);
    1177                 :             : 
    1178                 :             :                 /* Remember that we have field(s) that may need to be pfree'd */
    1179                 :         126 :                 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   [ +  -  +  - ]:         126 :                 if (attr->attlen == -1 &&
    1188                 :         126 :                         VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
    1189                 :           0 :                         erh->flags |= ER_FLAG_HAVE_EXTERNAL;
    1190                 :         126 :         }
    1191                 :             : 
    1192                 :             :         /*
    1193                 :             :          * We're ready to make irreversible changes.
    1194                 :             :          */
    1195                 :         254 :         dvalues = erh->dvalues;
    1196                 :         254 :         dnulls = erh->dnulls;
    1197                 :             : 
    1198                 :             :         /* Flattened value will no longer represent record accurately */
    1199                 :         254 :         erh->flags &= ~ER_FLAG_FVALUE_VALID;
    1200                 :             :         /* And we don't know the flattened size either */
    1201                 :         254 :         erh->flat_size = 0;
    1202                 :             : 
    1203                 :             :         /* Grab old field value for pfree'ing, if needed. */
    1204   [ +  +  +  + ]:         254 :         if (!attr->attbyval && !dnulls[fnumber - 1])
    1205                 :          98 :                 oldValue = (char *) DatumGetPointer(dvalues[fnumber - 1]);
    1206                 :             :         else
    1207                 :         156 :                 oldValue = NULL;
    1208                 :             : 
    1209                 :             :         /* And finally we can insert the new field. */
    1210                 :         254 :         dvalues[fnumber - 1] = newValue;
    1211                 :         254 :         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   [ +  +  -  + ]:         254 :         if (oldValue && !(erh->flags & ER_FLAG_IS_DUMMY))
    1225                 :             :         {
    1226                 :             :                 /* Don't try to pfree a part of the original flat record */
    1227   [ +  -  -  + ]:          98 :                 if (oldValue < erh->fstartptr || oldValue >= erh->fendptr)
    1228                 :           0 :                         pfree(oldValue);
    1229                 :          98 :         }
    1230                 :         254 : }
    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                 :           3 : expanded_record_set_fields(ExpandedRecordHeader *erh,
    1250                 :             :                                                    const Datum *newValues, const bool *isnulls,
    1251                 :             :                                                    bool expand_external)
    1252                 :             : {
    1253                 :           3 :         TupleDesc       tupdesc;
    1254                 :           3 :         Datum      *dvalues;
    1255                 :           3 :         bool       *dnulls;
    1256                 :           3 :         int                     fnumber;
    1257                 :           3 :         MemoryContext oldcxt;
    1258                 :             : 
    1259                 :             :         /* Shouldn't ever be trying to assign new data to a dummy header */
    1260         [ +  - ]:           3 :         Assert(!(erh->flags & ER_FLAG_IS_DUMMY));
    1261                 :             : 
    1262                 :             :         /* If we haven't yet deconstructed the tuple, do that */
    1263         [ -  + ]:           3 :         if (!(erh->flags & ER_FLAG_DVALUES_VALID))
    1264                 :           3 :                 deconstruct_expanded_record(erh);
    1265                 :             : 
    1266                 :             :         /* Tuple descriptor must be valid by now */
    1267                 :           3 :         tupdesc = erh->er_tupdesc;
    1268         [ +  - ]:           3 :         Assert(erh->nfields == tupdesc->natts);
    1269                 :             : 
    1270                 :             :         /* Flattened value will no longer represent record accurately */
    1271                 :           3 :         erh->flags &= ~ER_FLAG_FVALUE_VALID;
    1272                 :             :         /* And we don't know the flattened size either */
    1273                 :           3 :         erh->flat_size = 0;
    1274                 :             : 
    1275                 :           3 :         oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
    1276                 :             : 
    1277                 :           3 :         dvalues = erh->dvalues;
    1278                 :           3 :         dnulls = erh->dnulls;
    1279                 :             : 
    1280         [ +  + ]:          10 :         for (fnumber = 0; fnumber < erh->nfields; fnumber++)
    1281                 :             :         {
    1282                 :           7 :                 CompactAttribute *attr = TupleDescCompactAttr(tupdesc, fnumber);
    1283                 :           7 :                 Datum           newValue;
    1284                 :           7 :                 bool            isnull;
    1285                 :             : 
    1286                 :             :                 /* Ignore dropped columns */
    1287         [ +  + ]:           7 :                 if (attr->attisdropped)
    1288                 :           1 :                         continue;
    1289                 :             : 
    1290                 :           6 :                 newValue = newValues[fnumber];
    1291                 :           6 :                 isnull = isnulls[fnumber];
    1292                 :             : 
    1293         [ +  + ]:           6 :                 if (!attr->attbyval)
    1294                 :             :                 {
    1295                 :             :                         /*
    1296                 :             :                          * Copy new field value into record's context, and deal with
    1297                 :             :                          * detoasting, if needed.
    1298                 :             :                          */
    1299         [ -  + ]:           2 :                         if (!isnull)
    1300                 :             :                         {
    1301                 :             :                                 /* Is it an external toasted value? */
    1302   [ +  -  +  - ]:           2 :                                 if (attr->attlen == -1 &&
    1303                 :           2 :                                         VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
    1304                 :             :                                 {
    1305         [ #  # ]:           0 :                                         if (expand_external)
    1306                 :             :                                         {
    1307                 :             :                                                 /* Detoast as requested while copying the value */
    1308                 :           0 :                                                 newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue)));
    1309                 :           0 :                                         }
    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                 :           0 :                                 }
    1319                 :             :                                 else
    1320                 :             :                                 {
    1321                 :             :                                         /* Not an external value, just copy it */
    1322                 :           2 :                                         newValue = datumCopy(newValue, false, attr->attlen);
    1323                 :             :                                 }
    1324                 :             : 
    1325                 :             :                                 /* Remember that we have field(s) that need to be pfree'd */
    1326                 :           2 :                                 erh->flags |= ER_FLAG_DVALUES_ALLOCED;
    1327                 :           2 :                         }
    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         [ +  - ]:           2 :                         if (unlikely(!dnulls[fnumber]))
    1334                 :             :                         {
    1335                 :           0 :                                 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                 :           0 :                         }
    1342                 :           2 :                 }
    1343                 :             : 
    1344                 :             :                 /* And finally we can insert the new field. */
    1345                 :           6 :                 dvalues[fnumber] = newValue;
    1346                 :           6 :                 dnulls[fnumber] = isnull;
    1347      [ -  +  + ]:           7 :         }
    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         [ +  - ]:           3 :         if (erh->flags & ER_FLAG_IS_DOMAIN)
    1355                 :             :         {
    1356                 :             :                 /* We run domain_check in a short-lived context to limit cruft */
    1357                 :           0 :                 MemoryContextSwitchTo(get_short_term_cxt(erh));
    1358                 :             : 
    1359                 :           0 :                 domain_check(ExpandedRecordGetRODatum(erh), false,
    1360                 :           0 :                                          erh->er_decltypeid,
    1361                 :           0 :                                          &erh->er_domaininfo,
    1362                 :           0 :                                          erh->hdr.eoh_context);
    1363                 :           0 :         }
    1364                 :             : 
    1365                 :           3 :         MemoryContextSwitchTo(oldcxt);
    1366                 :           3 : }
    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                 :          10 : get_short_term_cxt(ExpandedRecordHeader *erh)
    1380                 :             : {
    1381         [ +  + ]:          10 :         if (erh->er_short_term_cxt == NULL)
    1382                 :           8 :                 erh->er_short_term_cxt =
    1383                 :           8 :                         AllocSetContextCreate(erh->hdr.eoh_context,
    1384                 :             :                                                                   "expanded record short-term context",
    1385                 :             :                                                                   ALLOCSET_SMALL_SIZES);
    1386                 :             :         else
    1387                 :           2 :                 MemoryContextReset(erh->er_short_term_cxt);
    1388                 :          10 :         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                 :           0 : build_dummy_expanded_header(ExpandedRecordHeader *main_erh)
    1403                 :             : {
    1404                 :           0 :         ExpandedRecordHeader *erh;
    1405                 :           0 :         TupleDesc       tupdesc = expanded_record_get_tupdesc(main_erh);
    1406                 :             : 
    1407                 :             :         /* Ensure we have a short-lived context */
    1408                 :           0 :         (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                 :           0 :         erh = main_erh->er_dummy_header;
    1416   [ #  #  #  # ]:           0 :         if (erh == NULL || erh->nfields != tupdesc->natts)
    1417                 :             :         {
    1418                 :           0 :                 char       *chunk;
    1419                 :             : 
    1420                 :           0 :                 erh = (ExpandedRecordHeader *)
    1421                 :           0 :                         MemoryContextAlloc(main_erh->hdr.eoh_context,
    1422                 :             :                                                            MAXALIGN(sizeof(ExpandedRecordHeader))
    1423                 :           0 :                                                            + tupdesc->natts * (sizeof(Datum) + sizeof(bool)));
    1424                 :             : 
    1425                 :             :                 /* Ensure all header fields are initialized to 0/null */
    1426                 :           0 :                 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                 :           0 :                 EOH_init_header(&erh->hdr, &ER_methods, main_erh->er_short_term_cxt);
    1440                 :           0 :                 erh->er_magic = ER_MAGIC;
    1441                 :             : 
    1442                 :             :                 /* Set up dvalues/dnulls, with no valid contents as yet */
    1443                 :           0 :                 chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader));
    1444                 :           0 :                 erh->dvalues = (Datum *) chunk;
    1445                 :           0 :                 erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum));
    1446                 :           0 :                 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                 :           0 :                 main_erh->er_dummy_header = erh;
    1456                 :           0 :         }
    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                 :           0 :         erh->flags = ER_FLAG_IS_DUMMY;
    1467                 :             : 
    1468                 :             :         /* Copy composite-type identification info */
    1469                 :           0 :         erh->er_decltypeid = erh->er_typeid = main_erh->er_typeid;
    1470                 :           0 :         erh->er_typmod = main_erh->er_typmod;
    1471                 :             : 
    1472                 :             :         /* Dummy header does not need its own tupdesc refcount */
    1473                 :           0 :         erh->er_tupdesc = tupdesc;
    1474                 :           0 :         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                 :           0 :         erh->flat_size = 0;
    1483                 :             : 
    1484                 :             :         /* Copy over fvalue if we have it, so that system columns are available */
    1485                 :           0 :         erh->fvalue = main_erh->fvalue;
    1486                 :           0 :         erh->fstartptr = main_erh->fstartptr;
    1487                 :           0 :         erh->fendptr = main_erh->fendptr;
    1488                 :           0 : }
    1489                 :             : 
    1490                 :             : /*
    1491                 :             :  * Precheck domain constraints for a set_field operation
    1492                 :             :  */
    1493                 :             : static pg_noinline void
    1494                 :           0 : check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber,
    1495                 :             :                                                    Datum newValue, bool isnull)
    1496                 :             : {
    1497                 :           0 :         ExpandedRecordHeader *dummy_erh;
    1498                 :           0 :         MemoryContext oldcxt;
    1499                 :             : 
    1500                 :             :         /* Construct dummy header to contain proposed new field set */
    1501                 :           0 :         build_dummy_expanded_header(erh);
    1502                 :           0 :         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         [ #  # ]:           0 :         if (!ExpandedRecordIsEmpty(erh))
    1510                 :             :         {
    1511                 :           0 :                 deconstruct_expanded_record(erh);
    1512                 :           0 :                 memcpy(dummy_erh->dvalues, erh->dvalues,
    1513                 :             :                            dummy_erh->nfields * sizeof(Datum));
    1514                 :           0 :                 memcpy(dummy_erh->dnulls, erh->dnulls,
    1515                 :             :                            dummy_erh->nfields * sizeof(bool));
    1516                 :             :                 /* There might be some external values in there... */
    1517                 :           0 :                 dummy_erh->flags |= erh->flags & ER_FLAG_HAVE_EXTERNAL;
    1518                 :           0 :         }
    1519                 :             :         else
    1520                 :             :         {
    1521                 :           0 :                 memset(dummy_erh->dvalues, 0, dummy_erh->nfields * sizeof(Datum));
    1522                 :           0 :                 memset(dummy_erh->dnulls, true, dummy_erh->nfields * sizeof(bool));
    1523                 :             :         }
    1524                 :             : 
    1525                 :             :         /* Either way, we now have valid dvalues */
    1526                 :           0 :         dummy_erh->flags |= ER_FLAG_DVALUES_VALID;
    1527                 :             : 
    1528                 :             :         /* Caller error if fnumber is system column or nonexistent column */
    1529   [ #  #  #  # ]:           0 :         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                 :           0 :         dummy_erh->dvalues[fnumber - 1] = newValue;
    1534                 :           0 :         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         [ #  # ]:           0 :         if (!isnull)
    1543                 :             :         {
    1544                 :           0 :                 CompactAttribute *attr = TupleDescCompactAttr(erh->er_tupdesc, fnumber - 1);
    1545                 :             : 
    1546   [ #  #  #  #  :           0 :                 if (!attr->attbyval && attr->attlen == -1 &&
                   #  # ]
    1547                 :           0 :                         VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
    1548                 :           0 :                         dummy_erh->flags |= ER_FLAG_HAVE_EXTERNAL;
    1549                 :           0 :         }
    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                 :           0 :         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                 :           0 :         domain_check(ExpandedRecordGetRODatum(dummy_erh), false,
    1562                 :           0 :                                  erh->er_decltypeid,
    1563                 :           0 :                                  &erh->er_domaininfo,
    1564                 :           0 :                                  erh->hdr.eoh_context);
    1565                 :             : 
    1566                 :           0 :         MemoryContextSwitchTo(oldcxt);
    1567                 :             : 
    1568                 :             :         /* We might as well clean up cruft immediately. */
    1569                 :           0 :         MemoryContextReset(erh->er_short_term_cxt);
    1570                 :           0 : }
    1571                 :             : 
    1572                 :             : /*
    1573                 :             :  * Precheck domain constraints for a set_tuple operation
    1574                 :             :  */
    1575                 :             : static pg_noinline void
    1576                 :           0 : check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple)
    1577                 :             : {
    1578                 :           0 :         ExpandedRecordHeader *dummy_erh;
    1579                 :           0 :         MemoryContext oldcxt;
    1580                 :             : 
    1581                 :             :         /* If we're being told to set record to empty, just see if NULL is OK */
    1582         [ #  # ]:           0 :         if (tuple == NULL)
    1583                 :             :         {
    1584                 :             :                 /* We run domain_check in a short-lived context to limit cruft */
    1585                 :           0 :                 oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
    1586                 :             : 
    1587                 :           0 :                 domain_check((Datum) 0, true,
    1588                 :           0 :                                          erh->er_decltypeid,
    1589                 :           0 :                                          &erh->er_domaininfo,
    1590                 :           0 :                                          erh->hdr.eoh_context);
    1591                 :             : 
    1592                 :           0 :                 MemoryContextSwitchTo(oldcxt);
    1593                 :             : 
    1594                 :             :                 /* We might as well clean up cruft immediately. */
    1595                 :           0 :                 MemoryContextReset(erh->er_short_term_cxt);
    1596                 :             : 
    1597                 :           0 :                 return;
    1598                 :             :         }
    1599                 :             : 
    1600                 :             :         /* Construct dummy header to contain replacement tuple */
    1601                 :           0 :         build_dummy_expanded_header(erh);
    1602                 :           0 :         dummy_erh = erh->er_dummy_header;
    1603                 :             : 
    1604                 :             :         /* Insert tuple, but don't bother to deconstruct its fields for now */
    1605                 :           0 :         dummy_erh->fvalue = tuple;
    1606                 :           0 :         dummy_erh->fstartptr = (char *) tuple->t_data;
    1607                 :           0 :         dummy_erh->fendptr = ((char *) tuple->t_data) + tuple->t_len;
    1608                 :           0 :         dummy_erh->flags |= ER_FLAG_FVALUE_VALID;
    1609                 :             : 
    1610                 :             :         /* Remember if we have any out-of-line field values */
    1611         [ #  # ]:           0 :         if (HeapTupleHasExternal(tuple))
    1612                 :           0 :                 dummy_erh->flags |= ER_FLAG_HAVE_EXTERNAL;
    1613                 :             : 
    1614                 :             :         /*
    1615                 :             :          * We call domain_check in the short-lived context, so that any cruft
    1616                 :             :          * leaked by expression evaluation can be reclaimed.
    1617                 :             :          */
    1618                 :           0 :         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                 :           0 :         domain_check(ExpandedRecordGetRODatum(dummy_erh), false,
    1625                 :           0 :                                  erh->er_decltypeid,
    1626                 :           0 :                                  &erh->er_domaininfo,
    1627                 :           0 :                                  erh->hdr.eoh_context);
    1628                 :             : 
    1629                 :           0 :         MemoryContextSwitchTo(oldcxt);
    1630                 :             : 
    1631                 :             :         /* We might as well clean up cruft immediately. */
    1632                 :           0 :         MemoryContextReset(erh->er_short_term_cxt);
    1633         [ #  # ]:           0 : }
        

Generated by: LCOV version 2.3.2-1