/*------------------------------------------------------------------------- * * expandedrecord.c * Functions for manipulating composite expanded objects. * * This module supports "expanded objects" (cf. expandeddatum.h) that can * store values of named composite types, domains over named composite types, * and record types (registered or anonymous). * * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/utils/adt/expandedrecord.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/detoast.h" #include "access/heaptoast.h" #include "access/htup_details.h" #include "catalog/heap.h" #include "catalog/pg_type.h" #include "utils/builtins.h" #include "utils/datum.h" #include "utils/expandedrecord.h" #include "utils/memutils.h" #include "utils/typcache.h" /* "Methods" required for an expanded object */ static Size ER_get_flat_size(ExpandedObjectHeader *eohptr); static void ER_flatten_into(ExpandedObjectHeader *eohptr, void *result, Size allocated_size); static const ExpandedObjectMethods ER_methods = { ER_get_flat_size, ER_flatten_into }; /* Other local functions */ static void ER_mc_callback(void *arg); static MemoryContext get_short_term_cxt(ExpandedRecordHeader *erh); static void build_dummy_expanded_header(ExpandedRecordHeader *main_erh); static pg_noinline void check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber, Datum newValue, bool isnull); static pg_noinline void check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple); /* * Build an expanded record of the specified composite type * * type_id can be RECORDOID, but only if a positive typmod is given. * * The expanded record is initially "empty", having a state logically * equivalent to a NULL composite value (not ROW(NULL, NULL, ...)). * Note that this might not be a valid state for a domain type; * if the caller needs to check that, call * expanded_record_set_tuple(erh, NULL, false, false). * * The expanded object will be a child of parentcontext. */ ExpandedRecordHeader * make_expanded_record_from_typeid(Oid type_id, int32 typmod, MemoryContext parentcontext) { ExpandedRecordHeader *erh; int flags = 0; TupleDesc tupdesc; uint64 tupdesc_id; MemoryContext objcxt; char *chunk; if (type_id != RECORDOID) { /* * Consult the typcache to see if it's a domain over composite, and in * any case to get the tupdesc and tupdesc identifier. */ TypeCacheEntry *typentry; typentry = lookup_type_cache(type_id, TYPECACHE_TUPDESC | TYPECACHE_DOMAIN_BASE_INFO); if (typentry->typtype == TYPTYPE_DOMAIN) { flags |= ER_FLAG_IS_DOMAIN; typentry = lookup_type_cache(typentry->domainBaseType, TYPECACHE_TUPDESC); } if (typentry->tupDesc == NULL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("type %s is not composite", format_type_be(type_id)))); tupdesc = typentry->tupDesc; tupdesc_id = typentry->tupDesc_identifier; } else { /* * For RECORD types, get the tupdesc and identifier from typcache. */ tupdesc = lookup_rowtype_tupdesc(type_id, typmod); tupdesc_id = assign_record_type_identifier(type_id, typmod); } /* * Allocate private context for expanded object. We use a regular-size * context, not a small one, to improve the odds that we can fit a tupdesc * into it without needing an extra malloc block. (This code path doesn't * ever need to copy a tupdesc into the expanded record, but let's be * consistent with the other ways of making an expanded record.) */ objcxt = AllocSetContextCreate(parentcontext, "expanded record", ALLOCSET_DEFAULT_SIZES); /* * Since we already know the number of fields in the tupdesc, we can * allocate the dvalues/dnulls arrays along with the record header. This * is useless if we never need those arrays, but it costs almost nothing, * and it will save a palloc cycle if we do need them. */ erh = (ExpandedRecordHeader *) MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader)) + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); /* Ensure all header fields are initialized to 0/null */ memset(erh, 0, sizeof(ExpandedRecordHeader)); EOH_init_header(&erh->hdr, &ER_methods, objcxt); erh->er_magic = ER_MAGIC; /* Set up dvalues/dnulls, with no valid contents as yet */ chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); erh->dvalues = (Datum *) chunk; erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); erh->nfields = tupdesc->natts; /* Fill in composite-type identification info */ erh->er_decltypeid = type_id; erh->er_typeid = tupdesc->tdtypeid; erh->er_typmod = tupdesc->tdtypmod; erh->er_tupdesc_id = tupdesc_id; erh->flags = flags; /* * If what we got from the typcache is a refcounted tupdesc, we need to * acquire our own refcount on it. We manage the refcount with a memory * context callback rather than assuming that the CurrentResourceOwner is * longer-lived than this expanded object. */ if (tupdesc->tdrefcount >= 0) { /* Register callback to release the refcount */ erh->er_mcb.func = ER_mc_callback; erh->er_mcb.arg = (void *) erh; MemoryContextRegisterResetCallback(erh->hdr.eoh_context, &erh->er_mcb); /* And save the pointer */ erh->er_tupdesc = tupdesc; tupdesc->tdrefcount++; /* If we called lookup_rowtype_tupdesc, release the pin it took */ if (type_id == RECORDOID) DecrTupleDescRefCount(tupdesc); } else { /* * If it's not refcounted, just assume it will outlive the expanded * object. (This can happen for shared record types, for instance.) */ erh->er_tupdesc = tupdesc; } /* * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the * record remains logically empty. */ return erh; } /* * Build an expanded record of the rowtype defined by the tupdesc * * The tupdesc is copied if necessary (i.e., if we can't just bump its * reference count instead). * * The expanded record is initially "empty", having a state logically * equivalent to a NULL composite value (not ROW(NULL, NULL, ...)). * * The expanded object will be a child of parentcontext. */ ExpandedRecordHeader * make_expanded_record_from_tupdesc(TupleDesc tupdesc, MemoryContext parentcontext) { ExpandedRecordHeader *erh; uint64 tupdesc_id; MemoryContext objcxt; MemoryContext oldcxt; char *chunk; if (tupdesc->tdtypeid != RECORDOID) { /* * If it's a named composite type (not RECORD), we prefer to reference * the typcache's copy of the tupdesc, which is guaranteed to be * refcounted (the given tupdesc might not be). In any case, we need * to consult the typcache to get the correct tupdesc identifier. * * Note that tdtypeid couldn't be a domain type, so we need not * consider that case here. */ TypeCacheEntry *typentry; typentry = lookup_type_cache(tupdesc->tdtypeid, TYPECACHE_TUPDESC); if (typentry->tupDesc == NULL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("type %s is not composite", format_type_be(tupdesc->tdtypeid)))); tupdesc = typentry->tupDesc; tupdesc_id = typentry->tupDesc_identifier; } else { /* * For RECORD types, get the appropriate unique identifier (possibly * freshly assigned). */ tupdesc_id = assign_record_type_identifier(tupdesc->tdtypeid, tupdesc->tdtypmod); } /* * Allocate private context for expanded object. We use a regular-size * context, not a small one, to improve the odds that we can fit a tupdesc * into it without needing an extra malloc block. */ objcxt = AllocSetContextCreate(parentcontext, "expanded record", ALLOCSET_DEFAULT_SIZES); /* * Since we already know the number of fields in the tupdesc, we can * allocate the dvalues/dnulls arrays along with the record header. This * is useless if we never need those arrays, but it costs almost nothing, * and it will save a palloc cycle if we do need them. */ erh = (ExpandedRecordHeader *) MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader)) + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); /* Ensure all header fields are initialized to 0/null */ memset(erh, 0, sizeof(ExpandedRecordHeader)); EOH_init_header(&erh->hdr, &ER_methods, objcxt); erh->er_magic = ER_MAGIC; /* Set up dvalues/dnulls, with no valid contents as yet */ chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); erh->dvalues = (Datum *) chunk; erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); erh->nfields = tupdesc->natts; /* Fill in composite-type identification info */ erh->er_decltypeid = erh->er_typeid = tupdesc->tdtypeid; erh->er_typmod = tupdesc->tdtypmod; erh->er_tupdesc_id = tupdesc_id; /* * Copy tupdesc if needed, but we prefer to bump its refcount if possible. * We manage the refcount with a memory context callback rather than * assuming that the CurrentResourceOwner is longer-lived than this * expanded object. */ if (tupdesc->tdrefcount >= 0) { /* Register callback to release the refcount */ erh->er_mcb.func = ER_mc_callback; erh->er_mcb.arg = (void *) erh; MemoryContextRegisterResetCallback(erh->hdr.eoh_context, &erh->er_mcb); /* And save the pointer */ erh->er_tupdesc = tupdesc; tupdesc->tdrefcount++; } else { /* Just copy it */ oldcxt = MemoryContextSwitchTo(objcxt); erh->er_tupdesc = CreateTupleDescCopy(tupdesc); erh->flags |= ER_FLAG_TUPDESC_ALLOCED; MemoryContextSwitchTo(oldcxt); } /* * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the * record remains logically empty. */ return erh; } /* * Build an expanded record of the same rowtype as the given expanded record * * This is faster than either of the above routines because we can bypass * typcache lookup(s). * * The expanded record is initially "empty" --- we do not copy whatever * tuple might be in the source expanded record. * * The expanded object will be a child of parentcontext. */ ExpandedRecordHeader * make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh, MemoryContext parentcontext) { ExpandedRecordHeader *erh; TupleDesc tupdesc = expanded_record_get_tupdesc(olderh); MemoryContext objcxt; MemoryContext oldcxt; char *chunk; /* * Allocate private context for expanded object. We use a regular-size * context, not a small one, to improve the odds that we can fit a tupdesc * into it without needing an extra malloc block. */ objcxt = AllocSetContextCreate(parentcontext, "expanded record", ALLOCSET_DEFAULT_SIZES); /* * Since we already know the number of fields in the tupdesc, we can * allocate the dvalues/dnulls arrays along with the record header. This * is useless if we never need those arrays, but it costs almost nothing, * and it will save a palloc cycle if we do need them. */ erh = (ExpandedRecordHeader *) MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader)) + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); /* Ensure all header fields are initialized to 0/null */ memset(erh, 0, sizeof(ExpandedRecordHeader)); EOH_init_header(&erh->hdr, &ER_methods, objcxt); erh->er_magic = ER_MAGIC; /* Set up dvalues/dnulls, with no valid contents as yet */ chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); erh->dvalues = (Datum *) chunk; erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); erh->nfields = tupdesc->natts; /* Fill in composite-type identification info */ erh->er_decltypeid = olderh->er_decltypeid; erh->er_typeid = olderh->er_typeid; erh->er_typmod = olderh->er_typmod; erh->er_tupdesc_id = olderh->er_tupdesc_id; /* The only flag bit that transfers over is IS_DOMAIN */ erh->flags = olderh->flags & ER_FLAG_IS_DOMAIN; /* * Copy tupdesc if needed, but we prefer to bump its refcount if possible. * We manage the refcount with a memory context callback rather than * assuming that the CurrentResourceOwner is longer-lived than this * expanded object. */ if (tupdesc->tdrefcount >= 0) { /* Register callback to release the refcount */ erh->er_mcb.func = ER_mc_callback; erh->er_mcb.arg = (void *) erh; MemoryContextRegisterResetCallback(erh->hdr.eoh_context, &erh->er_mcb); /* And save the pointer */ erh->er_tupdesc = tupdesc; tupdesc->tdrefcount++; } else if (olderh->flags & ER_FLAG_TUPDESC_ALLOCED) { /* We need to make our own copy of the tupdesc */ oldcxt = MemoryContextSwitchTo(objcxt); erh->er_tupdesc = CreateTupleDescCopy(tupdesc); erh->flags |= ER_FLAG_TUPDESC_ALLOCED; MemoryContextSwitchTo(oldcxt); } else { /* * Assume the tupdesc will outlive this expanded object, just like * we're assuming it will outlive the source object. */ erh->er_tupdesc = tupdesc; } /* * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the * record remains logically empty. */ return erh; } /* * Insert given tuple as the value of the expanded record * * It is caller's responsibility that the tuple matches the record's * previously-assigned rowtype. (However domain constraints, if any, * will be checked here.) * * The tuple is physically copied into the expanded record's local storage * if "copy" is true, otherwise it's caller's responsibility that the tuple * will live as long as the expanded record does. * * Out-of-line field values in the tuple are automatically inlined if * "expand_external" is true, otherwise not. (The combination copy = false, * expand_external = true is not sensible and not supported.) * * Alternatively, tuple can be NULL, in which case we just set the expanded * record to be empty. */ void expanded_record_set_tuple(ExpandedRecordHeader *erh, HeapTuple tuple, bool copy, bool expand_external) { int oldflags; HeapTuple oldtuple; char *oldfstartptr; char *oldfendptr; int newflags; HeapTuple newtuple; MemoryContext oldcxt; /* Shouldn't ever be trying to assign new data to a dummy header */ Assert(!(erh->flags & ER_FLAG_IS_DUMMY)); /* * Before performing the assignment, see if result will satisfy domain. */ if (erh->flags & ER_FLAG_IS_DOMAIN) check_domain_for_new_tuple(erh, tuple); /* * If we need to get rid of out-of-line field values, do so, using the * short-term context to avoid leaking whatever cruft the toast fetch * might generate. */ if (expand_external && tuple) { /* Assert caller didn't ask for unsupported case */ Assert(copy); if (HeapTupleHasExternal(tuple)) { oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh)); tuple = toast_flatten_tuple(tuple, erh->er_tupdesc); MemoryContextSwitchTo(oldcxt); } else expand_external = false; /* need not clean up below */ } /* * Initialize new flags, keeping only non-data status bits. */ oldflags = erh->flags; newflags = oldflags & ER_FLAGS_NON_DATA; /* * Copy tuple into local storage if needed. We must be sure this succeeds * before we start to modify the expanded record's state. */ if (copy && tuple) { oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context); newtuple = heap_copytuple(tuple); newflags |= ER_FLAG_FVALUE_ALLOCED; MemoryContextSwitchTo(oldcxt); /* We can now flush anything that detoasting might have leaked. */ if (expand_external) MemoryContextReset(erh->er_short_term_cxt); } else newtuple = tuple; /* Make copies of fields we're about to overwrite */ oldtuple = erh->fvalue; oldfstartptr = erh->fstartptr; oldfendptr = erh->fendptr; /* * It's now safe to update the expanded record's state. */ if (newtuple) { /* Save flat representation */ erh->fvalue = newtuple; erh->fstartptr = (char *) newtuple->t_data; erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len; newflags |= ER_FLAG_FVALUE_VALID; /* Remember if we have any out-of-line field values */ if (HeapTupleHasExternal(newtuple)) newflags |= ER_FLAG_HAVE_EXTERNAL; } else { erh->fvalue = NULL; erh->fstartptr = erh->fendptr = NULL; } erh->flags = newflags; /* Reset flat-size info; we don't bother to make it valid now */ erh->flat_size = 0; /* * Now, release any storage belonging to old field values. It's safe to * do this because ER_FLAG_DVALUES_VALID is no longer set in erh->flags; * even if we fail partway through, the record is valid, and at worst * we've failed to reclaim some space. */ if (oldflags & ER_FLAG_DVALUES_ALLOCED) { TupleDesc tupdesc = erh->er_tupdesc; int i; for (i = 0; i < erh->nfields; i++) { if (!erh->dnulls[i] && !(TupleDescAttr(tupdesc, i)->attbyval)) { char *oldValue = (char *) DatumGetPointer(erh->dvalues[i]); if (oldValue < oldfstartptr || oldValue >= oldfendptr) pfree(oldValue); } } } /* Likewise free the old tuple, if it was locally allocated */ if (oldflags & ER_FLAG_FVALUE_ALLOCED) heap_freetuple(oldtuple); /* We won't make a new deconstructed representation until/unless needed */ } /* * make_expanded_record_from_datum: build expanded record from composite Datum * * This combines the functions of make_expanded_record_from_typeid and * expanded_record_set_tuple. However, we do not force a lookup of the * tupdesc immediately, reasoning that it might never be needed. * * The expanded object will be a child of parentcontext. * * Note: a composite datum cannot self-identify as being of a domain type, * so we need not consider domain cases here. */ Datum make_expanded_record_from_datum(Datum recorddatum, MemoryContext parentcontext) { ExpandedRecordHeader *erh; HeapTupleHeader tuphdr; HeapTupleData tmptup; HeapTuple newtuple; MemoryContext objcxt; MemoryContext oldcxt; /* * Allocate private context for expanded object. We use a regular-size * context, not a small one, to improve the odds that we can fit a tupdesc * into it without needing an extra malloc block. */ objcxt = AllocSetContextCreate(parentcontext, "expanded record", ALLOCSET_DEFAULT_SIZES); /* Set up expanded record header, initializing fields to 0/null */ erh = (ExpandedRecordHeader *) MemoryContextAllocZero(objcxt, sizeof(ExpandedRecordHeader)); EOH_init_header(&erh->hdr, &ER_methods, objcxt); erh->er_magic = ER_MAGIC; /* * Detoast and copy source record into private context, as a HeapTuple. * (If we actually have to detoast the source, we'll leak some memory in * the caller's context, but it doesn't seem worth worrying about.) */ tuphdr = DatumGetHeapTupleHeader(recorddatum); tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr); ItemPointerSetInvalid(&(tmptup.t_self)); tmptup.t_tableOid = InvalidOid; tmptup.t_data = tuphdr; oldcxt = MemoryContextSwitchTo(objcxt); newtuple = heap_copytuple(&tmptup); erh->flags |= ER_FLAG_FVALUE_ALLOCED; MemoryContextSwitchTo(oldcxt); /* Fill in composite-type identification info */ erh->er_decltypeid = erh->er_typeid = HeapTupleHeaderGetTypeId(tuphdr); erh->er_typmod = HeapTupleHeaderGetTypMod(tuphdr); /* remember we have a flat representation */ erh->fvalue = newtuple; erh->fstartptr = (char *) newtuple->t_data; erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len; erh->flags |= ER_FLAG_FVALUE_VALID; /* Shouldn't need to set ER_FLAG_HAVE_EXTERNAL */ Assert(!HeapTupleHeaderHasExternal(tuphdr)); /* * We won't look up the tupdesc till we have to, nor make a deconstructed * representation. We don't have enough info to fill flat_size and * friends, either. */ /* return a R/W pointer to the expanded record */ return EOHPGetRWDatum(&erh->hdr); } /* * get_flat_size method for expanded records * * Note: call this in a reasonably short-lived memory context, in case of * memory leaks from activities such as detoasting. */ static Size ER_get_flat_size(ExpandedObjectHeader *eohptr) { ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr; TupleDesc tupdesc; Size len; Size data_len; int hoff; bool hasnull; int i; Assert(erh->er_magic == ER_MAGIC); /* * The flat representation has to be a valid composite datum. Make sure * that we have a registered, not anonymous, RECORD type. */ if (erh->er_typeid == RECORDOID && erh->er_typmod < 0) { tupdesc = expanded_record_get_tupdesc(erh); assign_record_type_typmod(tupdesc); erh->er_typmod = tupdesc->tdtypmod; } /* * If we have a valid flattened value without out-of-line fields, we can * just use it as-is. */ if (erh->flags & ER_FLAG_FVALUE_VALID && !(erh->flags & ER_FLAG_HAVE_EXTERNAL)) return erh->fvalue->t_len; /* If we have a cached size value, believe that */ if (erh->flat_size) return erh->flat_size; /* If we haven't yet deconstructed the tuple, do that */ if (!(erh->flags & ER_FLAG_DVALUES_VALID)) deconstruct_expanded_record(erh); /* Tuple descriptor must be valid by now */ tupdesc = erh->er_tupdesc; /* * Composite datums mustn't contain any out-of-line values. */ if (erh->flags & ER_FLAG_HAVE_EXTERNAL) { for (i = 0; i < erh->nfields; i++) { Form_pg_attribute attr = TupleDescAttr(tupdesc, i); if (!erh->dnulls[i] && !attr->attbyval && attr->attlen == -1 && VARATT_IS_EXTERNAL(DatumGetPointer(erh->dvalues[i]))) { /* * expanded_record_set_field_internal can do the actual work * of detoasting. It needn't recheck domain constraints. */ expanded_record_set_field_internal(erh, i + 1, erh->dvalues[i], false, true, false); } } /* * We have now removed all external field values, so we can clear the * flag about them. This won't cause ER_flatten_into() to mistakenly * take the fast path, since expanded_record_set_field() will have * cleared ER_FLAG_FVALUE_VALID. */ erh->flags &= ~ER_FLAG_HAVE_EXTERNAL; } /* Test if we currently have any null values */ hasnull = false; for (i = 0; i < erh->nfields; i++) { if (erh->dnulls[i]) { hasnull = true; break; } } /* Determine total space needed */ len = offsetof(HeapTupleHeaderData, t_bits); if (hasnull) len += BITMAPLEN(tupdesc->natts); hoff = len = MAXALIGN(len); /* align user data safely */ data_len = heap_compute_data_size(tupdesc, erh->dvalues, erh->dnulls); len += data_len; /* Cache for next time */ erh->flat_size = len; erh->data_len = data_len; erh->hoff = hoff; erh->hasnull = hasnull; return len; } /* * flatten_into method for expanded records */ static void ER_flatten_into(ExpandedObjectHeader *eohptr, void *result, Size allocated_size) { ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr; HeapTupleHeader tuphdr = (HeapTupleHeader) result; TupleDesc tupdesc; Assert(erh->er_magic == ER_MAGIC); /* Easy if we have a valid flattened value without out-of-line fields */ if (erh->flags & ER_FLAG_FVALUE_VALID && !(erh->flags & ER_FLAG_HAVE_EXTERNAL)) { Assert(allocated_size == erh->fvalue->t_len); memcpy(tuphdr, erh->fvalue->t_data, allocated_size); /* The original flattened value might not have datum header fields */ HeapTupleHeaderSetDatumLength(tuphdr, allocated_size); HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid); HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod); return; } /* Else allocation should match previous get_flat_size result */ Assert(allocated_size == erh->flat_size); /* We'll need the tuple descriptor */ tupdesc = expanded_record_get_tupdesc(erh); /* We must ensure that any pad space is zero-filled */ memset(tuphdr, 0, allocated_size); /* Set up header fields of composite Datum */ HeapTupleHeaderSetDatumLength(tuphdr, allocated_size); HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid); HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod); /* We also make sure that t_ctid is invalid unless explicitly set */ ItemPointerSetInvalid(&(tuphdr->t_ctid)); HeapTupleHeaderSetNatts(tuphdr, tupdesc->natts); tuphdr->t_hoff = erh->hoff; /* And fill the data area from dvalues/dnulls */ heap_fill_tuple(tupdesc, erh->dvalues, erh->dnulls, (char *) tuphdr + erh->hoff, erh->data_len, &tuphdr->t_infomask, (erh->hasnull ? tuphdr->t_bits : NULL)); } /* * Look up the tupdesc for the expanded record's actual type * * Note: code internal to this module is allowed to just fetch * erh->er_tupdesc if ER_FLAG_DVALUES_VALID is set; otherwise it should call * expanded_record_get_tupdesc. This function is the out-of-line portion * of expanded_record_get_tupdesc. */ TupleDesc expanded_record_fetch_tupdesc(ExpandedRecordHeader *erh) { TupleDesc tupdesc; /* Easy if we already have it (but caller should have checked already) */ if (erh->er_tupdesc) return erh->er_tupdesc; /* Lookup the composite type's tupdesc using the typcache */ tupdesc = lookup_rowtype_tupdesc(erh->er_typeid, erh->er_typmod); /* * If it's a refcounted tupdesc rather than a statically allocated one, we * want to manage the refcount with a memory context callback rather than * assuming that the CurrentResourceOwner is longer-lived than this * expanded object. */ if (tupdesc->tdrefcount >= 0) { /* Register callback if we didn't already */ if (erh->er_mcb.arg == NULL) { erh->er_mcb.func = ER_mc_callback; erh->er_mcb.arg = (void *) erh; MemoryContextRegisterResetCallback(erh->hdr.eoh_context, &erh->er_mcb); } /* Remember our own pointer */ erh->er_tupdesc = tupdesc; tupdesc->tdrefcount++; /* Release the pin lookup_rowtype_tupdesc acquired */ DecrTupleDescRefCount(tupdesc); } else { /* Just remember the pointer */ erh->er_tupdesc = tupdesc; } /* In either case, fetch the process-global ID for this tupdesc */ erh->er_tupdesc_id = assign_record_type_identifier(tupdesc->tdtypeid, tupdesc->tdtypmod); return tupdesc; } /* * Get a HeapTuple representing the current value of the expanded record * * If valid, the originally stored tuple is returned, so caller must not * scribble on it. Otherwise, we return a HeapTuple created in the current * memory context. In either case, no attempt has been made to inline * out-of-line toasted values, so the tuple isn't usable as a composite * datum. * * Returns NULL if expanded record is empty. */ HeapTuple expanded_record_get_tuple(ExpandedRecordHeader *erh) { /* Easy case if we still have original tuple */ if (erh->flags & ER_FLAG_FVALUE_VALID) return erh->fvalue; /* Else just build a tuple from datums */ if (erh->flags & ER_FLAG_DVALUES_VALID) return heap_form_tuple(erh->er_tupdesc, erh->dvalues, erh->dnulls); /* Expanded record is empty */ return NULL; } /* * Memory context reset callback for cleaning up external resources */ static void ER_mc_callback(void *arg) { ExpandedRecordHeader *erh = (ExpandedRecordHeader *) arg; TupleDesc tupdesc = erh->er_tupdesc; /* Release our privately-managed tupdesc refcount, if any */ if (tupdesc) { erh->er_tupdesc = NULL; /* just for luck */ if (tupdesc->tdrefcount > 0) { if (--tupdesc->tdrefcount == 0) FreeTupleDesc(tupdesc); } } } /* * DatumGetExpandedRecord: get a writable expanded record from an input argument * * Caution: if the input is a read/write pointer, this returns the input * argument; so callers must be sure that their changes are "safe", that is * they cannot leave the record in a corrupt state. */ ExpandedRecordHeader * DatumGetExpandedRecord(Datum d) { /* If it's a writable expanded record already, just return it */ if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) { ExpandedRecordHeader *erh = (ExpandedRecordHeader *) DatumGetEOHP(d); Assert(erh->er_magic == ER_MAGIC); return erh; } /* Else expand the hard way */ d = make_expanded_record_from_datum(d, CurrentMemoryContext); return (ExpandedRecordHeader *) DatumGetEOHP(d); } /* * Create the Datum/isnull representation of an expanded record object * if we didn't do so already. After calling this, it's OK to read the * dvalues/dnulls arrays directly, rather than going through get_field. * * Note that if the object is currently empty ("null"), this will change * it to represent a row of nulls. */ void deconstruct_expanded_record(ExpandedRecordHeader *erh) { TupleDesc tupdesc; Datum *dvalues; bool *dnulls; int nfields; if (erh->flags & ER_FLAG_DVALUES_VALID) return; /* already valid, nothing to do */ /* We'll need the tuple descriptor */ tupdesc = expanded_record_get_tupdesc(erh); /* * Allocate arrays in private context, if we don't have them already. We * don't expect to see a change in nfields here, so while we cope if it * happens, we don't bother avoiding a leak of the old arrays (which might * not be separately palloc'd, anyway). */ nfields = tupdesc->natts; if (erh->dvalues == NULL || erh->nfields != nfields) { char *chunk; /* * To save a palloc cycle, we allocate both the Datum and isnull * arrays in one palloc chunk. */ chunk = MemoryContextAlloc(erh->hdr.eoh_context, nfields * (sizeof(Datum) + sizeof(bool))); dvalues = (Datum *) chunk; dnulls = (bool *) (chunk + nfields * sizeof(Datum)); erh->dvalues = dvalues; erh->dnulls = dnulls; erh->nfields = nfields; } else { dvalues = erh->dvalues; dnulls = erh->dnulls; } if (erh->flags & ER_FLAG_FVALUE_VALID) { /* Deconstruct tuple */ heap_deform_tuple(erh->fvalue, tupdesc, dvalues, dnulls); } else { /* If record was empty, instantiate it as a row of nulls */ memset(dvalues, 0, nfields * sizeof(Datum)); memset(dnulls, true, nfields * sizeof(bool)); } /* Mark the dvalues as valid */ erh->flags |= ER_FLAG_DVALUES_VALID; } /* * Look up a record field by name * * If there is a field named "fieldname", fill in the contents of finfo * and return "true". Else return "false" without changing *finfo. */ bool expanded_record_lookup_field(ExpandedRecordHeader *erh, const char *fieldname, ExpandedRecordFieldInfo *finfo) { TupleDesc tupdesc; int fno; Form_pg_attribute attr; const FormData_pg_attribute *sysattr; tupdesc = expanded_record_get_tupdesc(erh); /* First, check user-defined attributes */ for (fno = 0; fno < tupdesc->natts; fno++) { attr = TupleDescAttr(tupdesc, fno); if (namestrcmp(&attr->attname, fieldname) == 0 && !attr->attisdropped) { finfo->fnumber = attr->attnum; finfo->ftypeid = attr->atttypid; finfo->ftypmod = attr->atttypmod; finfo->fcollation = attr->attcollation; return true; } } /* How about system attributes? */ sysattr = SystemAttributeByName(fieldname); if (sysattr != NULL) { finfo->fnumber = sysattr->attnum; finfo->ftypeid = sysattr->atttypid; finfo->ftypmod = sysattr->atttypmod; finfo->fcollation = sysattr->attcollation; return true; } return false; } /* * Fetch value of record field * * expanded_record_get_field is the frontend for this; it handles the * easy inline-able cases. */ Datum expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber, bool *isnull) { if (fnumber > 0) { /* Empty record has null fields */ if (ExpandedRecordIsEmpty(erh)) { *isnull = true; return (Datum) 0; } /* Make sure we have deconstructed form */ deconstruct_expanded_record(erh); /* Out-of-range field number reads as null */ if (unlikely(fnumber > erh->nfields)) { *isnull = true; return (Datum) 0; } *isnull = erh->dnulls[fnumber - 1]; return erh->dvalues[fnumber - 1]; } else { /* System columns read as null if we haven't got flat tuple */ if (erh->fvalue == NULL) { *isnull = true; return (Datum) 0; } /* heap_getsysattr doesn't actually use tupdesc, so just pass null */ return heap_getsysattr(erh->fvalue, fnumber, NULL, isnull); } } /* * Set value of record field * * If the expanded record is of domain type, the assignment will be rejected * (without changing the record's state) if the domain's constraints would * be violated. * * If expand_external is true and newValue is an out-of-line value, we'll * forcibly detoast it so that the record does not depend on external storage. * * Internal callers can pass check_constraints = false to skip application * of domain constraints. External callers should never do that. */ void expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber, Datum newValue, bool isnull, bool expand_external, bool check_constraints) { TupleDesc tupdesc; Form_pg_attribute attr; Datum *dvalues; bool *dnulls; char *oldValue; /* * Shouldn't ever be trying to assign new data to a dummy header, except * in the case of an internal call for field inlining. */ Assert(!(erh->flags & ER_FLAG_IS_DUMMY) || !check_constraints); /* Before performing the assignment, see if result will satisfy domain */ if ((erh->flags & ER_FLAG_IS_DOMAIN) && check_constraints) check_domain_for_new_field(erh, fnumber, newValue, isnull); /* If we haven't yet deconstructed the tuple, do that */ if (!(erh->flags & ER_FLAG_DVALUES_VALID)) deconstruct_expanded_record(erh); /* Tuple descriptor must be valid by now */ tupdesc = erh->er_tupdesc; Assert(erh->nfields == tupdesc->natts); /* Caller error if fnumber is system column or nonexistent column */ if (unlikely(fnumber <= 0 || fnumber > erh->nfields)) elog(ERROR, "cannot assign to field %d of expanded record", fnumber); /* * Copy new field value into record's context, and deal with detoasting, * if needed. */ attr = TupleDescAttr(tupdesc, fnumber - 1); if (!isnull && !attr->attbyval) { MemoryContext oldcxt; /* If requested, detoast any external value */ if (expand_external) { if (attr->attlen == -1 && VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) { /* Detoasting should be done in short-lived context. */ oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh)); newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue))); MemoryContextSwitchTo(oldcxt); } else expand_external = false; /* need not clean up below */ } /* Copy value into record's context */ oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context); newValue = datumCopy(newValue, false, attr->attlen); MemoryContextSwitchTo(oldcxt); /* We can now flush anything that detoasting might have leaked */ if (expand_external) MemoryContextReset(erh->er_short_term_cxt); /* Remember that we have field(s) that may need to be pfree'd */ erh->flags |= ER_FLAG_DVALUES_ALLOCED; /* * While we're here, note whether it's an external toasted value, * because that could mean we need to inline it later. (Think not to * merge this into the previous expand_external logic: datumCopy could * by itself have made the value non-external.) */ if (attr->attlen == -1 && VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) erh->flags |= ER_FLAG_HAVE_EXTERNAL; } /* * We're ready to make irreversible changes. */ dvalues = erh->dvalues; dnulls = erh->dnulls; /* Flattened value will no longer represent record accurately */ erh->flags &= ~ER_FLAG_FVALUE_VALID; /* And we don't know the flattened size either */ erh->flat_size = 0; /* Grab old field value for pfree'ing, if needed. */ if (!attr->attbyval && !dnulls[fnumber - 1]) oldValue = (char *) DatumGetPointer(dvalues[fnumber - 1]); else oldValue = NULL; /* And finally we can insert the new field. */ dvalues[fnumber - 1] = newValue; dnulls[fnumber - 1] = isnull; /* * Free old field if needed; this keeps repeated field replacements from * bloating the record's storage. If the pfree somehow fails, it won't * corrupt the record. * * If we're updating a dummy header, we can't risk pfree'ing the old * value, because most likely the expanded record's main header still has * a pointer to it. This won't result in any sustained memory leak, since * whatever we just allocated here is in the short-lived domain check * context. */ if (oldValue && !(erh->flags & ER_FLAG_IS_DUMMY)) { /* Don't try to pfree a part of the original flat record */ if (oldValue < erh->fstartptr || oldValue >= erh->fendptr) pfree(oldValue); } } /* * Set all record field(s) * * Caller must ensure that the provided datums are of the right types * to match the record's previously assigned rowtype. * * If expand_external is true, we'll forcibly detoast out-of-line field values * so that the record does not depend on external storage. * * Unlike repeated application of expanded_record_set_field(), this does not * guarantee to leave the expanded record in a non-corrupt state in event * of an error. Typically it would only be used for initializing a new * expanded record. Also, because we expect this to be applied at most once * in the lifespan of an expanded record, we do not worry about any cruft * that detoasting might leak. */ void expanded_record_set_fields(ExpandedRecordHeader *erh, const Datum *newValues, const bool *isnulls, bool expand_external) { TupleDesc tupdesc; Datum *dvalues; bool *dnulls; int fnumber; MemoryContext oldcxt; /* Shouldn't ever be trying to assign new data to a dummy header */ Assert(!(erh->flags & ER_FLAG_IS_DUMMY)); /* If we haven't yet deconstructed the tuple, do that */ if (!(erh->flags & ER_FLAG_DVALUES_VALID)) deconstruct_expanded_record(erh); /* Tuple descriptor must be valid by now */ tupdesc = erh->er_tupdesc; Assert(erh->nfields == tupdesc->natts); /* Flattened value will no longer represent record accurately */ erh->flags &= ~ER_FLAG_FVALUE_VALID; /* And we don't know the flattened size either */ erh->flat_size = 0; oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context); dvalues = erh->dvalues; dnulls = erh->dnulls; for (fnumber = 0; fnumber < erh->nfields; fnumber++) { Form_pg_attribute attr = TupleDescAttr(tupdesc, fnumber); Datum newValue; bool isnull; /* Ignore dropped columns */ if (attr->attisdropped) continue; newValue = newValues[fnumber]; isnull = isnulls[fnumber]; if (!attr->attbyval) { /* * Copy new field value into record's context, and deal with * detoasting, if needed. */ if (!isnull) { /* Is it an external toasted value? */ if (attr->attlen == -1 && VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) { if (expand_external) { /* Detoast as requested while copying the value */ newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue))); } else { /* Just copy the value */ newValue = datumCopy(newValue, false, -1); /* If it's still external, remember that */ if (VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) erh->flags |= ER_FLAG_HAVE_EXTERNAL; } } else { /* Not an external value, just copy it */ newValue = datumCopy(newValue, false, attr->attlen); } /* Remember that we have field(s) that need to be pfree'd */ erh->flags |= ER_FLAG_DVALUES_ALLOCED; } /* * Free old field value, if any (not likely, since really we ought * to be inserting into an empty record). */ if (unlikely(!dnulls[fnumber])) { char *oldValue; oldValue = (char *) DatumGetPointer(dvalues[fnumber]); /* Don't try to pfree a part of the original flat record */ if (oldValue < erh->fstartptr || oldValue >= erh->fendptr) pfree(oldValue); } } /* And finally we can insert the new field. */ dvalues[fnumber] = newValue; dnulls[fnumber] = isnull; } /* * Because we don't guarantee atomicity of set_fields(), we can just leave * checking of domain constraints to occur as the final step; if it throws * an error, too bad. */ if (erh->flags & ER_FLAG_IS_DOMAIN) { /* We run domain_check in a short-lived context to limit cruft */ MemoryContextSwitchTo(get_short_term_cxt(erh)); domain_check(ExpandedRecordGetRODatum(erh), false, erh->er_decltypeid, &erh->er_domaininfo, erh->hdr.eoh_context); } MemoryContextSwitchTo(oldcxt); } /* * Construct (or reset) working memory context for short-term operations. * * This context is used for domain check evaluation and for detoasting. * * If we don't have a short-lived memory context, make one; if we have one, * reset it to get rid of any leftover cruft. (It is a tad annoying to need a * whole context for this, since it will often go unused --- but it's hard to * avoid memory leaks otherwise. We can make the context small, at least.) */ static MemoryContext get_short_term_cxt(ExpandedRecordHeader *erh) { if (erh->er_short_term_cxt == NULL) erh->er_short_term_cxt = AllocSetContextCreate(erh->hdr.eoh_context, "expanded record short-term context", ALLOCSET_SMALL_SIZES); else MemoryContextReset(erh->er_short_term_cxt); return erh->er_short_term_cxt; } /* * Construct "dummy header" for checking domain constraints. * * Since we don't want to modify the state of the expanded record until * we've validated the constraints, our approach is to set up a dummy * record header containing the new field value(s) and then pass that to * domain_check. We retain the dummy header as part of the expanded * record's state to save palloc cycles, but reinitialize (most of) * its contents on each use. */ static void build_dummy_expanded_header(ExpandedRecordHeader *main_erh) { ExpandedRecordHeader *erh; TupleDesc tupdesc = expanded_record_get_tupdesc(main_erh); /* Ensure we have a short-lived context */ (void) get_short_term_cxt(main_erh); /* * Allocate dummy header on first time through, or in the unlikely event * that the number of fields changes (in which case we just leak the old * one). Include space for its field values in the request. */ erh = main_erh->er_dummy_header; if (erh == NULL || erh->nfields != tupdesc->natts) { char *chunk; erh = (ExpandedRecordHeader *) MemoryContextAlloc(main_erh->hdr.eoh_context, MAXALIGN(sizeof(ExpandedRecordHeader)) + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); /* Ensure all header fields are initialized to 0/null */ memset(erh, 0, sizeof(ExpandedRecordHeader)); /* * We set up the dummy header with an indication that its memory * context is the short-lived context. This is so that, if any * detoasting of out-of-line values happens due to an attempt to * extract a composite datum from the dummy header, the detoasted * stuff will end up in the short-lived context and not cause a leak. * This is cheating a bit on the expanded-object protocol; but since * we never pass a R/W pointer to the dummy object to any other code, * nothing else is authorized to delete or transfer ownership of the * object's context, so it should be safe enough. */ EOH_init_header(&erh->hdr, &ER_methods, main_erh->er_short_term_cxt); erh->er_magic = ER_MAGIC; /* Set up dvalues/dnulls, with no valid contents as yet */ chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); erh->dvalues = (Datum *) chunk; erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); erh->nfields = tupdesc->natts; /* * The fields we just set are assumed to remain constant through * multiple uses of the dummy header to check domain constraints. All * other dummy header fields should be explicitly reset below, to * ensure there's not accidental effects of one check on the next one. */ main_erh->er_dummy_header = erh; } /* * If anything inquires about the dummy header's declared type, it should * report the composite base type, not the domain type (since the VALUE in * a domain check constraint is of the base type not the domain). Hence * we do not transfer over the IS_DOMAIN flag, nor indeed any of the main * header's flags, since the dummy header is empty of data at this point. * But don't forget to mark header as dummy. */ erh->flags = ER_FLAG_IS_DUMMY; /* Copy composite-type identification info */ erh->er_decltypeid = erh->er_typeid = main_erh->er_typeid; erh->er_typmod = main_erh->er_typmod; /* Dummy header does not need its own tupdesc refcount */ erh->er_tupdesc = tupdesc; erh->er_tupdesc_id = main_erh->er_tupdesc_id; /* * It's tempting to copy over whatever we know about the flat size, but * there's no point since we're surely about to modify the dummy record's * field(s). Instead just clear anything left over from a previous usage * cycle. */ erh->flat_size = 0; /* Copy over fvalue if we have it, so that system columns are available */ erh->fvalue = main_erh->fvalue; erh->fstartptr = main_erh->fstartptr; erh->fendptr = main_erh->fendptr; } /* * Precheck domain constraints for a set_field operation */ static pg_noinline void check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber, Datum newValue, bool isnull) { ExpandedRecordHeader *dummy_erh; MemoryContext oldcxt; /* Construct dummy header to contain proposed new field set */ build_dummy_expanded_header(erh); dummy_erh = erh->er_dummy_header; /* * If record isn't empty, just deconstruct it (if needed) and copy over * the existing field values. If it is empty, just fill fields with nulls * manually --- don't call deconstruct_expanded_record prematurely. */ if (!ExpandedRecordIsEmpty(erh)) { deconstruct_expanded_record(erh); memcpy(dummy_erh->dvalues, erh->dvalues, dummy_erh->nfields * sizeof(Datum)); memcpy(dummy_erh->dnulls, erh->dnulls, dummy_erh->nfields * sizeof(bool)); /* There might be some external values in there... */ dummy_erh->flags |= erh->flags & ER_FLAG_HAVE_EXTERNAL; } else { memset(dummy_erh->dvalues, 0, dummy_erh->nfields * sizeof(Datum)); memset(dummy_erh->dnulls, true, dummy_erh->nfields * sizeof(bool)); } /* Either way, we now have valid dvalues */ dummy_erh->flags |= ER_FLAG_DVALUES_VALID; /* Caller error if fnumber is system column or nonexistent column */ if (unlikely(fnumber <= 0 || fnumber > dummy_erh->nfields)) elog(ERROR, "cannot assign to field %d of expanded record", fnumber); /* Insert proposed new value into dummy field array */ dummy_erh->dvalues[fnumber - 1] = newValue; dummy_erh->dnulls[fnumber - 1] = isnull; /* * The proposed new value might be external, in which case we'd better set * the flag for that in dummy_erh. (This matters in case something in the * domain check expressions tries to extract a flat value from the dummy * header.) */ if (!isnull) { Form_pg_attribute attr = TupleDescAttr(erh->er_tupdesc, fnumber - 1); if (!attr->attbyval && attr->attlen == -1 && VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) dummy_erh->flags |= ER_FLAG_HAVE_EXTERNAL; } /* * We call domain_check in the short-lived context, so that any cruft * leaked by expression evaluation can be reclaimed. */ oldcxt = MemoryContextSwitchTo(erh->er_short_term_cxt); /* * And now we can apply the check. Note we use main header's domain cache * space, so that caching carries across repeated uses. */ domain_check(ExpandedRecordGetRODatum(dummy_erh), false, erh->er_decltypeid, &erh->er_domaininfo, erh->hdr.eoh_context); MemoryContextSwitchTo(oldcxt); /* We might as well clean up cruft immediately. */ MemoryContextReset(erh->er_short_term_cxt); } /* * Precheck domain constraints for a set_tuple operation */ static pg_noinline void check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple) { ExpandedRecordHeader *dummy_erh; MemoryContext oldcxt; /* If we're being told to set record to empty, just see if NULL is OK */ if (tuple == NULL) { /* We run domain_check in a short-lived context to limit cruft */ oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh)); domain_check((Datum) 0, true, erh->er_decltypeid, &erh->er_domaininfo, erh->hdr.eoh_context); MemoryContextSwitchTo(oldcxt); /* We might as well clean up cruft immediately. */ MemoryContextReset(erh->er_short_term_cxt); return; } /* Construct dummy header to contain replacement tuple */ build_dummy_expanded_header(erh); dummy_erh = erh->er_dummy_header; /* Insert tuple, but don't bother to deconstruct its fields for now */ dummy_erh->fvalue = tuple; dummy_erh->fstartptr = (char *) tuple->t_data; dummy_erh->fendptr = ((char *) tuple->t_data) + tuple->t_len; dummy_erh->flags |= ER_FLAG_FVALUE_VALID; /* Remember if we have any out-of-line field values */ if (HeapTupleHasExternal(tuple)) dummy_erh->flags |= ER_FLAG_HAVE_EXTERNAL; /* * We call domain_check in the short-lived context, so that any cruft * leaked by expression evaluation can be reclaimed. */ oldcxt = MemoryContextSwitchTo(erh->er_short_term_cxt); /* * And now we can apply the check. Note we use main header's domain cache * space, so that caching carries across repeated uses. */ domain_check(ExpandedRecordGetRODatum(dummy_erh), false, erh->er_decltypeid, &erh->er_domaininfo, erh->hdr.eoh_context); MemoryContextSwitchTo(oldcxt); /* We might as well clean up cruft immediately. */ MemoryContextReset(erh->er_short_term_cxt); }