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.c180
1 files changed, 124 insertions, 56 deletions
diff --git a/src/backend/utils/adt/expandedrecord.c b/src/backend/utils/adt/expandedrecord.c
index 0bf5fe8cc7a..b1b6883c19f 100644
--- a/src/backend/utils/adt/expandedrecord.c
+++ b/src/backend/utils/adt/expandedrecord.c
@@ -19,6 +19,7 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/tuptoaster.h"
#include "catalog/heap.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
@@ -41,7 +42,7 @@ static const ExpandedObjectMethods ER_methods =
/* Other local functions */
static void ER_mc_callback(void *arg);
-static MemoryContext get_domain_check_cxt(ExpandedRecordHeader *erh);
+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,
@@ -57,8 +58,9 @@ static pg_noinline void check_domain_for_new_tuple(ExpandedRecordHeader *erh,
*
* 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).
+ * 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.
*/
@@ -424,8 +426,11 @@ make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh,
*
* 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.
+ * 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.
@@ -433,7 +438,8 @@ make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh,
void
expanded_record_set_tuple(ExpandedRecordHeader *erh,
HeapTuple tuple,
- bool copy)
+ bool copy,
+ bool expand_external)
{
int oldflags;
HeapTuple oldtuple;
@@ -453,6 +459,25 @@ expanded_record_set_tuple(ExpandedRecordHeader *erh,
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;
@@ -468,6 +493,10 @@ expanded_record_set_tuple(ExpandedRecordHeader *erh,
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;
@@ -676,23 +705,13 @@ ER_get_flat_size(ExpandedObjectHeader *eohptr)
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.
+ * expanded_record_set_field_internal can do the actual work
+ * of detoasting. It needn't recheck domain constraints.
*/
- 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,
+ erh->dvalues[i], false,
+ true,
false);
- /* Might as well free the detoasted value */
- pfree(DatumGetPointer(newValue));
}
}
@@ -1087,12 +1106,16 @@ expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber,
* (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;
@@ -1124,23 +1147,46 @@ expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
elog(ERROR, "cannot assign to field %d of expanded record", fnumber);
/*
- * Copy new field value into record's context, if needed.
+ * 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(heap_tuple_fetch_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.
+ * 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)))
@@ -1193,14 +1239,20 @@ expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
* 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.
+ * 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)
+ const Datum *newValues, const bool *isnulls,
+ bool expand_external)
{
TupleDesc tupdesc;
Datum *dvalues;
@@ -1245,22 +1297,37 @@ expanded_record_set_fields(ExpandedRecordHeader *erh,
if (!attr->attbyval)
{
/*
- * Copy new field value into record's context, if needed.
+ * Copy new field value into record's context, and deal with
+ * detoasting, if needed.
*/
if (!isnull)
{
- newValue = datumCopy(newValue, false, attr->attlen);
+ /* 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(heap_tuple_fetch_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;
-
- /*
- * 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;
}
/*
@@ -1291,7 +1358,7 @@ expanded_record_set_fields(ExpandedRecordHeader *erh,
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));
+ MemoryContextSwitchTo(get_short_term_cxt(erh));
domain_check(ExpandedRecordGetRODatum(erh), false,
erh->er_decltypeid,
@@ -1303,25 +1370,26 @@ expanded_record_set_fields(ExpandedRecordHeader *erh,
}
/*
- * Construct (or reset) working memory context for domain checks.
+ * 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 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.)
+ * 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_domain_check_cxt(ExpandedRecordHeader *erh)
+get_short_term_cxt(ExpandedRecordHeader *erh)
{
- if (erh->er_domain_check_cxt == NULL)
- erh->er_domain_check_cxt =
+ if (erh->er_short_term_cxt == NULL)
+ erh->er_short_term_cxt =
AllocSetContextCreate(erh->hdr.eoh_context,
- "expanded record domain checks",
+ "expanded record short-term context",
ALLOCSET_SMALL_SIZES);
else
- MemoryContextReset(erh->er_domain_check_cxt);
- return erh->er_domain_check_cxt;
+ MemoryContextReset(erh->er_short_term_cxt);
+ return erh->er_short_term_cxt;
}
/*
@@ -1340,8 +1408,8 @@ 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);
+ /* 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
@@ -1372,7 +1440,7 @@ build_dummy_expanded_header(ExpandedRecordHeader *main_erh)
* 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);
+ 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 */
@@ -1488,7 +1556,7 @@ check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber,
* 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);
+ oldcxt = MemoryContextSwitchTo(erh->er_short_term_cxt);
/*
* And now we can apply the check. Note we use main header's domain cache
@@ -1502,7 +1570,7 @@ check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber,
MemoryContextSwitchTo(oldcxt);
/* We might as well clean up cruft immediately. */
- MemoryContextReset(erh->er_domain_check_cxt);
+ MemoryContextReset(erh->er_short_term_cxt);
}
/*
@@ -1518,7 +1586,7 @@ check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple)
if (tuple == NULL)
{
/* We run domain_check in a short-lived context to limit cruft */
- oldcxt = MemoryContextSwitchTo(get_domain_check_cxt(erh));
+ oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh));
domain_check((Datum) 0, true,
erh->er_decltypeid,
@@ -1528,7 +1596,7 @@ check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple)
MemoryContextSwitchTo(oldcxt);
/* We might as well clean up cruft immediately. */
- MemoryContextReset(erh->er_domain_check_cxt);
+ MemoryContextReset(erh->er_short_term_cxt);
return;
}
@@ -1551,7 +1619,7 @@ check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple)
* 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);
+ oldcxt = MemoryContextSwitchTo(erh->er_short_term_cxt);
/*
* And now we can apply the check. Note we use main header's domain cache
@@ -1565,5 +1633,5 @@ check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple)
MemoryContextSwitchTo(oldcxt);
/* We might as well clean up cruft immediately. */
- MemoryContextReset(erh->er_domain_check_cxt);
+ MemoryContextReset(erh->er_short_term_cxt);
}