diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2009-03-30 04:09:09 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2009-03-30 04:09:09 +0000 |
commit | edc7f96c9d18c5edceeb526f0b7c464a3dc4dde7 (patch) | |
tree | ef04bc926dc25f7306944f4072546fd7c1570d9b /src/backend/executor/execTuples.c | |
parent | f14ce2124dd3e73276290fb1f66c9d783f7b1e20 (diff) | |
download | postgresql-edc7f96c9d18c5edceeb526f0b7c464a3dc4dde7.tar.gz postgresql-edc7f96c9d18c5edceeb526f0b7c464a3dc4dde7.zip |
Fix an oversight in the support for storing/retrieving "minimal tuples" in
TupleTableSlots. We have functions for retrieving a minimal tuple from a slot
after storing a regular tuple in it, or vice versa; but these were implemented
by converting the internal storage from one format to the other. The problem
with that is it invalidates any pass-by-reference Datums that were already
fetched from the slot, since they'll be pointing into the just-freed version
of the tuple. The known problem cases involve fetching both a whole-row
variable and a pass-by-reference value from a slot that is fed from a
tuplestore or tuplesort object. The added regression tests illustrate some
simple cases, but there may be other failure scenarios traceable to the same
bug. Note that the added tests probably only fail on unpatched code if it's
built with --enable-cassert; otherwise the bug leads to fetching from freed
memory, which will not have been overwritten without additional conditions.
Fix by allowing a slot to contain both formats simultaneously; which turns out
not to complicate the logic much at all, if anything it seems less contorted
than before.
Back-patch to 8.2, where minimal tuples were introduced.
Diffstat (limited to 'src/backend/executor/execTuples.c')
-rw-r--r-- | src/backend/executor/execTuples.c | 134 |
1 files changed, 77 insertions, 57 deletions
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 7767df79207..db377da099a 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.100 2008/01/01 19:45:49 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.100.2.1 2009/03/30 04:09:09 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; } |