Skip to content

Commit ec543db

Browse files
committed
Ensure that the contents of a holdable cursor don't depend on out-of-line
toasted values, since those could get dropped once the cursor's transaction is over. Per bug #4553 from Andrew Gierth. Back-patch as far as 8.1. The bug actually exists back to 7.4 when holdable cursors were introduced, but this patch won't work before 8.1 without significant adjustments. Given the lack of field complaints, it doesn't seem worth the work (and risk of introducing new bugs) to try to make a patch for the older branches.
1 parent 3191ab5 commit ec543db

File tree

4 files changed

+140
-17
lines changed

4 files changed

+140
-17
lines changed

src/backend/commands/portalcmds.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
*
1515
*
1616
* IDENTIFICATION
17-
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.76 2008/11/30 20:51:25 tgl Exp $
17+
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.77 2008/12/01 17:06:21 tgl Exp $
1818
*
1919
*-------------------------------------------------------------------------
2020
*/
@@ -351,11 +351,15 @@ PersistHoldablePortal(Portal portal)
351351
*/
352352
ExecutorRewind(queryDesc);
353353

354-
/* Change the destination to output to the tuplestore */
354+
/*
355+
* Change the destination to output to the tuplestore. Note we
356+
* tell the tuplestore receiver to detoast all data passed through it.
357+
*/
355358
queryDesc->dest = CreateDestReceiver(DestTuplestore);
356359
SetTuplestoreDestReceiverParams(queryDesc->dest,
357360
portal->holdStore,
358-
portal->holdContext);
361+
portal->holdContext,
362+
true);
359363

360364
/* Fetch the result set into the tuplestore */
361365
ExecutorRun(queryDesc, ForwardScanDirection, 0L);

src/backend/executor/tstoreReceiver.c

Lines changed: 127 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,97 @@
11
/*-------------------------------------------------------------------------
22
*
33
* tstoreReceiver.c
4-
* an implementation of DestReceiver that stores the result tuples in
5-
* a Tuplestore
4+
* An implementation of DestReceiver that stores the result tuples in
5+
* a Tuplestore.
6+
*
7+
* Optionally, we can force detoasting (but not decompression) of out-of-line
8+
* toasted values. This is to support cursors WITH HOLD, which must retain
9+
* data even if the underlying table is dropped.
610
*
711
*
812
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
913
* Portions Copyright (c) 1994, Regents of the University of California
1014
*
1115
* IDENTIFICATION
12-
* $PostgreSQL: pgsql/src/backend/executor/tstoreReceiver.c,v 1.20 2008/11/30 20:51:25 tgl Exp $
16+
* $PostgreSQL: pgsql/src/backend/executor/tstoreReceiver.c,v 1.21 2008/12/01 17:06:21 tgl Exp $
1317
*
1418
*-------------------------------------------------------------------------
1519
*/
1620

1721
#include "postgres.h"
1822

23+
#include "access/tuptoaster.h"
1924
#include "executor/tstoreReceiver.h"
2025

2126

2227
typedef struct
2328
{
2429
DestReceiver pub;
25-
Tuplestorestate *tstore;
26-
MemoryContext cxt;
30+
/* parameters: */
31+
Tuplestorestate *tstore; /* where to put the data */
32+
MemoryContext cxt; /* context containing tstore */
33+
bool detoast; /* were we told to detoast? */
34+
/* workspace: */
35+
Datum *outvalues; /* values array for result tuple */
36+
Datum *tofree; /* temp values to be pfree'd */
2737
} TStoreState;
2838

2939

40+
static void tstoreReceiveSlot_notoast(TupleTableSlot *slot, DestReceiver *self);
41+
static void tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self);
42+
43+
3044
/*
3145
* Prepare to receive tuples from executor.
3246
*/
3347
static void
3448
tstoreStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
3549
{
36-
/* do nothing */
50+
TStoreState *myState = (TStoreState *) self;
51+
bool needtoast = false;
52+
Form_pg_attribute *attrs = typeinfo->attrs;
53+
int natts = typeinfo->natts;
54+
int i;
55+
56+
/* Check if any columns require detoast work */
57+
if (myState->detoast)
58+
{
59+
for (i = 0; i < natts; i++)
60+
{
61+
if (attrs[i]->attisdropped)
62+
continue;
63+
if (attrs[i]->attlen == -1)
64+
{
65+
needtoast = true;
66+
break;
67+
}
68+
}
69+
}
70+
71+
/* Set up appropriate callback */
72+
if (needtoast)
73+
{
74+
myState->pub.receiveSlot = tstoreReceiveSlot_detoast;
75+
/* Create workspace */
76+
myState->outvalues = (Datum *)
77+
MemoryContextAlloc(myState->cxt, natts * sizeof(Datum));
78+
myState->tofree = (Datum *)
79+
MemoryContextAlloc(myState->cxt, natts * sizeof(Datum));
80+
}
81+
else
82+
{
83+
myState->pub.receiveSlot = tstoreReceiveSlot_notoast;
84+
myState->outvalues = NULL;
85+
myState->tofree = NULL;
86+
}
3787
}
3888

3989
/*
4090
* Receive a tuple from the executor and store it in the tuplestore.
91+
* This is for the easy case where we don't have to detoast.
4192
*/
4293
static void
43-
tstoreReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
94+
tstoreReceiveSlot_notoast(TupleTableSlot *slot, DestReceiver *self)
4495
{
4596
TStoreState *myState = (TStoreState *) self;
4697
MemoryContext oldcxt = MemoryContextSwitchTo(myState->cxt);
@@ -50,13 +101,77 @@ tstoreReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
50101
MemoryContextSwitchTo(oldcxt);
51102
}
52103

