/src/postgres/src/backend/access/common/printtup.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * printtup.c |
4 | | * Routines to print out tuples to the destination (both frontend |
5 | | * clients and standalone backends are supported here). |
6 | | * |
7 | | * |
8 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
9 | | * Portions Copyright (c) 1994, Regents of the University of California |
10 | | * |
11 | | * IDENTIFICATION |
12 | | * src/backend/access/common/printtup.c |
13 | | * |
14 | | *------------------------------------------------------------------------- |
15 | | */ |
16 | | #include "postgres.h" |
17 | | |
18 | | #include "access/printtup.h" |
19 | | #include "libpq/pqformat.h" |
20 | | #include "libpq/protocol.h" |
21 | | #include "tcop/pquery.h" |
22 | | #include "utils/lsyscache.h" |
23 | | #include "utils/memdebug.h" |
24 | | #include "utils/memutils.h" |
25 | | |
26 | | |
27 | | static void printtup_startup(DestReceiver *self, int operation, |
28 | | TupleDesc typeinfo); |
29 | | static bool printtup(TupleTableSlot *slot, DestReceiver *self); |
30 | | static void printtup_shutdown(DestReceiver *self); |
31 | | static void printtup_destroy(DestReceiver *self); |
32 | | |
33 | | /* ---------------------------------------------------------------- |
34 | | * printtup / debugtup support |
35 | | * ---------------------------------------------------------------- |
36 | | */ |
37 | | |
38 | | /* ---------------- |
39 | | * Private state for a printtup destination object |
40 | | * |
41 | | * NOTE: finfo is the lookup info for either typoutput or typsend, whichever |
42 | | * we are using for this column. |
43 | | * ---------------- |
44 | | */ |
45 | | typedef struct |
46 | | { /* Per-attribute information */ |
47 | | Oid typoutput; /* Oid for the type's text output fn */ |
48 | | Oid typsend; /* Oid for the type's binary output fn */ |
49 | | bool typisvarlena; /* is it varlena (ie possibly toastable)? */ |
50 | | int16 format; /* format code for this column */ |
51 | | FmgrInfo finfo; /* Precomputed call info for output fn */ |
52 | | } PrinttupAttrInfo; |
53 | | |
54 | | typedef struct |
55 | | { |
56 | | DestReceiver pub; /* publicly-known function pointers */ |
57 | | Portal portal; /* the Portal we are printing from */ |
58 | | bool sendDescrip; /* send RowDescription at startup? */ |
59 | | TupleDesc attrinfo; /* The attr info we are set up for */ |
60 | | int nattrs; |
61 | | PrinttupAttrInfo *myinfo; /* Cached info about each attr */ |
62 | | StringInfoData buf; /* output buffer (*not* in tmpcontext) */ |
63 | | MemoryContext tmpcontext; /* Memory context for per-row workspace */ |
64 | | } DR_printtup; |
65 | | |
66 | | /* ---------------- |
67 | | * Initialize: create a DestReceiver for printtup |
68 | | * ---------------- |
69 | | */ |
70 | | DestReceiver * |
71 | | printtup_create_DR(CommandDest dest) |
72 | 0 | { |
73 | 0 | DR_printtup *self = (DR_printtup *) palloc0(sizeof(DR_printtup)); |
74 | |
|
75 | 0 | self->pub.receiveSlot = printtup; /* might get changed later */ |
76 | 0 | self->pub.rStartup = printtup_startup; |
77 | 0 | self->pub.rShutdown = printtup_shutdown; |
78 | 0 | self->pub.rDestroy = printtup_destroy; |
79 | 0 | self->pub.mydest = dest; |
80 | | |
81 | | /* |
82 | | * Send T message automatically if DestRemote, but not if |
83 | | * DestRemoteExecute |
84 | | */ |
85 | 0 | self->sendDescrip = (dest == DestRemote); |
86 | |
|
87 | 0 | self->attrinfo = NULL; |
88 | 0 | self->nattrs = 0; |
89 | 0 | self->myinfo = NULL; |
90 | 0 | self->buf.data = NULL; |
91 | 0 | self->tmpcontext = NULL; |
92 | |
|
93 | 0 | return (DestReceiver *) self; |
94 | 0 | } |
95 | | |
96 | | /* |
97 | | * Set parameters for a DestRemote (or DestRemoteExecute) receiver |
98 | | */ |
99 | | void |
100 | | SetRemoteDestReceiverParams(DestReceiver *self, Portal portal) |
101 | 0 | { |
102 | 0 | DR_printtup *myState = (DR_printtup *) self; |
103 | |
|
104 | 0 | Assert(myState->pub.mydest == DestRemote || |
105 | 0 | myState->pub.mydest == DestRemoteExecute); |
106 | |
|
107 | 0 | myState->portal = portal; |
108 | 0 | } |
109 | | |
110 | | static void |
111 | | printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo) |
112 | 0 | { |
113 | 0 | DR_printtup *myState = (DR_printtup *) self; |
114 | 0 | Portal portal = myState->portal; |
115 | | |
116 | | /* |
117 | | * Create I/O buffer to be used for all messages. This cannot be inside |
118 | | * tmpcontext, since we want to re-use it across rows. |
119 | | */ |
120 | 0 | initStringInfo(&myState->buf); |
121 | | |
122 | | /* |
123 | | * Create a temporary memory context that we can reset once per row to |
124 | | * recover palloc'd memory. This avoids any problems with leaks inside |
125 | | * datatype output routines, and should be faster than retail pfree's |
126 | | * anyway. |
127 | | */ |
128 | 0 | myState->tmpcontext = AllocSetContextCreate(CurrentMemoryContext, |
129 | 0 | "printtup", |
130 | 0 | ALLOCSET_DEFAULT_SIZES); |
131 | | |
132 | | /* |
133 | | * If we are supposed to emit row descriptions, then send the tuple |
134 | | * descriptor of the tuples. |
135 | | */ |
136 | 0 | if (myState->sendDescrip) |
137 | 0 | SendRowDescriptionMessage(&myState->buf, |
138 | 0 | typeinfo, |
139 | 0 | FetchPortalTargetList(portal), |
140 | 0 | portal->formats); |
141 | | |
142 | | /* ---------------- |
143 | | * We could set up the derived attr info at this time, but we postpone it |
144 | | * until the first call of printtup, for 2 reasons: |
145 | | * 1. We don't waste time (compared to the old way) if there are no |
146 | | * tuples at all to output. |
147 | | * 2. Checking in printtup allows us to handle the case that the tuples |
148 | | * change type midway through (although this probably can't happen in |
149 | | * the current executor). |
150 | | * ---------------- |
151 | | */ |
152 | 0 | } |
153 | | |
154 | | /* |
155 | | * SendRowDescriptionMessage --- send a RowDescription message to the frontend |
156 | | * |
157 | | * Notes: the TupleDesc has typically been manufactured by ExecTypeFromTL() |
158 | | * or some similar function; it does not contain a full set of fields. |
159 | | * The targetlist will be NIL when executing a utility function that does |
160 | | * not have a plan. If the targetlist isn't NIL then it is a Query node's |
161 | | * targetlist; it is up to us to ignore resjunk columns in it. The formats[] |
162 | | * array pointer might be NULL (if we are doing Describe on a prepared stmt); |
163 | | * send zeroes for the format codes in that case. |
164 | | */ |
165 | | void |
166 | | SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo, |
167 | | List *targetlist, int16 *formats) |
168 | 0 | { |
169 | 0 | int natts = typeinfo->natts; |
170 | 0 | int i; |
171 | 0 | ListCell *tlist_item = list_head(targetlist); |
172 | | |
173 | | /* tuple descriptor message type */ |
174 | 0 | pq_beginmessage_reuse(buf, PqMsg_RowDescription); |
175 | | /* # of attrs in tuples */ |
176 | 0 | pq_sendint16(buf, natts); |
177 | | |
178 | | /* |
179 | | * Preallocate memory for the entire message to be sent. That allows to |
180 | | * use the significantly faster inline pqformat.h functions and to avoid |
181 | | * reallocations. |
182 | | * |
183 | | * Have to overestimate the size of the column-names, to account for |
184 | | * character set overhead. |
185 | | */ |
186 | 0 | enlargeStringInfo(buf, (NAMEDATALEN * MAX_CONVERSION_GROWTH /* attname */ |
187 | 0 | + sizeof(Oid) /* resorigtbl */ |
188 | 0 | + sizeof(AttrNumber) /* resorigcol */ |
189 | 0 | + sizeof(Oid) /* atttypid */ |
190 | 0 | + sizeof(int16) /* attlen */ |
191 | 0 | + sizeof(int32) /* attypmod */ |
192 | 0 | + sizeof(int16) /* format */ |
193 | 0 | ) * natts); |
194 | |
|
195 | 0 | for (i = 0; i < natts; ++i) |
196 | 0 | { |
197 | 0 | Form_pg_attribute att = TupleDescAttr(typeinfo, i); |
198 | 0 | Oid atttypid = att->atttypid; |
199 | 0 | int32 atttypmod = att->atttypmod; |
200 | 0 | Oid resorigtbl; |
201 | 0 | AttrNumber resorigcol; |
202 | 0 | int16 format; |
203 | | |
204 | | /* |
205 | | * If column is a domain, send the base type and typmod instead. |
206 | | * Lookup before sending any ints, for efficiency. |
207 | | */ |
208 | 0 | atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod); |
209 | | |
210 | | /* Do we have a non-resjunk tlist item? */ |
211 | 0 | while (tlist_item && |
212 | 0 | ((TargetEntry *) lfirst(tlist_item))->resjunk) |
213 | 0 | tlist_item = lnext(targetlist, tlist_item); |
214 | 0 | if (tlist_item) |
215 | 0 | { |
216 | 0 | TargetEntry *tle = (TargetEntry *) lfirst(tlist_item); |
217 | |
|
218 | 0 | resorigtbl = tle->resorigtbl; |
219 | 0 | resorigcol = tle->resorigcol; |
220 | 0 | tlist_item = lnext(targetlist, tlist_item); |
221 | 0 | } |
222 | 0 | else |
223 | 0 | { |
224 | | /* No info available, so send zeroes */ |
225 | 0 | resorigtbl = 0; |
226 | 0 | resorigcol = 0; |
227 | 0 | } |
228 | |
|
229 | 0 | if (formats) |
230 | 0 | format = formats[i]; |
231 | 0 | else |
232 | 0 | format = 0; |
233 | |
|
234 | 0 | pq_writestring(buf, NameStr(att->attname)); |
235 | 0 | pq_writeint32(buf, resorigtbl); |
236 | 0 | pq_writeint16(buf, resorigcol); |
237 | 0 | pq_writeint32(buf, atttypid); |
238 | 0 | pq_writeint16(buf, att->attlen); |
239 | 0 | pq_writeint32(buf, atttypmod); |
240 | 0 | pq_writeint16(buf, format); |
241 | 0 | } |
242 | |
|
243 | 0 | pq_endmessage_reuse(buf); |
244 | 0 | } |
245 | | |
246 | | /* |
247 | | * Get the lookup info that printtup() needs |
248 | | */ |
249 | | static void |
250 | | printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs) |
251 | 0 | { |
252 | 0 | int16 *formats = myState->portal->formats; |
253 | 0 | int i; |
254 | | |
255 | | /* get rid of any old data */ |
256 | 0 | if (myState->myinfo) |
257 | 0 | pfree(myState->myinfo); |
258 | 0 | myState->myinfo = NULL; |
259 | |
|
260 | 0 | myState->attrinfo = typeinfo; |
261 | 0 | myState->nattrs = numAttrs; |
262 | 0 | if (numAttrs <= 0) |
263 | 0 | return; |
264 | | |
265 | 0 | myState->myinfo = (PrinttupAttrInfo *) |
266 | 0 | palloc0(numAttrs * sizeof(PrinttupAttrInfo)); |
267 | |
|
268 | 0 | for (i = 0; i < numAttrs; i++) |
269 | 0 | { |
270 | 0 | PrinttupAttrInfo *thisState = myState->myinfo + i; |
271 | 0 | int16 format = (formats ? formats[i] : 0); |
272 | 0 | Form_pg_attribute attr = TupleDescAttr(typeinfo, i); |
273 | |
|
274 | 0 | thisState->format = format; |
275 | 0 | if (format == 0) |
276 | 0 | { |
277 | 0 | getTypeOutputInfo(attr->atttypid, |
278 | 0 | &thisState->typoutput, |
279 | 0 | &thisState->typisvarlena); |
280 | 0 | fmgr_info(thisState->typoutput, &thisState->finfo); |
281 | 0 | } |
282 | 0 | else if (format == 1) |
283 | 0 | { |
284 | 0 | getTypeBinaryOutputInfo(attr->atttypid, |
285 | 0 | &thisState->typsend, |
286 | 0 | &thisState->typisvarlena); |
287 | 0 | fmgr_info(thisState->typsend, &thisState->finfo); |
288 | 0 | } |
289 | 0 | else |
290 | 0 | ereport(ERROR, |
291 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
292 | 0 | errmsg("unsupported format code: %d", format))); |
293 | 0 | } |
294 | 0 | } |
295 | | |
296 | | /* ---------------- |
297 | | * printtup --- send a tuple to the client |
298 | | * |
299 | | * Note: if you change this function, see also serializeAnalyzeReceive |
300 | | * in explain.c, which is meant to replicate the computations done here. |
301 | | * ---------------- |
302 | | */ |
303 | | static bool |
304 | | printtup(TupleTableSlot *slot, DestReceiver *self) |
305 | 0 | { |
306 | 0 | TupleDesc typeinfo = slot->tts_tupleDescriptor; |
307 | 0 | DR_printtup *myState = (DR_printtup *) self; |
308 | 0 | MemoryContext oldcontext; |
309 | 0 | StringInfo buf = &myState->buf; |
310 | 0 | int natts = typeinfo->natts; |
311 | 0 | int i; |
312 | | |
313 | | /* Set or update my derived attribute info, if needed */ |
314 | 0 | if (myState->attrinfo != typeinfo || myState->nattrs != natts) |
315 | 0 | printtup_prepare_info(myState, typeinfo, natts); |
316 | | |
317 | | /* Make sure the tuple is fully deconstructed */ |
318 | 0 | slot_getallattrs(slot); |
319 | | |
320 | | /* Switch into per-row context so we can recover memory below */ |
321 | 0 | oldcontext = MemoryContextSwitchTo(myState->tmpcontext); |
322 | | |
323 | | /* |
324 | | * Prepare a DataRow message (note buffer is in per-query context) |
325 | | */ |
326 | 0 | pq_beginmessage_reuse(buf, PqMsg_DataRow); |
327 | |
|
328 | 0 | pq_sendint16(buf, natts); |
329 | | |
330 | | /* |
331 | | * send the attributes of this tuple |
332 | | */ |
333 | 0 | for (i = 0; i < natts; ++i) |
334 | 0 | { |
335 | 0 | PrinttupAttrInfo *thisState = myState->myinfo + i; |
336 | 0 | Datum attr = slot->tts_values[i]; |
337 | |
|
338 | 0 | if (slot->tts_isnull[i]) |
339 | 0 | { |
340 | 0 | pq_sendint32(buf, -1); |
341 | 0 | continue; |
342 | 0 | } |
343 | | |
344 | | /* |
345 | | * Here we catch undefined bytes in datums that are returned to the |
346 | | * client without hitting disk; see comments at the related check in |
347 | | * PageAddItem(). This test is most useful for uncompressed, |
348 | | * non-external datums, but we're quite likely to see such here when |
349 | | * testing new C functions. |
350 | | */ |
351 | 0 | if (thisState->typisvarlena) |
352 | 0 | VALGRIND_CHECK_MEM_IS_DEFINED(DatumGetPointer(attr), |
353 | 0 | VARSIZE_ANY(attr)); |
354 | |
|
355 | 0 | if (thisState->format == 0) |
356 | 0 | { |
357 | | /* Text output */ |
358 | 0 | char *outputstr; |
359 | |
|
360 | 0 | outputstr = OutputFunctionCall(&thisState->finfo, attr); |
361 | 0 | pq_sendcountedtext(buf, outputstr, strlen(outputstr)); |
362 | 0 | } |
363 | 0 | else |
364 | 0 | { |
365 | | /* Binary output */ |
366 | 0 | bytea *outputbytes; |
367 | |
|
368 | 0 | outputbytes = SendFunctionCall(&thisState->finfo, attr); |
369 | 0 | pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ); |
370 | 0 | pq_sendbytes(buf, VARDATA(outputbytes), |
371 | 0 | VARSIZE(outputbytes) - VARHDRSZ); |
372 | 0 | } |
373 | 0 | } |
374 | |
|
375 | 0 | pq_endmessage_reuse(buf); |
376 | | |
377 | | /* Return to caller's context, and flush row's temporary memory */ |
378 | 0 | MemoryContextSwitchTo(oldcontext); |
379 | 0 | MemoryContextReset(myState->tmpcontext); |
380 | |
|
381 | 0 | return true; |
382 | 0 | } |
383 | | |
384 | | /* ---------------- |
385 | | * printtup_shutdown |
386 | | * ---------------- |
387 | | */ |
388 | | static void |
389 | | printtup_shutdown(DestReceiver *self) |
390 | 0 | { |
391 | 0 | DR_printtup *myState = (DR_printtup *) self; |
392 | |
|
393 | 0 | if (myState->myinfo) |
394 | 0 | pfree(myState->myinfo); |
395 | 0 | myState->myinfo = NULL; |
396 | |
|
397 | 0 | myState->attrinfo = NULL; |
398 | |
|
399 | 0 | if (myState->buf.data) |
400 | 0 | pfree(myState->buf.data); |
401 | 0 | myState->buf.data = NULL; |
402 | |
|
403 | 0 | if (myState->tmpcontext) |
404 | 0 | MemoryContextDelete(myState->tmpcontext); |
405 | 0 | myState->tmpcontext = NULL; |
406 | 0 | } |
407 | | |
408 | | /* ---------------- |
409 | | * printtup_destroy |
410 | | * ---------------- |
411 | | */ |
412 | | static void |
413 | | printtup_destroy(DestReceiver *self) |
414 | 0 | { |
415 | 0 | pfree(self); |
416 | 0 | } |
417 | | |
418 | | /* ---------------- |
419 | | * printatt |
420 | | * ---------------- |
421 | | */ |
422 | | static void |
423 | | printatt(unsigned attributeId, |
424 | | Form_pg_attribute attributeP, |
425 | | char *value) |
426 | 0 | { |
427 | 0 | printf("\t%2d: %s%s%s%s\t(typeid = %u, len = %d, typmod = %d, byval = %c)\n", |
428 | 0 | attributeId, |
429 | 0 | NameStr(attributeP->attname), |
430 | 0 | value != NULL ? " = \"" : "", |
431 | 0 | value != NULL ? value : "", |
432 | 0 | value != NULL ? "\"" : "", |
433 | 0 | (unsigned int) (attributeP->atttypid), |
434 | 0 | attributeP->attlen, |
435 | 0 | attributeP->atttypmod, |
436 | 0 | attributeP->attbyval ? 't' : 'f'); |
437 | 0 | } |
438 | | |
439 | | /* ---------------- |
440 | | * debugStartup - prepare to print tuples for an interactive backend |
441 | | * ---------------- |
442 | | */ |
443 | | void |
444 | | debugStartup(DestReceiver *self, int operation, TupleDesc typeinfo) |
445 | 0 | { |
446 | 0 | int natts = typeinfo->natts; |
447 | 0 | int i; |
448 | | |
449 | | /* |
450 | | * show the return type of the tuples |
451 | | */ |
452 | 0 | for (i = 0; i < natts; ++i) |
453 | 0 | printatt((unsigned) i + 1, TupleDescAttr(typeinfo, i), NULL); |
454 | 0 | printf("\t----\n"); |
455 | 0 | } |
456 | | |
457 | | /* ---------------- |
458 | | * debugtup - print one tuple for an interactive backend |
459 | | * ---------------- |
460 | | */ |
461 | | bool |
462 | | debugtup(TupleTableSlot *slot, DestReceiver *self) |
463 | 0 | { |
464 | 0 | TupleDesc typeinfo = slot->tts_tupleDescriptor; |
465 | 0 | int natts = typeinfo->natts; |
466 | 0 | int i; |
467 | 0 | Datum attr; |
468 | 0 | char *value; |
469 | 0 | bool isnull; |
470 | 0 | Oid typoutput; |
471 | 0 | bool typisvarlena; |
472 | |
|
473 | 0 | for (i = 0; i < natts; ++i) |
474 | 0 | { |
475 | 0 | attr = slot_getattr(slot, i + 1, &isnull); |
476 | 0 | if (isnull) |
477 | 0 | continue; |
478 | 0 | getTypeOutputInfo(TupleDescAttr(typeinfo, i)->atttypid, |
479 | 0 | &typoutput, &typisvarlena); |
480 | |
|
481 | 0 | value = OidOutputFunctionCall(typoutput, attr); |
482 | |
|
483 | 0 | printatt((unsigned) i + 1, TupleDescAttr(typeinfo, i), value); |
484 | 0 | } |
485 | 0 | printf("\t----\n"); |
486 | |
|
487 | 0 | return true; |
488 | 0 | } |