aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/expandedrecord.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/expandedrecord.c')
-rw-r--r--src/backend/utils/adt/expandedrecord.c1569
1 files changed, 1569 insertions, 0 deletions
diff --git a/src/backend/utils/adt/expandedrecord.c b/src/backend/utils/adt/expandedrecord.c
new file mode 100644
index 00000000000..0bf5fe8cc7a
--- /dev/null
+++ b/src/backend/utils/adt/expandedrecord.c
@@ -0,0 +1,1569 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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-2018, 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/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_domain_check_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).
+ *
+ * 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. In any case, out-of-line
+ * fields in the tuple are not automatically inlined.
+ *
+ * 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)
+{
+ 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);
+
+ /*
+ * 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);
+ }
+ 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])))
+ {
+ /*
+ * It's an external toasted value, so we need to dereference
+ * it so that the flat representation will be self-contained.
+ * Do this step in the caller's context because the TOAST
+ * fetch might leak memory. That means making an extra copy,
+ * which is a tad annoying, but repetitive leaks in the
+ * record's context would be worse.
+ */
+ Datum newValue;
+
+ newValue = PointerGetDatum(PG_DETOAST_DATUM(erh->dvalues[i]));
+ /* expanded_record_set_field can do the rest */
+ /* ... and we don't need it to recheck domain constraints */
+ expanded_record_set_field_internal(erh, i + 1,
+ newValue, false,
+ false);
+ /* Might as well free the detoasted value */
+ pfree(DatumGetPointer(newValue));
+ }
+ }
+
+ /*
+ * 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);
+
+ if (tupdesc->tdhasoid)
+ len += sizeof(Oid);
+
+ 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;
+
+ if (tupdesc->tdhasoid) /* else leave infomask = 0 */
+ tuphdr->t_infomask = HEAP_HASOID;
+
+ /* 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;
+
+ 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? */
+ attr = SystemAttributeByName(fieldname, tupdesc->tdhasoid);
+ if (attr != NULL)
+ {
+ finfo->fnumber = attr->attnum;
+ finfo->ftypeid = attr->atttypid;
+ finfo->ftypmod = attr->atttypmod;
+ finfo->fcollation = attr->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.
+ *
+ * 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 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, if needed.
+ */
+ attr = TupleDescAttr(tupdesc, fnumber - 1);
+ if (!isnull && !attr->attbyval)
+ {
+ MemoryContext oldcxt;
+
+ oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context);
+ newValue = datumCopy(newValue, false, attr->attlen);
+ MemoryContextSwitchTo(oldcxt);
+
+ /* 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.
+ */
+ 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.
+ *
+ * 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.
+ */
+void
+expanded_record_set_fields(ExpandedRecordHeader *erh,
+ const Datum *newValues, const bool *isnulls)
+{
+ 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, if needed.
+ */
+ if (!isnull)
+ {
+ newValue = datumCopy(newValue, false, attr->attlen);
+
+ /* Remember that we have field(s) that 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.
+ */
+ if (attr->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
+ erh->flags |= ER_FLAG_HAVE_EXTERNAL;
+ }
+
+ /*
+ * 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_domain_check_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 domain checks.
+ *
+ * If we don't have a working memory context for domain checking, 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_domain_check_cxt(ExpandedRecordHeader *erh)
+{
+ if (erh->er_domain_check_cxt == NULL)
+ erh->er_domain_check_cxt =
+ AllocSetContextCreate(erh->hdr.eoh_context,
+ "expanded record domain checks",
+ ALLOCSET_SMALL_SIZES);
+ else
+ MemoryContextReset(erh->er_domain_check_cxt);
+ return erh->er_domain_check_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 domain_check_cxt */
+ (void) get_domain_check_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_domain_check_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_domain_check_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_domain_check_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_domain_check_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_domain_check_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_domain_check_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_domain_check_cxt);
+}