104+
/*
105+
* Receive a tuple from the executor and store it in the tuplestore.
106+
* This is for the case where we have to detoast any toasted values.
107+
*/
108+
static void
109+
tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self)
110+
{
111+
TStoreState *myState = (TStoreState *) self;
112+
TupleDesc typeinfo = slot->tts_tupleDescriptor;
113+
Form_pg_attribute *attrs = typeinfo->attrs;
114+
int natts = typeinfo->natts;
115+
int nfree;
116+
int i;
117+
MemoryContext oldcxt;
118+
119+
/* Make sure the tuple is fully deconstructed */
120+
slot_getallattrs(slot);
121+
122+
/*
123+
* Fetch back any out-of-line datums. We build the new datums array in
124+
* myState->outvalues[] (but we can re-use the slot's isnull array).
125+
* Also, remember the fetched values to free afterwards.
126+
*/
127+
nfree = 0;
128+
for (i = 0; i < natts; i++)
129+
{
130+
Datum val = slot->tts_values[i];
131+
132+
if (!attrs[i]->attisdropped &&
133+
attrs[i]->attlen == -1 &&
134+
!slot->tts_isnull[i])
135+
{
136+
if (VARATT_IS_EXTERNAL(DatumGetPointer(val)))
137+
{
138+
val = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
139+
DatumGetPointer(val)));
140+
myState->tofree[nfree++] = val;
141+
}
142+
}
143+
144+
myState->outvalues[i] = val;
145+
}
146+
147+
/*
148+
* Push the modified tuple into the tuplestore.
149+
*/
150+
oldcxt = MemoryContextSwitchTo(myState->cxt);
151+
tuplestore_putvalues(myState->tstore, typeinfo,
152+
myState->outvalues, slot->tts_isnull);
153+
MemoryContextSwitchTo(oldcxt);
154+
155+
/* And release any temporary detoasted values */
156+
for (i = 0; i < nfree; i++)
157+
pfree(DatumGetPointer(myState->tofree[i]));
158+
}
159+
53160
/*
54161
* Clean up at end of an executor run
55162
*/
56163
static void
57164
tstoreShutdownReceiver(DestReceiver *self)
58165
{
59-
/* do nothing */
166+
TStoreState *myState = (TStoreState *) self;
167+
168+
/* Release workspace if any */
169+
if (myState->outvalues)
170+
pfree(myState->outvalues);
171+
myState->outvalues = NULL;
172+
if (myState->tofree)
173+
pfree(myState->tofree);
174+
myState->tofree = NULL;
60175
}
61176

62177
/*
@@ -76,7 +191,7 @@ CreateTuplestoreDestReceiver(void)
76191
{
77192
TStoreState *self = (TStoreState *) palloc0(sizeof(TStoreState));
78193

79-
self->pub.receiveSlot = tstoreReceiveSlot;
194+
self->pub.receiveSlot = tstoreReceiveSlot_notoast; /* might change */
80195
self->pub.rStartup = tstoreStartupReceiver;
81196
self->pub.rShutdown = tstoreShutdownReceiver;
82197
self->pub.rDestroy = tstoreDestroyReceiver;
@@ -93,11 +208,13 @@ CreateTuplestoreDestReceiver(void)
93208
void
94209
SetTuplestoreDestReceiverParams(DestReceiver *self,
95210
Tuplestorestate *tStore,
96-
MemoryContext tContext)
211+
MemoryContext tContext,
212+
bool detoast)
97213
{
98214
TStoreState *myState = (TStoreState *) self;
99215

100216
Assert(myState->pub.mydest == DestTuplestore);
101217
myState->tstore = tStore;
102218
myState->cxt = tContext;
219+
myState->detoast = detoast;
103220
}

src/backend/tcop/pquery.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.126 2008/11/30 20:51:25 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.127 2008/12/01 17:06:21 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -1036,7 +1036,8 @@ FillPortalStore(Portal portal, bool isTopLevel)
10361036
treceiver = CreateDestReceiver(DestTuplestore);
10371037
SetTuplestoreDestReceiverParams(treceiver,
10381038
portal->holdStore,
1039-
portal->holdContext);
1039+
portal->holdContext,
1040+
false);
10401041

10411042
completionTag[0] = '\0';
10421043

src/include/executor/tstoreReceiver.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
88
* Portions Copyright (c) 1994, Regents of the University of California
99
*
10-
* $PostgreSQL: pgsql/src/include/executor/tstoreReceiver.h,v 1.11 2008/11/30 20:51:25 tgl Exp $
10+
* $PostgreSQL: pgsql/src/include/executor/tstoreReceiver.h,v 1.12 2008/12/01 17:06:21 tgl Exp $
1111
*
1212
*-------------------------------------------------------------------------
1313
*/
@@ -23,6 +23,7 @@ extern DestReceiver *CreateTuplestoreDestReceiver(void);
2323

2424
extern void SetTuplestoreDestReceiverParams(DestReceiver *self,
2525
Tuplestorestate *tStore,
26-
MemoryContext tContext);
26+
MemoryContext tContext,
27+
bool detoast);
2728

2829
#endif /* TSTORE_RECEIVER_H */

0 commit comments

Comments
 (0)