/src/postgres/src/backend/executor/tstoreReceiver.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * tstoreReceiver.c |
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. |
10 | | * |
11 | | * Also optionally, we can apply a tuple conversion map before storing. |
12 | | * |
13 | | * |
14 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
15 | | * Portions Copyright (c) 1994, Regents of the University of California |
16 | | * |
17 | | * IDENTIFICATION |
18 | | * src/backend/executor/tstoreReceiver.c |
19 | | * |
20 | | *------------------------------------------------------------------------- |
21 | | */ |
22 | | |
23 | | #include "postgres.h" |
24 | | |
25 | | #include "access/detoast.h" |
26 | | #include "access/tupconvert.h" |
27 | | #include "executor/tstoreReceiver.h" |
28 | | |
29 | | |
30 | | typedef struct |
31 | | { |
32 | | DestReceiver pub; |
33 | | /* parameters: */ |
34 | | Tuplestorestate *tstore; /* where to put the data */ |
35 | | MemoryContext cxt; /* context containing tstore */ |
36 | | bool detoast; /* were we told to detoast? */ |
37 | | TupleDesc target_tupdesc; /* target tupdesc, or NULL if none */ |
38 | | const char *map_failure_msg; /* tupdesc mapping failure message */ |
39 | | /* workspace: */ |
40 | | Datum *outvalues; /* values array for result tuple */ |
41 | | Datum *tofree; /* temp values to be pfree'd */ |
42 | | TupleConversionMap *tupmap; /* conversion map, if needed */ |
43 | | TupleTableSlot *mapslot; /* slot for mapped tuples */ |
44 | | } TStoreState; |
45 | | |
46 | | |
47 | | static bool tstoreReceiveSlot_notoast(TupleTableSlot *slot, DestReceiver *self); |
48 | | static bool tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self); |
49 | | static bool tstoreReceiveSlot_tupmap(TupleTableSlot *slot, DestReceiver *self); |
50 | | |
51 | | |
52 | | /* |
53 | | * Prepare to receive tuples from executor. |
54 | | */ |
55 | | static void |
56 | | tstoreStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo) |
57 | 0 | { |
58 | 0 | TStoreState *myState = (TStoreState *) self; |
59 | 0 | bool needtoast = false; |
60 | 0 | int natts = typeinfo->natts; |
61 | 0 | int i; |
62 | | |
63 | | /* Check if any columns require detoast work */ |
64 | 0 | if (myState->detoast) |
65 | 0 | { |
66 | 0 | for (i = 0; i < natts; i++) |
67 | 0 | { |
68 | 0 | CompactAttribute *attr = TupleDescCompactAttr(typeinfo, i); |
69 | |
|
70 | 0 | if (attr->attisdropped) |
71 | 0 | continue; |
72 | 0 | if (attr->attlen == -1) |
73 | 0 | { |
74 | 0 | needtoast = true; |
75 | 0 | break; |
76 | 0 | } |
77 | 0 | } |
78 | 0 | } |
79 | | |
80 | | /* Check if tuple conversion is needed */ |
81 | 0 | if (myState->target_tupdesc) |
82 | 0 | myState->tupmap = convert_tuples_by_position(typeinfo, |
83 | 0 | myState->target_tupdesc, |
84 | 0 | myState->map_failure_msg); |
85 | 0 | else |
86 | 0 | myState->tupmap = NULL; |
87 | | |
88 | | /* Set up appropriate callback */ |
89 | 0 | if (needtoast) |
90 | 0 | { |
91 | 0 | Assert(!myState->tupmap); |
92 | 0 | myState->pub.receiveSlot = tstoreReceiveSlot_detoast; |
93 | | /* Create workspace */ |
94 | 0 | myState->outvalues = (Datum *) |
95 | 0 | MemoryContextAlloc(myState->cxt, natts * sizeof(Datum)); |
96 | 0 | myState->tofree = (Datum *) |
97 | 0 | MemoryContextAlloc(myState->cxt, natts * sizeof(Datum)); |
98 | 0 | myState->mapslot = NULL; |
99 | 0 | } |
100 | 0 | else if (myState->tupmap) |
101 | 0 | { |
102 | 0 | myState->pub.receiveSlot = tstoreReceiveSlot_tupmap; |
103 | 0 | myState->outvalues = NULL; |
104 | 0 | myState->tofree = NULL; |
105 | 0 | myState->mapslot = MakeSingleTupleTableSlot(myState->target_tupdesc, |
106 | 0 | &TTSOpsVirtual); |
107 | 0 | } |
108 | 0 | else |
109 | 0 | { |
110 | 0 | myState->pub.receiveSlot = tstoreReceiveSlot_notoast; |
111 | 0 | myState->outvalues = NULL; |
112 | 0 | myState->tofree = NULL; |
113 | 0 | myState->mapslot = NULL; |
114 | 0 | } |
115 | 0 | } |
116 | | |
117 | | /* |
118 | | * Receive a tuple from the executor and store it in the tuplestore. |
119 | | * This is for the easy case where we don't have to detoast nor map anything. |
120 | | */ |
121 | | static bool |
122 | | tstoreReceiveSlot_notoast(TupleTableSlot *slot, DestReceiver *self) |
123 | 0 | { |
124 | 0 | TStoreState *myState = (TStoreState *) self; |
125 | |
|
126 | 0 | tuplestore_puttupleslot(myState->tstore, slot); |
127 | |
|
128 | 0 | return true; |
129 | 0 | } |
130 | | |
131 | | /* |
132 | | * Receive a tuple from the executor and store it in the tuplestore. |
133 | | * This is for the case where we have to detoast any toasted values. |
134 | | */ |
135 | | static bool |
136 | | tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self) |
137 | 0 | { |
138 | 0 | TStoreState *myState = (TStoreState *) self; |
139 | 0 | TupleDesc typeinfo = slot->tts_tupleDescriptor; |
140 | 0 | int natts = typeinfo->natts; |
141 | 0 | int nfree; |
142 | 0 | int i; |
143 | 0 | MemoryContext oldcxt; |
144 | | |
145 | | /* Make sure the tuple is fully deconstructed */ |
146 | 0 | slot_getallattrs(slot); |
147 | | |
148 | | /* |
149 | | * Fetch back any out-of-line datums. We build the new datums array in |
150 | | * myState->outvalues[] (but we can re-use the slot's isnull array). Also, |
151 | | * remember the fetched values to free afterwards. |
152 | | */ |
153 | 0 | nfree = 0; |
154 | 0 | for (i = 0; i < natts; i++) |
155 | 0 | { |
156 | 0 | Datum val = slot->tts_values[i]; |
157 | 0 | CompactAttribute *attr = TupleDescCompactAttr(typeinfo, i); |
158 | |
|
159 | 0 | if (!attr->attisdropped && attr->attlen == -1 && !slot->tts_isnull[i]) |
160 | 0 | { |
161 | 0 | if (VARATT_IS_EXTERNAL(DatumGetPointer(val))) |
162 | 0 | { |
163 | 0 | val = PointerGetDatum(detoast_external_attr((struct varlena *) |
164 | 0 | DatumGetPointer(val))); |
165 | 0 | myState->tofree[nfree++] = val; |
166 | 0 | } |
167 | 0 | } |
168 | |
|
169 | 0 | myState->outvalues[i] = val; |
170 | 0 | } |
171 | | |
172 | | /* |
173 | | * Push the modified tuple into the tuplestore. |
174 | | */ |
175 | 0 | oldcxt = MemoryContextSwitchTo(myState->cxt); |
176 | 0 | tuplestore_putvalues(myState->tstore, typeinfo, |
177 | 0 | myState->outvalues, slot->tts_isnull); |
178 | 0 | MemoryContextSwitchTo(oldcxt); |
179 | | |
180 | | /* And release any temporary detoasted values */ |
181 | 0 | for (i = 0; i < nfree; i++) |
182 | 0 | pfree(DatumGetPointer(myState->tofree[i])); |
183 | |
|
184 | 0 | return true; |
185 | 0 | } |
186 | | |
187 | | /* |
188 | | * Receive a tuple from the executor and store it in the tuplestore. |
189 | | * This is for the case where we must apply a tuple conversion map. |
190 | | */ |
191 | | static bool |
192 | | tstoreReceiveSlot_tupmap(TupleTableSlot *slot, DestReceiver *self) |
193 | 0 | { |
194 | 0 | TStoreState *myState = (TStoreState *) self; |
195 | |
|
196 | 0 | execute_attr_map_slot(myState->tupmap->attrMap, slot, myState->mapslot); |
197 | 0 | tuplestore_puttupleslot(myState->tstore, myState->mapslot); |
198 | |
|
199 | 0 | return true; |
200 | 0 | } |
201 | | |
202 | | /* |
203 | | * Clean up at end of an executor run |
204 | | */ |
205 | | static void |
206 | | tstoreShutdownReceiver(DestReceiver *self) |
207 | 0 | { |
208 | 0 | TStoreState *myState = (TStoreState *) self; |
209 | | |
210 | | /* Release workspace if any */ |
211 | 0 | if (myState->outvalues) |
212 | 0 | pfree(myState->outvalues); |
213 | 0 | myState->outvalues = NULL; |
214 | 0 | if (myState->tofree) |
215 | 0 | pfree(myState->tofree); |
216 | 0 | myState->tofree = NULL; |
217 | 0 | if (myState->tupmap) |
218 | 0 | free_conversion_map(myState->tupmap); |
219 | 0 | myState->tupmap = NULL; |
220 | 0 | if (myState->mapslot) |
221 | 0 | ExecDropSingleTupleTableSlot(myState->mapslot); |
222 | 0 | myState->mapslot = NULL; |
223 | 0 | } |
224 | | |
225 | | /* |
226 | | * Destroy receiver when done with it |
227 | | */ |
228 | | static void |
229 | | tstoreDestroyReceiver(DestReceiver *self) |
230 | 0 | { |
231 | 0 | pfree(self); |
232 | 0 | } |
233 | | |
234 | | /* |
235 | | * Initially create a DestReceiver object. |
236 | | */ |
237 | | DestReceiver * |
238 | | CreateTuplestoreDestReceiver(void) |
239 | 0 | { |
240 | 0 | TStoreState *self = (TStoreState *) palloc0(sizeof(TStoreState)); |
241 | |
|
242 | 0 | self->pub.receiveSlot = tstoreReceiveSlot_notoast; /* might change */ |
243 | 0 | self->pub.rStartup = tstoreStartupReceiver; |
244 | 0 | self->pub.rShutdown = tstoreShutdownReceiver; |
245 | 0 | self->pub.rDestroy = tstoreDestroyReceiver; |
246 | 0 | self->pub.mydest = DestTuplestore; |
247 | | |
248 | | /* private fields will be set by SetTuplestoreDestReceiverParams */ |
249 | |
|
250 | 0 | return (DestReceiver *) self; |
251 | 0 | } |
252 | | |
253 | | /* |
254 | | * Set parameters for a TuplestoreDestReceiver |
255 | | * |
256 | | * tStore: where to store the tuples |
257 | | * tContext: memory context containing tStore |
258 | | * detoast: forcibly detoast contained data? |
259 | | * target_tupdesc: if not NULL, forcibly convert tuples to this rowtype |
260 | | * map_failure_msg: error message to use if mapping to target_tupdesc fails |
261 | | * |
262 | | * We don't currently support both detoast and target_tupdesc at the same |
263 | | * time, just because no existing caller needs that combination. |
264 | | */ |
265 | | void |
266 | | SetTuplestoreDestReceiverParams(DestReceiver *self, |
267 | | Tuplestorestate *tStore, |
268 | | MemoryContext tContext, |
269 | | bool detoast, |
270 | | TupleDesc target_tupdesc, |
271 | | const char *map_failure_msg) |
272 | 0 | { |
273 | 0 | TStoreState *myState = (TStoreState *) self; |
274 | |
|
275 | 0 | Assert(!(detoast && target_tupdesc)); |
276 | |
|
277 | 0 | Assert(myState->pub.mydest == DestTuplestore); |
278 | 0 | myState->tstore = tStore; |
279 | 0 | myState->cxt = tContext; |
280 | 0 | myState->detoast = detoast; |
281 | 0 | myState->target_tupdesc = target_tupdesc; |
282 | 0 | myState->map_failure_msg = map_failure_msg; |
283 | 0 | } |