Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 793d566

Browse files
committed
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.
1 parent a576994 commit 793d566

File tree

7 files changed

+174
-70
lines changed

7 files changed

+174
-70
lines changed

src/backend/access/common/heaptuple.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
*
5151
*
5252
* IDENTIFICATION
53-
* $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.125 2009/01/01 17:23:34 momjian Exp $
53+
* $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.126 2009/03/30 04:08:43 tgl Exp $
5454
*
5555
*-------------------------------------------------------------------------
5656
*/
@@ -1183,7 +1183,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
11831183
{
11841184
if (tuple == NULL) /* internal error */
11851185
elog(ERROR, "cannot extract system attribute from virtual tuple");
1186-
if (slot->tts_mintuple) /* internal error */
1186+
if (tuple == &(slot->tts_minhdr)) /* internal error */
11871187
elog(ERROR, "cannot extract system attribute from minimal tuple");
11881188
return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
11891189
}
@@ -1369,7 +1369,7 @@ slot_attisnull(TupleTableSlot *slot, int attnum)
13691369
{
13701370
if (tuple == NULL) /* internal error */
13711371
elog(ERROR, "cannot extract system attribute from virtual tuple");
1372-
if (slot->tts_mintuple) /* internal error */
1372+
if (tuple == &(slot->tts_minhdr)) /* internal error */
13731373
elog(ERROR, "cannot extract system attribute from minimal tuple");
13741374
return heap_attisnull(tuple, attnum);
13751375
}

src/backend/executor/execTuples.c

