summaryrefslogtreecommitdiff
path: root/src/backend/executor
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2014-05-01 15:19:23 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2014-05-01 15:19:23 -0400
commit70debcf09563a82c85b3221d402b4e8a07f4367e (patch)
treef9f97ecdbe7269e1ccdc8da4e289fb29f9a49599 /src/backend/executor
parentf49df9150b9a00f7934f025677975e2396efebcd (diff)
downloadpostgresql-70debcf09563a82c85b3221d402b4e8a07f4367e.tar.gz
Fix failure to detoast fields in composite elements of structured types.
If we have an array of records stored on disk, the individual record fields cannot contain out-of-line TOAST pointers: the tuptoaster.c mechanisms are only prepared to deal with TOAST pointers appearing in top-level fields of a stored row. The same applies for ranges over composite types, nested composites, etc. However, the existing code only took care of expanding sub-field TOAST pointers for the case of nested composites, not for other structured types containing composites. For example, given a command such as UPDATE tab SET arraycol = ARRAY[(ROW(x,42)::mycompositetype] ... where x is a direct reference to a field of an on-disk tuple, if that field is long enough to be toasted out-of-line then the TOAST pointer would be inserted as-is into the array column. If the source record for x is later deleted, the array field value would become a dangling pointer, leading to errors along the line of "missing chunk number 0 for toast value ..." when the value is referenced. A reproducible test case for this was provided by Jan Pecek, but it seems likely that some of the "missing chunk number" reports we've heard in the past were caused by similar issues. Code-wise, the problem is that PG_DETOAST_DATUM() is not adequate to produce a self-contained Datum value if the Datum is of composite type. Seen in this light, the problem is not just confined to arrays and ranges, but could also affect some other places where detoasting is done in that way, for example form_index_tuple(). I tried teaching the array code to apply toast_flatten_tuple_attribute() along with PG_DETOAST_DATUM() when the array element type is composite, but this was messy and imposed extra cache lookup costs whether or not any TOAST pointers were present, indeed sometimes when the array element type isn't even composite (since sometimes it takes a typcache lookup to find that out). The idea of extending that approach to all the places that currently use PG_DETOAST_DATUM() wasn't attractive at all. This patch instead solves the problem by decreeing that composite Datum values must not contain any out-of-line TOAST pointers in the first place; that is, we expand out-of-line fields at the point of constructing a composite Datum, not at the point where we're about to insert it into a larger tuple. This rule is applied only to true composite Datums, not to tuples that are being passed around the system as tuples, so it's not as invasive as it might sound at first. With this approach, the amount of code that has to be touched for a full solution is greatly reduced, and added cache lookup costs are avoided except when there actually is a TOAST pointer that needs to be inlined. The main drawback of this approach is that we might sometimes dereference a TOAST pointer that will never actually be used by the query, imposing a rather large cost that wasn't there before. On the other side of the coin, if the field value is used multiple times then we'll come out ahead by avoiding repeat detoastings. Experimentation suggests that common SQL coding patterns are unaffected either way, though. Applications that are very negatively affected could be advised to modify their code to not fetch columns they won't be using. In future, we might consider reverting this solution in favor of detoasting only at the point where data is about to be stored to disk, using some method that can drill down into multiple levels of nested structured types. That will require defining new APIs for structured types, though, so it doesn't seem feasible as a back-patchable fix. Note that this patch changes HeapTupleGetDatum() from a macro to a function call; this means that any third-party code using that macro will not get protection against creating TOAST-pointer-containing Datums until it's recompiled. The same applies to any uses of PG_RETURN_HEAPTUPLEHEADER(). It seems likely that this is not a big problem in practice: most of the tuple-returning functions in core and contrib produce outputs that could not possibly be toasted anyway, and the same probably holds for third-party extensions. This bug has existed since TOAST was invented, so back-patch to all supported branches.
Diffstat (limited to 'src/backend/executor')
-rw-r--r--src/backend/executor/execQual.c32
-rw-r--r--src/backend/executor/execTuples.c79
-rw-r--r--src/backend/executor/functions.c1
-rw-r--r--src/backend/executor/spi.c7
4 files changed, 77 insertions, 42 deletions
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index d3c2f437d1..d5e828967f 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -882,8 +882,6 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
{
Var *variable = (Var *) wrvstate->xprstate.expr;
TupleTableSlot *slot = econtext->ecxt_scantuple;
- HeapTuple tuple;
- TupleDesc tupleDesc;
HeapTupleHeader dtuple;
if (isDone)
@@ -894,32 +892,20 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
if (wrvstate->wrv_junkFilter != NULL)
slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
- tuple = ExecFetchSlotTuple(slot);
- tupleDesc = slot->tts_tupleDescriptor;
-
/*
- * We have to make a copy of the tuple so we can safely insert the Datum
- * overhead fields, which are not set in on-disk tuples.
+ * Copy the slot tuple and make sure any toasted fields get detoasted.
*/
- dtuple = (HeapTupleHeader) palloc(tuple->t_len);
- memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
-
- HeapTupleHeaderSetDatumLength(dtuple, tuple->t_len);
+ dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
/*
- * If the Var identifies a named composite type, label the tuple with that
- * type; otherwise use what is in the tupleDesc.
+ * If the Var identifies a named composite type, label the datum with that
+ * type; otherwise we'll use the slot's info.
*/
if (variable->vartype != RECORDOID)
{
HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
}
- else
- {
- HeapTupleHeaderSetTypeId(dtuple, tupleDesc->tdtypeid);
- HeapTupleHeaderSetTypMod(dtuple, tupleDesc->tdtypmod);
- }
return PointerGetDatum(dtuple);
}
@@ -977,13 +963,13 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
}
/*
- * We have to make a copy of the tuple so we can safely insert the Datum
- * overhead fields, which are not set in on-disk tuples.
+ * Copy the slot tuple and make sure any toasted fields get detoasted.
*/
- dtuple = (HeapTupleHeader) palloc(tuple->t_len);
- memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
+ dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
- HeapTupleHeaderSetDatumLength(dtuple, tuple->t_len);
+ /*
+ * Reset datum's type ID fields to match the Var.
+ */
HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 7b5ba41f5f..97bd26a854 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -91,6 +91,7 @@
*/
#include "postgres.h"
+#include "access/tuptoaster.h"
#include "funcapi.h"
#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
@@ -768,27 +769,21 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
* ExecFetchSlotTupleDatum
* Fetch the slot's tuple as a composite-type Datum.
*
- * We convert the slot's contents to local physical-tuple form,
- * and fill in the Datum header fields. Note that the result
- * always points to storage owned by the slot.
+ * The result is always freshly palloc'd in the caller's memory context.
* --------------------------------
*/
Datum
ExecFetchSlotTupleDatum(TupleTableSlot *slot)
{
HeapTuple tup;
- HeapTupleHeader td;
TupleDesc tupdesc;
- /* Make sure we can scribble on the slot contents ... */
- tup = ExecMaterializeSlot(slot);
- /* ... and set up the composite-Datum header fields, in case not done */
- td = tup->t_data;
+ /* Fetch slot's contents in regular-physical-tuple form */
+ tup = ExecFetchSlotTuple(slot);
tupdesc = slot->tts_tupleDescriptor;
- HeapTupleHeaderSetDatumLength(td, tup->t_len);
- HeapTupleHeaderSetTypeId(td, tupdesc->tdtypeid);
- HeapTupleHeaderSetTypMod(td, tupdesc->tdtypmod);
- return PointerGetDatum(td);
+
+ /* Convert to Datum form */
+ return heap_copy_tuple_as_datum(tup, tupdesc);
}
/* --------------------------------
@@ -1192,6 +1187,66 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
}
/*
+ * HeapTupleHeaderGetDatum - convert a HeapTupleHeader pointer to a Datum.
+ *
+ * This must *not* get applied to an on-disk tuple; the tuple should be
+ * freshly made by heap_form_tuple or some wrapper routine for it (such as
+ * BuildTupleFromCStrings). Be sure also that the tupledesc used to build
+ * the tuple has a properly "blessed" rowtype.
+ *
+ * Formerly this was a macro equivalent to PointerGetDatum, relying on the
+ * fact that heap_form_tuple fills in the appropriate tuple header fields
+ * for a composite Datum. However, we now require that composite Datums not
+ * contain any external TOAST pointers. We do not want heap_form_tuple itself
+ * to enforce that; more specifically, the rule applies only to actual Datums
+ * and not to HeapTuple structures. Therefore, HeapTupleHeaderGetDatum is
+ * now a function that detects whether there are externally-toasted fields
+ * and constructs a new tuple with inlined fields if so. We still need
+ * heap_form_tuple to insert the Datum header fields, because otherwise this
+ * code would have no way to obtain a tupledesc for the tuple.
+ *
+ * Note that if we do build a new tuple, it's palloc'd in the current
+ * memory context. Beware of code that changes context between the initial
+ * heap_form_tuple/etc call and calling HeapTuple(Header)GetDatum.
+ *
+ * For performance-critical callers, it could be worthwhile to take extra
+ * steps to ensure that there aren't TOAST pointers in the output of
+ * heap_form_tuple to begin with. It's likely however that the costs of the
+ * typcache lookup and tuple disassembly/reassembly are swamped by TOAST
+ * dereference costs, so that the benefits of such extra effort would be
+ * minimal.
+ *
+ * XXX it would likely be better to create wrapper functions that produce
+ * a composite Datum from the field values in one step. However, there's
+ * enough code using the existing APIs that we couldn't get rid of this
+ * hack anytime soon.
+ */
+Datum
+HeapTupleHeaderGetDatum(HeapTupleHeader tuple)
+{
+ Datum result;
+ TupleDesc tupDesc;
+
+ /* No work if there are no external TOAST pointers in the tuple */
+ if (!HeapTupleHeaderHasExternal(tuple))
+ return PointerGetDatum(tuple);
+
+ /* Use the type data saved by heap_form_tuple to look up the rowtype */
+ tupDesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(tuple),
+ HeapTupleHeaderGetTypMod(tuple));
+
+ /* And do the flattening */
+ result = toast_flatten_tuple_to_datum(tuple,
+ HeapTupleHeaderGetDatumLength(tuple),
+ tupDesc);
+
+ ReleaseTupleDesc(tupDesc);
+
+ return result;
+}
+
+
+/*
* Functions for sending tuples to the frontend (or other specified destination)
* as though it is a SELECT result. These are used by utility commands that
* need to project directly to the destination and don't need or want full
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index c6f6451466..b5fa130579 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -581,7 +581,6 @@ postquel_get_single_result(TupleTableSlot *slot,
/* We must return the whole tuple as a Datum. */
fcinfo->isnull = false;
value = ExecFetchSlotTupleDatum(slot);
- value = datumCopy(value, fcache->typbyval, fcache->typlen);
}
else
{
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 3cffc2ad72..0f8f742caa 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -639,12 +639,7 @@ SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
}
- dtup = (HeapTupleHeader) palloc(tuple->t_len);
- memcpy((char *) dtup, (char *) tuple->t_data, tuple->t_len);
-
- HeapTupleHeaderSetDatumLength(dtup, tuple->t_len);
- HeapTupleHeaderSetTypeId(dtup, tupdesc->tdtypeid);
- HeapTupleHeaderSetTypMod(dtup, tupdesc->tdtypmod);
+ dtup = DatumGetHeapTupleHeader(heap_copy_tuple_as_datum(tuple, tupdesc));
if (oldcxt)
MemoryContextSwitchTo(oldcxt);