aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/access/common/heaptuple.c6
-rw-r--r--src/backend/executor/execTuples.c134
-rw-r--r--src/include/executor/tuptable.h32
-rw-r--r--src/test/regress/expected/rangefuncs.out20
-rw-r--r--src/test/regress/sql/rangefuncs.sql13
5 files changed, 135 insertions, 70 deletions
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 608f9c867ae..4affea6d04c 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -16,7 +16,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.112 2006/11/23 05:27:18 neilc Exp $
+ * $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.112.2.1 2009/03/30 04:09:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1297,7 +1297,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
{
if (tuple == NULL) /* internal error */
elog(ERROR, "cannot extract system attribute from virtual tuple");
- if (slot->tts_mintuple) /* internal error */
+ if (tuple == &(slot->tts_minhdr)) /* internal error */
elog(ERROR, "cannot extract system attribute from minimal tuple");
return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
}
@@ -1483,7 +1483,7 @@ slot_attisnull(TupleTableSlot *slot, int attnum)
{
if (tuple == NULL) /* internal error */
elog(ERROR, "cannot extract system attribute from virtual tuple");
- if (slot->tts_mintuple) /* internal error */
+ if (tuple == &(slot->tts_minhdr)) /* internal error */
elog(ERROR, "cannot extract system attribute from minimal tuple");
return heap_attisnull(tuple, attnum);
}
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index fb1fdf9e723..336cf9ed030 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -15,7 +15,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.98 2006/10/04 00:29:52 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.98.2.1 2009/03/30 04:09:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -146,6 +146,7 @@ ExecCreateTupleTable(int tableSize)
slot->type = T_TupleTableSlot;
slot->tts_isempty = true;
slot->tts_shouldFree = false;
+ slot->tts_shouldFreeMin = false;
slot->tts_tuple = NULL;
slot->tts_tupleDescriptor = NULL;
slot->tts_mcxt = CurrentMemoryContext;
@@ -224,6 +225,7 @@ MakeSingleTupleTableSlot(TupleDesc tupdesc)
/* This should match ExecCreateTupleTable() */
slot->tts_isempty = true;
slot->tts_shouldFree = false;
+ slot->tts_shouldFreeMin = false;
slot->tts_tuple = NULL;
slot->tts_tupleDescriptor = NULL;
slot->tts_mcxt = CurrentMemoryContext;
@@ -410,18 +412,16 @@ ExecStoreTuple(HeapTuple tuple,
* Free any old physical tuple belonging to the slot.
*/
if (slot->tts_shouldFree)
- {
- if (slot->tts_mintuple)
- heap_free_minimal_tuple(slot->tts_mintuple);
- else
- heap_freetuple(slot->tts_tuple);
- }
+ heap_freetuple(slot->tts_tuple);
+ if (slot->tts_shouldFreeMin)
+ heap_free_minimal_tuple(slot->tts_mintuple);
/*
* Store the new tuple into the specified slot.
*/
slot->tts_isempty = false;
slot->tts_shouldFree = shouldFree;
+ slot->tts_shouldFreeMin = false;
slot->tts_tuple = tuple;
slot->tts_mintuple = NULL;
@@ -473,12 +473,9 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
* Free any old physical tuple belonging to the slot.
*/
if (slot->tts_shouldFree)
- {
- if (slot->tts_mintuple)
- heap_free_minimal_tuple(slot->tts_mintuple);
- else
- heap_freetuple(slot->tts_tuple);
- }
+ heap_freetuple(slot->tts_tuple);
+ if (slot->tts_shouldFreeMin)
+ heap_free_minimal_tuple(slot->tts_mintuple);
/*
* Drop the pin on the referenced buffer, if there is one.
@@ -492,7 +489,8 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
* Store the new tuple into the specified slot.
*/
slot->tts_isempty = false;
- slot->tts_shouldFree = shouldFree;
+ slot->tts_shouldFree = false;
+ slot->tts_shouldFreeMin = shouldFree;
slot->tts_tuple = &slot->tts_minhdr;
slot->tts_mintuple = mtup;
@@ -526,16 +524,14 @@ ExecClearTuple(TupleTableSlot *slot) /* slot in which to store tuple */
* Free the old physical tuple if necessary.
*/
if (slot->tts_shouldFree)
- {
- if (slot->tts_mintuple)
- heap_free_minimal_tuple(slot->tts_mintuple);
- else
- heap_freetuple(slot->tts_tuple);
- }
+ heap_freetuple(slot->tts_tuple);
+ if (slot->tts_shouldFreeMin)
+ heap_free_minimal_tuple(slot->tts_mintuple);
slot->tts_tuple = NULL;
slot->tts_mintuple = NULL;
slot->tts_shouldFree = false;
+ slot->tts_shouldFreeMin = false;
/*
* Drop the pin on the referenced buffer, if there is one.
@@ -616,6 +612,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
* ExecCopySlotTuple
* Obtain a copy of a slot's regular physical tuple. The copy is
* palloc'd in the current memory context.
+ * The slot itself is undisturbed.
*
* This works even if the slot contains a virtual or minimal tuple;
* however the "system columns" of the result will not be meaningful.
@@ -631,15 +628,12 @@ ExecCopySlotTuple(TupleTableSlot *slot)
Assert(!slot->tts_isempty);
/*
- * If we have a physical tuple then just copy it.
+ * If we have a physical tuple (either format) then just copy it.
*/
- if (slot->tts_tuple)
- {
- if (slot->tts_mintuple)
- return heap_tuple_from_minimal_tuple(slot->tts_mintuple);
- else
- return heap_copytuple(slot->tts_tuple);
- }
+ if (TTS_HAS_PHYSICAL_TUPLE(slot))
+ return heap_copytuple(slot->tts_tuple);
+ if (slot->tts_mintuple)
+ return heap_tuple_from_minimal_tuple(slot->tts_mintuple);
/*
* Otherwise we need to build a tuple from the Datum array.
@@ -653,6 +647,7 @@ ExecCopySlotTuple(TupleTableSlot *slot)
* ExecCopySlotMinimalTuple
* Obtain a copy of a slot's minimal physical tuple. The copy is
* palloc'd in the current memory context.
+ * The slot itself is undisturbed.
* --------------------------------
*/
MinimalTuple
@@ -665,15 +660,13 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
Assert(!slot->tts_isempty);
/*
- * If we have a physical tuple then just copy it.
+ * If we have a physical tuple then just copy it. Prefer to copy
+ * tts_mintuple since that's a tad cheaper.
*/
+ if (slot->tts_mintuple)
+ return heap_copy_minimal_tuple(slot->tts_mintuple);
if (slot->tts_tuple)
- {
- if (slot->tts_mintuple)
- return heap_copy_minimal_tuple(slot->tts_mintuple);
- else
- return minimal_tuple_from_heap_tuple(slot->tts_tuple);
- }
+ return minimal_tuple_from_heap_tuple(slot->tts_tuple);
/*
* Otherwise we need to build a tuple from the Datum array.
@@ -689,9 +682,11 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
*
* If the slot contains a virtual tuple, we convert it to physical
* form. The slot retains ownership of the physical tuple.
- * Likewise, if it contains a minimal tuple we convert to regular form.
+ * If it contains a minimal tuple we convert to regular form and store
+ * that in addition to the minimal tuple (not instead of, because
+ * callers may hold pointers to Datums within the minimal tuple).
*
- * The difference between this and ExecMaterializeSlot() is that this
+ * The main difference between this and ExecMaterializeSlot() is that this
* does not guarantee that the contained tuple is local storage.
* Hence, the result must be treated as read-only.
* --------------------------------
@@ -708,7 +703,7 @@ ExecFetchSlotTuple(TupleTableSlot *slot)
/*
* If we have a regular physical tuple then just return it.
*/
- if (slot->tts_tuple && slot->tts_mintuple == NULL)
+ if (TTS_HAS_PHYSICAL_TUPLE(slot))
return slot->tts_tuple;
/*
@@ -722,8 +717,10 @@ ExecFetchSlotTuple(TupleTableSlot *slot)
* Fetch the slot's minimal physical tuple.
*
* If the slot contains a virtual tuple, we convert it to minimal
- * physical form. The slot retains ownership of the physical tuple.
- * Likewise, if it contains a regular tuple we convert to minimal form.
+ * physical form. The slot retains ownership of the minimal tuple.
+ * If it contains a regular tuple we convert to minimal form and store
+ * that in addition to the regular tuple (not instead of, because
+ * callers may hold pointers to Datums within the regular tuple).
*
* As above, the result must be treated as read-only.
* --------------------------------
@@ -731,7 +728,6 @@ ExecFetchSlotTuple(TupleTableSlot *slot)
MinimalTuple
ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
{
- MinimalTuple newTuple;
MemoryContext oldContext;
/*
@@ -741,28 +737,30 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
Assert(!slot->tts_isempty);
/*
- * If we have a minimal physical tuple then just return it.
+ * If we have a minimal physical tuple (local or not) then just return it.
*/
if (slot->tts_mintuple)
return slot->tts_mintuple;
/*
- * Otherwise, build a minimal tuple, and then store it as the new slot
- * value. (Note: tts_nvalid will be reset to zero here. There are cases
- * in which this could be optimized but it's probably not worth worrying
- * about.)
+ * Otherwise, copy or build a minimal tuple, and store it into the slot.
*
* We may be called in a context that is shorter-lived than the tuple
* slot, but we have to ensure that the materialized tuple will survive
* anyway.
*/
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
- newTuple = ExecCopySlotMinimalTuple(slot);
+ slot->tts_mintuple = ExecCopySlotMinimalTuple(slot);
+ slot->tts_shouldFreeMin = true;
MemoryContextSwitchTo(oldContext);
- ExecStoreMinimalTuple(newTuple, slot, true);
+ /*
+ * Note: we may now have a situation where we have a local minimal tuple
+ * attached to a virtual or non-local physical tuple. There seems no
+ * harm in that at the moment, but if any materializes, we should change
+ * this function to force the slot into minimal-tuple-only state.
+ */
- Assert(slot->tts_mintuple);
return slot->tts_mintuple;
}
@@ -782,7 +780,6 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
HeapTuple
ExecMaterializeSlot(TupleTableSlot *slot)
{
- HeapTuple newTuple;
MemoryContext oldContext;
/*
@@ -795,24 +792,47 @@ ExecMaterializeSlot(TupleTableSlot *slot)
* If we have a regular physical tuple, and it's locally palloc'd, we have
* nothing to do.
*/
- if (slot->tts_tuple && slot->tts_shouldFree && slot->tts_mintuple == NULL)
+ if (slot->tts_tuple && slot->tts_shouldFree)
return slot->tts_tuple;
/*
- * Otherwise, copy or build a tuple, and then store it as the new slot
- * value. (Note: tts_nvalid will be reset to zero here. There are cases
- * in which this could be optimized but it's probably not worth worrying
- * about.)
+ * Otherwise, copy or build a physical tuple, and store it into the slot.
*
* We may be called in a context that is shorter-lived than the tuple
* slot, but we have to ensure that the materialized tuple will survive
* anyway.
*/
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
- newTuple = ExecCopySlotTuple(slot);
+ slot->tts_tuple = ExecCopySlotTuple(slot);
+ slot->tts_shouldFree = true;
MemoryContextSwitchTo(oldContext);
- ExecStoreTuple(newTuple, slot, InvalidBuffer, true);
+ /*
+ * Drop the pin on the referenced buffer, if there is one.
+ */
+ if (BufferIsValid(slot->tts_buffer))
+ ReleaseBuffer(slot->tts_buffer);
+
+ slot->tts_buffer = InvalidBuffer;
+
+ /*
+ * Mark extracted state invalid. This is important because the slot
+ * is not supposed to depend any more on the previous external data;
+ * we mustn't leave any dangling pass-by-reference datums in tts_values.
+ * However, we have not actually invalidated any such datums, if there
+ * happen to be any previously fetched from the slot. (Note in particular
+ * that we have not pfree'd tts_mintuple, if there is one.)
+ */
+ slot->tts_nvalid = 0;
+
+ /*
+ * On the same principle of not depending on previous remote storage,
+ * forget the mintuple if it's not local storage. (If it is local storage,
+ * we must not pfree it now, since callers might have already fetched
+ * datum pointers referencing it.)
+ */
+ if (!slot->tts_shouldFreeMin)
+ slot->tts_mintuple = NULL;
return slot->tts_tuple;
}
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index c34234c836d..93b3844f63c 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/executor/tuptable.h,v 1.36 2006/10/04 00:30:08 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/tuptable.h,v 1.36.2.1 2009/03/30 04:09:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -49,6 +49,14 @@
* else need to be "materialized" into physical tuples. Note also that a
* virtual tuple does not have any "system columns".
*
+ * It is also possible for a TupleTableSlot to hold both physical and minimal
+ * copies of a tuple. This is done when the slot is requested to provide
+ * the format other than the one it currently holds. (Originally we attempted
+ * to handle such requests by replacing one format with the other, but that
+ * had the fatal defect of invalidating any pass-by-reference Datums pointing
+ * into the existing slot contents.) Both copies must contain identical data
+ * payloads when this is the case.
+ *
* The Datum/isnull arrays of a TupleTableSlot serve double duty. When the
* slot contains a virtual tuple, they are the authoritative data. When the
* slot contains a physical tuple, the arrays contain data extracted from
@@ -91,12 +99,12 @@
*
* tts_mintuple must always be NULL if the slot does not hold a "minimal"
* tuple. When it does, tts_mintuple points to the actual MinimalTupleData
- * object (the thing to be pfree'd if tts_shouldFree is true). In this case
- * tts_tuple points at tts_minhdr and the fields of that are set correctly
+ * object (the thing to be pfree'd if tts_shouldFreeMin is true). If the slot
+ * has only a minimal and not also a regular physical tuple, then tts_tuple
+ * points at tts_minhdr and the fields of that struct are set correctly
* for access to the minimal tuple; in particular, tts_minhdr.t_data points
- * MINIMAL_TUPLE_OFFSET bytes before tts_mintuple. (tts_mintuple is therefore
- * redundant, but for code simplicity we store it explicitly anyway.) This
- * case otherwise behaves identically to the regular-physical-tuple case.
+ * MINIMAL_TUPLE_OFFSET bytes before tts_mintuple. This allows column
+ * extraction to treat the case identically to regular physical tuples.
*
* tts_slow/tts_off are saved state for slot_deform_tuple, and should not
* be touched by any other code.
@@ -106,20 +114,24 @@ typedef struct TupleTableSlot
{
NodeTag type; /* vestigial ... allows IsA tests */
bool tts_isempty; /* true = slot is empty */
- bool tts_shouldFree; /* should pfree tuple? */
+ bool tts_shouldFree; /* should pfree tts_tuple? */
+ bool tts_shouldFreeMin; /* should pfree tts_mintuple? */
bool tts_slow; /* saved state for slot_deform_tuple */
- HeapTuple tts_tuple; /* physical tuple, or NULL if none */
+ HeapTuple tts_tuple; /* physical tuple, or NULL if virtual */
TupleDesc tts_tupleDescriptor; /* slot's tuple descriptor */
MemoryContext tts_mcxt; /* slot itself is in this context */
Buffer tts_buffer; /* tuple's buffer, or InvalidBuffer */
int tts_nvalid; /* # of valid values in tts_values */
Datum *tts_values; /* current per-attribute values */
bool *tts_isnull; /* current per-attribute isnull flags */
- MinimalTuple tts_mintuple; /* set if it's a minimal tuple, else NULL */
- HeapTupleData tts_minhdr; /* workspace if it's a minimal tuple */
+ MinimalTuple tts_mintuple; /* minimal tuple, or NULL if none */
+ HeapTupleData tts_minhdr; /* workspace for minimal-tuple-only case */
long tts_off; /* saved state for slot_deform_tuple */
} TupleTableSlot;
+#define TTS_HAS_PHYSICAL_TUPLE(slot) \
+ ((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr))
+
/*
* Tuple table data structure: an array of TupleTableSlots.
*/
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 000d4e36c52..4fedc41ed86 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -528,3 +528,23 @@ CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
AS 'select $1, array[$1,$1]' LANGUAGE sql;
ERROR: cannot determine result data type
DETAIL: A function returning "anyarray" or "anyelement" must have at least one argument of either type.
+-- test case for a whole-row-variable bug
+create function foo1(n integer, out a text, out b text)
+ returns setof record
+ language sql
+ as $$ select 'foo ' || i, 'bar ' || i from generate_series(1,$1) i $$;
+set work_mem='64kB';
+select t.a, t, t.a from foo1(10000) t limit 1;
+ a | t | a
+-------+-------------------+-------
+ foo 1 | ("foo 1","bar 1") | foo 1
+(1 row)
+
+reset work_mem;
+select t.a, t, t.a from foo1(10000) t limit 1;
+ a | t | a
+-------+-------------------+-------
+ foo 1 | ("foo 1","bar 1") | foo 1
+(1 row)
+
+drop function foo1(n integer);
diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql
index c56e35ded4e..baa56c7355c 100644
--- a/src/test/regress/sql/rangefuncs.sql
+++ b/src/test/regress/sql/rangefuncs.sql
@@ -261,3 +261,16 @@ DROP FUNCTION dup(anyelement);
-- fails, no way to deduce outputs
CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
AS 'select $1, array[$1,$1]' LANGUAGE sql;
+
+-- test case for a whole-row-variable bug
+create function foo1(n integer, out a text, out b text)
+ returns setof record
+ language sql
+ as $$ select 'foo ' || i, 'bar ' || i from generate_series(1,$1) i $$;
+
+set work_mem='64kB';
+select t.a, t, t.a from foo1(10000) t limit 1;
+reset work_mem;
+select t.a, t, t.a from foo1(10000) t limit 1;
+
+drop function foo1(n integer);