+77-57
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*
1616
*
1717
* IDENTIFICATION
18-
* $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.105 2009/01/01 17:23:41 momjian Exp $
18+
* $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.106 2009/03/30 04:08:43 tgl Exp $
1919
*
2020
*-------------------------------------------------------------------------
2121
*/
@@ -146,6 +146,7 @@ ExecCreateTupleTable(int tableSize)
146146
slot->type = T_TupleTableSlot;
147147
slot->tts_isempty = true;
148148
slot->tts_shouldFree = false;
149+
slot->tts_shouldFreeMin = false;
149150
slot->tts_tuple = NULL;
150151
slot->tts_tupleDescriptor = NULL;
151152
slot->tts_mcxt = CurrentMemoryContext;
@@ -224,6 +225,7 @@ MakeSingleTupleTableSlot(TupleDesc tupdesc)
224225
/* This should match ExecCreateTupleTable() */
225226
slot->tts_isempty = true;
226227
slot->tts_shouldFree = false;
228+
slot->tts_shouldFreeMin = false;
227229
slot->tts_tuple = NULL;
228230
slot->tts_tupleDescriptor = NULL;
229231
slot->tts_mcxt = CurrentMemoryContext;
@@ -410,18 +412,16 @@ ExecStoreTuple(HeapTuple tuple,
410412
* Free any old physical tuple belonging to the slot.
411413
*/
412414
if (slot->tts_shouldFree)
413-
{
414-
if (slot->tts_mintuple)
415-
heap_free_minimal_tuple(slot->tts_mintuple);
416-
else
417-
heap_freetuple(slot->tts_tuple);
418-
}
415+
heap_freetuple(slot->tts_tuple);
416+
if (slot->tts_shouldFreeMin)
417+
heap_free_minimal_tuple(slot->tts_mintuple);
419418

420419
/*
421420
* Store the new tuple into the specified slot.
422421
*/
423422
slot->tts_isempty = false;
424423
slot->tts_shouldFree = shouldFree;
424+
slot->tts_shouldFreeMin = false;
425425
slot->tts_tuple = tuple;
426426
slot->tts_mintuple = NULL;
427427

@@ -473,12 +473,9 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
473473
* Free any old physical tuple belonging to the slot.
474474
*/
475475
if (slot->tts_shouldFree)
476-
{
477-
if (slot->tts_mintuple)
478-
heap_free_minimal_tuple(slot->tts_mintuple);
479-
else
480-
heap_freetuple(slot->tts_tuple);
481-
}
476+
heap_freetuple(slot->tts_tuple);
477+
if (slot->tts_shouldFreeMin)
478+
heap_free_minimal_tuple(slot->tts_mintuple);
482479

483480
/*
484481
* Drop the pin on the referenced buffer, if there is one.
@@ -492,7 +489,8 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
492489
* Store the new tuple into the specified slot.
493490
*/
494491
slot->tts_isempty = false;
495-
slot->tts_shouldFree = shouldFree;
492+
slot->tts_shouldFree = false;
493+
slot->tts_shouldFreeMin = shouldFree;
496494
slot->tts_tuple = &slot->tts_minhdr;
497495
slot->tts_mintuple = mtup;
498496

@@ -526,16 +524,14 @@ ExecClearTuple(TupleTableSlot *slot) /* slot in which to store tuple */
526524
* Free the old physical tuple if necessary.
527525
*/
528526
if (slot->tts_shouldFree)
529-
{
530-
if (slot->tts_mintuple)
531-
heap_free_minimal_tuple(slot->tts_mintuple);
532-
else
533-
heap_freetuple(slot->tts_tuple);
534-
}
527+
heap_freetuple(slot->tts_tuple);
528+
if (slot->tts_shouldFreeMin)
529+
heap_free_minimal_tuple(slot->tts_mintuple);
535530

536531
slot->tts_tuple = NULL;
537532
slot->tts_mintuple = NULL;
538533
slot->tts_shouldFree = false;
534+
slot->tts_shouldFreeMin = false;
539535

540536
/*
541537
* Drop the pin on the referenced buffer, if there is one.
@@ -616,6 +612,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
616612
* ExecCopySlotTuple
617613
* Obtain a copy of a slot's regular physical tuple. The copy is
618614
* palloc'd in the current memory context.
615+
* The slot itself is undisturbed.
619616
*
620617
* This works even if the slot contains a virtual or minimal tuple;
621618
* however the "system columns" of the result will not be meaningful.
@@ -631,15 +628,12 @@ ExecCopySlotTuple(TupleTableSlot *slot)
631628
Assert(!slot->tts_isempty);
632629

633630
/*
634-
* If we have a physical tuple then just copy it.
631+
* If we have a physical tuple (either format) then just copy it.
635632
*/
636-
if (slot->tts_tuple)
637-
{
638-
if (slot->tts_mintuple)
639-
return heap_tuple_from_minimal_tuple(slot->tts_mintuple);
640-
else
641-
return heap_copytuple(slot->tts_tuple);
642-
}
633+
if (TTS_HAS_PHYSICAL_TUPLE(slot))
634+
return heap_copytuple(slot->tts_tuple);
635+
if (slot->tts_mintuple)
636+
return heap_tuple_from_minimal_tuple(slot->tts_mintuple);
643637

644638
/*
645639
* Otherwise we need to build a tuple from the Datum array.
@@ -653,6 +647,7 @@ ExecCopySlotTuple(TupleTableSlot *slot)
653647
* ExecCopySlotMinimalTuple
654648
* Obtain a copy of a slot's minimal physical tuple. The copy is
655649
* palloc'd in the current memory context.
650+
* The slot itself is undisturbed.
656651
* --------------------------------
657652
*/
658653
MinimalTuple
@@ -665,15 +660,13 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
665660
Assert(!slot->tts_isempty);
666661

667662
/*
668-
* If we have a physical tuple then just copy it.
663+
* If we have a physical tuple then just copy it. Prefer to copy
664+
* tts_mintuple since that's a tad cheaper.
669665
*/
666+
if (slot->tts_mintuple)
667+
return heap_copy_minimal_tuple(slot->tts_mintuple);
670668
if (slot->tts_tuple)
671-
{
672-
if (slot->tts_mintuple)
673-
return heap_copy_minimal_tuple(slot->tts_mintuple);
674-
else
675-
return minimal_tuple_from_heap_tuple(slot->tts_tuple);
676-
}
669+
return minimal_tuple_from_heap_tuple(slot->tts_tuple);
677670

678671
/*
679672
* Otherwise we need to build a tuple from the Datum array.
@@ -689,9 +682,11 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
689682
*
690683
* If the slot contains a virtual tuple, we convert it to physical
691684
* form. The slot retains ownership of the physical tuple.
692-
* Likewise, if it contains a minimal tuple we convert to regular form.
685+
* If it contains a minimal tuple we convert to regular form and store
686+
* that in addition to the minimal tuple (not instead of, because
687+
* callers may hold pointers to Datums within the minimal tuple).
693688
*
694-
* The difference between this and ExecMaterializeSlot() is that this
689+
* The main difference between this and ExecMaterializeSlot() is that this
695690
* does not guarantee that the contained tuple is local storage.
696691
* Hence, the result must be treated as read-only.
697692
* --------------------------------
@@ -708,7 +703,7 @@ ExecFetchSlotTuple(TupleTableSlot *slot)
708703
/*
709704
* If we have a regular physical tuple then just return it.
710705
*/
711-
if (slot->tts_tuple && slot->tts_mintuple == NULL)
706+
if (TTS_HAS_PHYSICAL_TUPLE(slot))
712707
return slot->tts_tuple;
713708

714709
/*
@@ -722,16 +717,17 @@ ExecFetchSlotTuple(TupleTableSlot *slot)
722717
* Fetch the slot's minimal physical tuple.
723718
*
724719
* If the slot contains a virtual tuple, we convert it to minimal
725-
* physical form. The slot retains ownership of the physical tuple.
726-
* Likewise, if it contains a regular tuple we convert to minimal form.
720+
* physical form. The slot retains ownership of the minimal tuple.
721+
* If it contains a regular tuple we convert to minimal form and store
722+
* that in addition to the regular tuple (not instead of, because
723+
* callers may hold pointers to Datums within the regular tuple).
727724
*
728725
* As above, the result must be treated as read-only.
729726
* --------------------------------
730727
*/
731728
MinimalTuple
732729
ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
733730
{
734-
MinimalTuple newTuple;
735731
MemoryContext oldContext;
736732

737733
/*
@@ -741,28 +737,30 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
741737
Assert(!slot->tts_isempty);
742738

743739
/*
744-
* If we have a minimal physical tuple then just return it.
740+
* If we have a minimal physical tuple (local or not) then just return it.
745741
*/
746742
if (slot->tts_mintuple)
747743
return slot->tts_mintuple;
748744

749745
/*
750-
* Otherwise, build a minimal tuple, and then store it as the new slot
751-
* value. (Note: tts_nvalid will be reset to zero here. There are cases
752-
* in which this could be optimized but it's probably not worth worrying
753-
* about.)
746+
* Otherwise, copy or build a minimal tuple, and store it into the slot.
754747
*
755748
* We may be called in a context that is shorter-lived than the tuple
756749
* slot, but we have to ensure that the materialized tuple will survive
757750
* anyway.
758751
*/
759752
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
760-
newTuple = ExecCopySlotMinimalTuple(slot);
753+
slot->tts_mintuple = ExecCopySlotMinimalTuple(slot);
754+
slot->tts_shouldFreeMin = true;
761755
MemoryContextSwitchTo(oldContext);
762756

763-
ExecStoreMinimalTuple(newTuple, slot, true);
757+
/*
758+
* Note: we may now have a situation where we have a local minimal tuple
759+
* attached to a virtual or non-local physical tuple. There seems no
760+
* harm in that at the moment, but if any materializes, we should change
761+
* this function to force the slot into minimal-tuple-only state.
762+
*/
764763

765-
Assert(slot->tts_mintuple);
766764
return slot->tts_mintuple;
767765
}
768766

@@ -809,7 +807,6 @@ ExecFetchSlotTupleDatum(TupleTableSlot *slot)
809807
HeapTuple
810808
ExecMaterializeSlot(TupleTableSlot *slot)
811809
{
812-
HeapTuple newTuple;
813810
MemoryContext oldContext;
814811

815812
/*
@@ -822,24 +819,47 @@ ExecMaterializeSlot(TupleTableSlot *slot)
822819
* If we have a regular physical tuple, and it's locally palloc'd, we have
823820
* nothing to do.
824821
*/
825-
if (slot->tts_tuple && slot->tts_shouldFree && slot->tts_mintuple == NULL)
822+
if (slot->tts_tuple && slot->tts_shouldFree)
826823
return slot->tts_tuple;
827824

828825
/*
829-
* Otherwise, copy or build a tuple, and then store it as the new slot
830-
* value. (Note: tts_nvalid will be reset to zero here. There are cases
831-
* in which this could be optimized but it's probably not worth worrying
832-
* about.)
826+
* Otherwise, copy or build a physical tuple, and store it into the slot.
833827
*
834828
* We may be called in a context that is shorter-lived than the tuple
835829
* slot, but we have to ensure that the materialized tuple will survive
836830
* anyway.
837831
*/
838832
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
839-
newTuple = ExecCopySlotTuple(slot);
833+
slot->tts_tuple = ExecCopySlotTuple(slot);
834+
slot->tts_shouldFree = true;
840835
MemoryContextSwitchTo(oldContext);
841836

842-
ExecStoreTuple(newTuple, slot, InvalidBuffer, true);
837+
/*
838+
* Drop the pin on the referenced buffer, if there is one.
839+
*/
840+
if (BufferIsValid(slot->tts_buffer))
841+
ReleaseBuffer(slot->tts_buffer);
842+
843+
slot->tts_buffer = InvalidBuffer;
844+
845+
/*
846+
* Mark extracted state invalid. This is important because the slot
847+
* is not supposed to depend any more on the previous external data;
848+
* we mustn't leave any dangling pass-by-reference datums in tts_values.
849+
* However, we have not actually invalidated any such datums, if there
850+
* happen to be any previously fetched from the slot. (Note in particular
851+
* that we have not pfree'd tts_mintuple, if there is one.)
852+
*/
853+
slot->tts_nvalid = 0;
854+
855+
/*
856+
* On the same principle of not depending on previous remote storage,
857+
* forget the mintuple if it's not local storage. (If it is local storage,
858+
* we must not pfree it now, since callers might have already fetched
859+
* datum pointers referencing it.)
860+
*/
861+
if (!slot->tts_shouldFreeMin)
862+
slot->tts_mintuple = NULL;
843863

844864
return slot->tts_tuple;
845865
}

src/include/executor/tuptable.h

+22-10
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
88
* Portions Copyright (c) 1994, Regents of the University of California
99
*
10-
* $PostgreSQL: pgsql/src/include/executor/tuptable.h,v 1.40 2009/01/01 17:23:59 momjian Exp $
10+
* $PostgreSQL: pgsql/src/include/executor/tuptable.h,v 1.41 2009/03/30 04:08:43 tgl Exp $
1111
*
1212
*-------------------------------------------------------------------------
1313
*/
@@ -49,6 +49,14 @@
4949
* else need to be "materialized" into physical tuples. Note also that a
5050
* virtual tuple does not have any "system columns".
5151
*
52+
* It is also possible for a TupleTableSlot to hold both physical and minimal
53+
* copies of a tuple. This is done when the slot is requested to provide
54+
* the format other than the one it currently holds. (Originally we attempted
55+
* to handle such requests by replacing one format with the other, but that
56+
* had the fatal defect of invalidating any pass-by-reference Datums pointing
57+
* into the existing slot contents.) Both copies must contain identical data
58+
* payloads when this is the case.
59+
*
5260
* The Datum/isnull arrays of a TupleTableSlot serve double duty. When the
5361
* slot contains a virtual tuple, they are the authoritative data. When the
5462
* slot contains a physical tuple, the arrays contain data extracted from
@@ -91,12 +99,12 @@
9199
*
92100
* tts_mintuple must always be NULL if the slot does not hold a "minimal"
93101
* tuple. When it does, tts_mintuple points to the actual MinimalTupleData
94-
* object (the thing to be pfree'd if tts_shouldFree is true). In this case
95-
* tts_tuple points at tts_minhdr and the fields of that are set correctly
102+
* object (the thing to be pfree'd if tts_shouldFreeMin is true). If the slot
103+
* has only a minimal and not also a regular physical tuple, then tts_tuple
104+
* points at tts_minhdr and the fields of that struct are set correctly
96105
* for access to the minimal tuple; in particular, tts_minhdr.t_data points
97-
* MINIMAL_TUPLE_OFFSET bytes before tts_mintuple. (tts_mintuple is therefore
98-
* redundant, but for code simplicity we store it explicitly anyway.) This
99-
* case otherwise behaves identically to the regular-physical-tuple case.
106+
* MINIMAL_TUPLE_OFFSET bytes before tts_mintuple. This allows column
107+
* extraction to treat the case identically to regular physical tuples.
100108
*
101109
* tts_slow/tts_off are saved state for slot_deform_tuple, and should not
102110
* be touched by any other code.
@@ -106,20 +114,24 @@ typedef struct TupleTableSlot
106114
{
107115
NodeTag type; /* vestigial ... allows IsA tests */
108116
bool tts_isempty; /* true = slot is empty */
109-
bool tts_shouldFree; /* should pfree tuple? */
117+
bool tts_shouldFree; /* should pfree tts_tuple? */
118+
bool tts_shouldFreeMin; /* should pfree tts_mintuple? */
110119
bool tts_slow; /* saved state for slot_deform_tuple */
111-
HeapTuple tts_tuple; /* physical tuple, or NULL if none */
120+
HeapTuple tts_tuple; /* physical tuple, or NULL if virtual */
112121
TupleDesc tts_tupleDescriptor; /* slot's tuple descriptor */
113122
MemoryContext tts_mcxt; /* slot itself is in this context */
114123
Buffer tts_buffer; /* tuple's buffer, or InvalidBuffer */
115124
int tts_nvalid; /* # of valid values in tts_values */
116125
Datum *tts_values; /* current per-attribute values */
117126
bool *tts_isnull; /* current per-attribute isnull flags */
118-
MinimalTuple tts_mintuple; /* set if it's a minimal tuple, else NULL */
119-
HeapTupleData tts_minhdr; /* workspace if it's a minimal tuple */
127+
MinimalTuple tts_mintuple; /* minimal tuple, or NULL if none */
128+
HeapTupleData tts_minhdr; /* workspace for minimal-tuple-only case */
120129
long tts_off; /* saved state for slot_deform_tuple */
121130
} TupleTableSlot;
122131

132+
#define TTS_HAS_PHYSICAL_TUPLE(slot) \
133+
((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr))
134+
123135
/*
124136
* Tuple table data structure: an array of TupleTableSlots.
125137
*/

0 commit comments

Comments
 (0)