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-2019, 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/libpq.h" |
20 | #include "libpq/pqformat.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 bool printtup_20(TupleTableSlot *slot, DestReceiver *self); |
31 | static bool printtup_internal_20(TupleTableSlot *slot, DestReceiver *self); |
32 | static void printtup_shutdown(DestReceiver *self); |
33 | static void printtup_destroy(DestReceiver *self); |
34 | |
35 | static void SendRowDescriptionCols_2(StringInfo buf, TupleDesc typeinfo, |
36 | List *targetlist, int16 *formats); |
37 | static void SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo, |
38 | List *targetlist, int16 *formats); |
39 | |
40 | /* ---------------------------------------------------------------- |
41 | * printtup / debugtup support |
42 | * ---------------------------------------------------------------- |
43 | */ |
44 | |
45 | /* ---------------- |
46 | * Private state for a printtup destination object |
47 | * |
48 | * NOTE: finfo is the lookup info for either typoutput or typsend, whichever |
49 | * we are using for this column. |
50 | * ---------------- |
51 | */ |
52 | typedef struct |
53 | { /* Per-attribute information */ |
54 | Oid typoutput; /* Oid for the type's text output fn */ |
55 | Oid typsend; /* Oid for the type's binary output fn */ |
56 | bool typisvarlena; /* is it varlena (ie possibly toastable)? */ |
57 | int16 format; /* format code for this column */ |
58 | FmgrInfo finfo; /* Precomputed call info for output fn */ |
59 | } PrinttupAttrInfo; |
60 | |
61 | typedef struct |
62 | { |
63 | DestReceiver pub; /* publicly-known function pointers */ |
64 | Portal portal; /* the Portal we are printing from */ |
65 | bool sendDescrip; /* send RowDescription at startup? */ |
66 | TupleDesc attrinfo; /* The attr info we are set up for */ |
67 | int nattrs; |
68 | PrinttupAttrInfo *myinfo; /* Cached info about each attr */ |
69 | StringInfoData buf; /* output buffer (*not* in tmpcontext) */ |
70 | MemoryContext tmpcontext; /* Memory context for per-row workspace */ |
71 | } DR_printtup; |
72 | |
73 | /* ---------------- |
74 | * Initialize: create a DestReceiver for printtup |
75 | * ---------------- |
76 | */ |
77 | DestReceiver * |
78 | printtup_create_DR(CommandDest dest) |
79 | { |
80 | DR_printtup *self = (DR_printtup *) palloc0(sizeof(DR_printtup)); |
81 | |
82 | self->pub.receiveSlot = printtup; /* might get changed later */ |
83 | self->pub.rStartup = printtup_startup; |
84 | self->pub.rShutdown = printtup_shutdown; |
85 | self->pub.rDestroy = printtup_destroy; |
86 | self->pub.mydest = dest; |
87 | |
88 | /* |
89 | * Send T message automatically if DestRemote, but not if |
90 | * DestRemoteExecute |
91 | */ |
92 | self->sendDescrip = (dest == DestRemote); |
93 | |
94 | self->attrinfo = NULL; |
95 | self->nattrs = 0; |
96 | self->myinfo = NULL; |
97 | self->buf.data = NULL; |
98 | self->tmpcontext = NULL; |
99 | |
100 | return (DestReceiver *) self; |
101 | } |
102 | |
103 | /* |
104 | * Set parameters for a DestRemote (or DestRemoteExecute) receiver |
105 | */ |
106 | void |
107 | SetRemoteDestReceiverParams(DestReceiver *self, Portal portal) |
108 | { |
109 | DR_printtup *myState = (DR_printtup *) self; |
110 | |
111 | Assert(myState->pub.mydest == DestRemote || |
112 | myState->pub.mydest == DestRemoteExecute); |
113 | |
114 | myState->portal = portal; |
115 | |
116 | if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) |
117 | { |
118 | /* |
119 | * In protocol 2.0 the Bind message does not exist, so there is no way |
120 | * for the columns to have different print formats; it's sufficient to |
121 | * look at the first one. |
122 | */ |
123 | if (portal->formats && portal->formats[0] != 0) |
124 | myState->pub.receiveSlot = printtup_internal_20; |
125 | else |
126 | myState->pub.receiveSlot = printtup_20; |
127 | } |
128 | } |
129 | |
130 | static void |
131 | printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo) |
132 | { |
133 | DR_printtup *myState = (DR_printtup *) self; |
134 | Portal portal = myState->portal; |
135 | |
136 | /* |
137 | * Create I/O buffer to be used for all messages. This cannot be inside |
138 | * tmpcontext, since we want to re-use it across rows. |
139 | */ |
140 | initStringInfo(&myState->buf); |
141 | |
142 | /* |
143 | * Create a temporary memory context that we can reset once per row to |
144 | * recover palloc'd memory. This avoids any problems with leaks inside |
145 | * datatype output routines, and should be faster than retail pfree's |
146 | * anyway. |
147 | */ |
148 | myState->tmpcontext = AllocSetContextCreate(CurrentMemoryContext, |
149 | "printtup" , |
150 | ALLOCSET_DEFAULT_SIZES); |
151 | |
152 | if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) |
153 | { |
154 | /* |
155 | * Send portal name to frontend (obsolete cruft, gone in proto 3.0) |
156 | * |
157 | * If portal name not specified, use "blank" portal. |
158 | */ |
159 | const char *portalName = portal->name; |
160 | |
161 | if (portalName == NULL || portalName[0] == '\0') |
162 | portalName = "blank" ; |
163 | |
164 | pq_puttextmessage('P', portalName); |
165 | } |
166 | |
167 | /* |
168 | * If we are supposed to emit row descriptions, then send the tuple |
169 | * descriptor of the tuples. |
170 | */ |
171 | if (myState->sendDescrip) |
172 | SendRowDescriptionMessage(&myState->buf, |
173 | typeinfo, |
174 | FetchPortalTargetList(portal), |
175 | portal->formats); |
176 | |
177 | /* ---------------- |
178 | * We could set up the derived attr info at this time, but we postpone it |
179 | * until the first call of printtup, for 2 reasons: |
180 | * 1. We don't waste time (compared to the old way) if there are no |
181 | * tuples at all to output. |
182 | * 2. Checking in printtup allows us to handle the case that the tuples |
183 | * change type midway through (although this probably can't happen in |
184 | * the current executor). |
185 | * ---------------- |
186 | */ |
187 | } |
188 | |
189 | /* |
190 | * SendRowDescriptionMessage --- send a RowDescription message to the frontend |
191 | * |
192 | * Notes: the TupleDesc has typically been manufactured by ExecTypeFromTL() |
193 | * or some similar function; it does not contain a full set of fields. |
194 | * The targetlist will be NIL when executing a utility function that does |
195 | * not have a plan. If the targetlist isn't NIL then it is a Query node's |
196 | * targetlist; it is up to us to ignore resjunk columns in it. The formats[] |
197 | * array pointer might be NULL (if we are doing Describe on a prepared stmt); |
198 | * send zeroes for the format codes in that case. |
199 | */ |
200 | void |
201 | SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo, |
202 | List *targetlist, int16 *formats) |
203 | { |
204 | int natts = typeinfo->natts; |
205 | int proto = PG_PROTOCOL_MAJOR(FrontendProtocol); |
206 | |
207 | /* tuple descriptor message type */ |
208 | pq_beginmessage_reuse(buf, 'T'); |
209 | /* # of attrs in tuples */ |
210 | pq_sendint16(buf, natts); |
211 | |
212 | if (proto >= 3) |
213 | SendRowDescriptionCols_3(buf, typeinfo, targetlist, formats); |
214 | else |
215 | SendRowDescriptionCols_2(buf, typeinfo, targetlist, formats); |
216 | |
217 | pq_endmessage_reuse(buf); |
218 | } |
219 | |
220 | /* |
221 | * Send description for each column when using v3+ protocol |
222 | */ |
223 | static void |
224 | SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats) |
225 | { |
226 | int natts = typeinfo->natts; |
227 | int i; |
228 | ListCell *tlist_item = list_head(targetlist); |
229 | |
230 | /* |
231 | * Preallocate memory for the entire message to be sent. That allows to |
232 | * use the significantly faster inline pqformat.h functions and to avoid |
233 | * reallocations. |
234 | * |
235 | * Have to overestimate the size of the column-names, to account for |
236 | * character set overhead. |
237 | */ |
238 | enlargeStringInfo(buf, (NAMEDATALEN * MAX_CONVERSION_GROWTH /* attname */ |
239 | + sizeof(Oid) /* resorigtbl */ |
240 | + sizeof(AttrNumber) /* resorigcol */ |
241 | + sizeof(Oid) /* atttypid */ |
242 | + sizeof(int16) /* attlen */ |
243 | + sizeof(int32) /* attypmod */ |
244 | + sizeof(int16) /* format */ |
245 | ) * natts); |
246 | |
247 | for (i = 0; i < natts; ++i) |
248 | { |
249 | Form_pg_attribute att = TupleDescAttr(typeinfo, i); |
250 | Oid atttypid = att->atttypid; |
251 | int32 atttypmod = att->atttypmod; |
252 | Oid resorigtbl; |
253 | AttrNumber resorigcol; |
254 | int16 format; |
255 | |
256 | /* |
257 | * If column is a domain, send the base type and typmod instead. |
258 | * Lookup before sending any ints, for efficiency. |
259 | */ |
260 | atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod); |
261 | |
262 | /* Do we have a non-resjunk tlist item? */ |
263 | while (tlist_item && |
264 | ((TargetEntry *) lfirst(tlist_item))->resjunk) |
265 | tlist_item = lnext(tlist_item); |
266 | if (tlist_item) |
267 | { |
268 | TargetEntry *tle = (TargetEntry *) lfirst(tlist_item); |
269 | |
270 | resorigtbl = tle->resorigtbl; |
271 | resorigcol = tle->resorigcol; |
272 | tlist_item = lnext(tlist_item); |
273 | } |
274 | else |
275 | { |
276 | /* No info available, so send zeroes */ |
277 | resorigtbl = 0; |
278 | resorigcol = 0; |
279 | } |
280 | |
281 | if (formats) |
282 | format = formats[i]; |
283 | else |
284 | format = 0; |
285 | |
286 | pq_writestring(buf, NameStr(att->attname)); |
287 | pq_writeint32(buf, resorigtbl); |
288 | pq_writeint16(buf, resorigcol); |
289 | pq_writeint32(buf, atttypid); |
290 | pq_writeint16(buf, att->attlen); |
291 | pq_writeint32(buf, atttypmod); |
292 | pq_writeint16(buf, format); |
293 | } |
294 | } |
295 | |
296 | /* |
297 | * Send description for each column when using v2 protocol |
298 | */ |
299 | static void |
300 | SendRowDescriptionCols_2(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats) |
301 | { |
302 | int natts = typeinfo->natts; |
303 | int i; |
304 | |
305 | for (i = 0; i < natts; ++i) |
306 | { |
307 | Form_pg_attribute att = TupleDescAttr(typeinfo, i); |
308 | Oid atttypid = att->atttypid; |
309 | int32 atttypmod = att->atttypmod; |
310 | |
311 | /* If column is a domain, send the base type and typmod instead */ |
312 | atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod); |
313 | |
314 | pq_sendstring(buf, NameStr(att->attname)); |
315 | /* column ID only info appears in protocol 3.0 and up */ |
316 | pq_sendint32(buf, atttypid); |
317 | pq_sendint16(buf, att->attlen); |
318 | pq_sendint32(buf, atttypmod); |
319 | /* format info only appears in protocol 3.0 and up */ |
320 | } |
321 | } |
322 | |
323 | /* |
324 | * Get the lookup info that printtup() needs |
325 | */ |
326 | static void |
327 | printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs) |
328 | { |
329 | int16 *formats = myState->portal->formats; |
330 | int i; |
331 | |
332 | /* get rid of any old data */ |
333 | if (myState->myinfo) |
334 | pfree(myState->myinfo); |
335 | myState->myinfo = NULL; |
336 | |
337 | myState->attrinfo = typeinfo; |
338 | myState->nattrs = numAttrs; |
339 | if (numAttrs <= 0) |
340 | return; |
341 | |
342 | myState->myinfo = (PrinttupAttrInfo *) |
343 | palloc0(numAttrs * sizeof(PrinttupAttrInfo)); |
344 | |
345 | for (i = 0; i < numAttrs; i++) |
346 | { |
347 | PrinttupAttrInfo *thisState = myState->myinfo + i; |
348 | int16 format = (formats ? formats[i] : 0); |
349 | Form_pg_attribute attr = TupleDescAttr(typeinfo, i); |
350 | |
351 | thisState->format = format; |
352 | if (format == 0) |
353 | { |
354 | getTypeOutputInfo(attr->atttypid, |
355 | &thisState->typoutput, |
356 | &thisState->typisvarlena); |
357 | fmgr_info(thisState->typoutput, &thisState->finfo); |
358 | } |
359 | else if (format == 1) |
360 | { |
361 | getTypeBinaryOutputInfo(attr->atttypid, |
362 | &thisState->typsend, |
363 | &thisState->typisvarlena); |
364 | fmgr_info(thisState->typsend, &thisState->finfo); |
365 | } |
366 | else |
367 | ereport(ERROR, |
368 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
369 | errmsg("unsupported format code: %d" , format))); |
370 | } |
371 | } |
372 | |
373 | /* ---------------- |
374 | * printtup --- print a tuple in protocol 3.0 |
375 | * ---------------- |
376 | */ |
377 | static bool |
378 | printtup(TupleTableSlot *slot, DestReceiver *self) |
379 | { |
380 | TupleDesc typeinfo = slot->tts_tupleDescriptor; |
381 | DR_printtup *myState = (DR_printtup *) self; |
382 | MemoryContext oldcontext; |
383 | StringInfo buf = &myState->buf; |
384 | int natts = typeinfo->natts; |
385 | int i; |
386 | |
387 | /* Set or update my derived attribute info, if needed */ |
388 | if (myState->attrinfo != typeinfo || myState->nattrs != natts) |
389 | printtup_prepare_info(myState, typeinfo, natts); |
390 | |
391 | /* Make sure the tuple is fully deconstructed */ |
392 | slot_getallattrs(slot); |
393 | |
394 | /* Switch into per-row context so we can recover memory below */ |
395 | oldcontext = MemoryContextSwitchTo(myState->tmpcontext); |
396 | |
397 | /* |
398 | * Prepare a DataRow message (note buffer is in per-row context) |
399 | */ |
400 | pq_beginmessage_reuse(buf, 'D'); |
401 | |
402 | pq_sendint16(buf, natts); |
403 | |
404 | /* |
405 | * send the attributes of this tuple |
406 | */ |
407 | for (i = 0; i < natts; ++i) |
408 | { |
409 | PrinttupAttrInfo *thisState = myState->myinfo + i; |
410 | Datum attr = slot->tts_values[i]; |
411 | |
412 | if (slot->tts_isnull[i]) |
413 | { |
414 | pq_sendint32(buf, -1); |
415 | continue; |
416 | } |
417 | |
418 | /* |
419 | * Here we catch undefined bytes in datums that are returned to the |
420 | * client without hitting disk; see comments at the related check in |
421 | * PageAddItem(). This test is most useful for uncompressed, |
422 | * non-external datums, but we're quite likely to see such here when |
423 | * testing new C functions. |
424 | */ |
425 | if (thisState->typisvarlena) |
426 | VALGRIND_CHECK_MEM_IS_DEFINED(DatumGetPointer(attr), |
427 | VARSIZE_ANY(attr)); |
428 | |
429 | if (thisState->format == 0) |
430 | { |
431 | /* Text output */ |
432 | char *outputstr; |
433 | |
434 | outputstr = OutputFunctionCall(&thisState->finfo, attr); |
435 | pq_sendcountedtext(buf, outputstr, strlen(outputstr), false); |
436 | } |
437 | else |
438 | { |
439 | /* Binary output */ |
440 | bytea *outputbytes; |
441 | |
442 | outputbytes = SendFunctionCall(&thisState->finfo, attr); |
443 | pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ); |
444 | pq_sendbytes(buf, VARDATA(outputbytes), |
445 | VARSIZE(outputbytes) - VARHDRSZ); |
446 | } |
447 | } |
448 | |
449 | pq_endmessage_reuse(buf); |
450 | |
451 | /* Return to caller's context, and flush row's temporary memory */ |
452 | MemoryContextSwitchTo(oldcontext); |
453 | MemoryContextReset(myState->tmpcontext); |
454 | |
455 | return true; |
456 | } |
457 | |
458 | /* ---------------- |
459 | * printtup_20 --- print a tuple in protocol 2.0 |
460 | * ---------------- |
461 | */ |
462 | static bool |
463 | printtup_20(TupleTableSlot *slot, DestReceiver *self) |
464 | { |
465 | TupleDesc typeinfo = slot->tts_tupleDescriptor; |
466 | DR_printtup *myState = (DR_printtup *) self; |
467 | MemoryContext oldcontext; |
468 | StringInfo buf = &myState->buf; |
469 | int natts = typeinfo->natts; |
470 | int i, |
471 | j, |
472 | k; |
473 | |
474 | /* Set or update my derived attribute info, if needed */ |
475 | if (myState->attrinfo != typeinfo || myState->nattrs != natts) |
476 | printtup_prepare_info(myState, typeinfo, natts); |
477 | |
478 | /* Make sure the tuple is fully deconstructed */ |
479 | slot_getallattrs(slot); |
480 | |
481 | /* Switch into per-row context so we can recover memory below */ |
482 | oldcontext = MemoryContextSwitchTo(myState->tmpcontext); |
483 | |
484 | /* |
485 | * tell the frontend to expect new tuple data (in ASCII style) |
486 | */ |
487 | pq_beginmessage_reuse(buf, 'D'); |
488 | |
489 | /* |
490 | * send a bitmap of which attributes are not null |
491 | */ |
492 | j = 0; |
493 | k = 1 << 7; |
494 | for (i = 0; i < natts; ++i) |
495 | { |
496 | if (!slot->tts_isnull[i]) |
497 | j |= k; /* set bit if not null */ |
498 | k >>= 1; |
499 | if (k == 0) /* end of byte? */ |
500 | { |
501 | pq_sendint8(buf, j); |
502 | j = 0; |
503 | k = 1 << 7; |
504 | } |
505 | } |
506 | if (k != (1 << 7)) /* flush last partial byte */ |
507 | pq_sendint8(buf, j); |
508 | |
509 | /* |
510 | * send the attributes of this tuple |
511 | */ |
512 | for (i = 0; i < natts; ++i) |
513 | { |
514 | PrinttupAttrInfo *thisState = myState->myinfo + i; |
515 | Datum attr = slot->tts_values[i]; |
516 | char *outputstr; |
517 | |
518 | if (slot->tts_isnull[i]) |
519 | continue; |
520 | |
521 | Assert(thisState->format == 0); |
522 | |
523 | outputstr = OutputFunctionCall(&thisState->finfo, attr); |
524 | pq_sendcountedtext(buf, outputstr, strlen(outputstr), true); |
525 | } |
526 | |
527 | pq_endmessage_reuse(buf); |
528 | |
529 | /* Return to caller's context, and flush row's temporary memory */ |
530 | MemoryContextSwitchTo(oldcontext); |
531 | MemoryContextReset(myState->tmpcontext); |
532 | |
533 | return true; |
534 | } |
535 | |
536 | /* ---------------- |
537 | * printtup_shutdown |
538 | * ---------------- |
539 | */ |
540 | static void |
541 | printtup_shutdown(DestReceiver *self) |
542 | { |
543 | DR_printtup *myState = (DR_printtup *) self; |
544 | |
545 | if (myState->myinfo) |
546 | pfree(myState->myinfo); |
547 | myState->myinfo = NULL; |
548 | |
549 | myState->attrinfo = NULL; |
550 | |
551 | if (myState->buf.data) |
552 | pfree(myState->buf.data); |
553 | myState->buf.data = NULL; |
554 | |
555 | if (myState->tmpcontext) |
556 | MemoryContextDelete(myState->tmpcontext); |
557 | myState->tmpcontext = NULL; |
558 | } |
559 | |
560 | /* ---------------- |
561 | * printtup_destroy |
562 | * ---------------- |
563 | */ |
564 | static void |
565 | printtup_destroy(DestReceiver *self) |
566 | { |
567 | pfree(self); |
568 | } |
569 | |
570 | /* ---------------- |
571 | * printatt |
572 | * ---------------- |
573 | */ |
574 | static void |
575 | printatt(unsigned attributeId, |
576 | Form_pg_attribute attributeP, |
577 | char *value) |
578 | { |
579 | printf("\t%2d: %s%s%s%s\t(typeid = %u, len = %d, typmod = %d, byval = %c)\n" , |
580 | attributeId, |
581 | NameStr(attributeP->attname), |
582 | value != NULL ? " = \"" : "" , |
583 | value != NULL ? value : "" , |
584 | value != NULL ? "\"" : "" , |
585 | (unsigned int) (attributeP->atttypid), |
586 | attributeP->attlen, |
587 | attributeP->atttypmod, |
588 | attributeP->attbyval ? 't' : 'f'); |
589 | } |
590 | |
591 | /* ---------------- |
592 | * debugStartup - prepare to print tuples for an interactive backend |
593 | * ---------------- |
594 | */ |
595 | void |
596 | debugStartup(DestReceiver *self, int operation, TupleDesc typeinfo) |
597 | { |
598 | int natts = typeinfo->natts; |
599 | int i; |
600 | |
601 | /* |
602 | * show the return type of the tuples |
603 | */ |
604 | for (i = 0; i < natts; ++i) |
605 | printatt((unsigned) i + 1, TupleDescAttr(typeinfo, i), NULL); |
606 | printf("\t----\n" ); |
607 | } |
608 | |
609 | /* ---------------- |
610 | * debugtup - print one tuple for an interactive backend |
611 | * ---------------- |
612 | */ |
613 | bool |
614 | debugtup(TupleTableSlot *slot, DestReceiver *self) |
615 | { |
616 | TupleDesc typeinfo = slot->tts_tupleDescriptor; |
617 | int natts = typeinfo->natts; |
618 | int i; |
619 | Datum attr; |
620 | char *value; |
621 | bool isnull; |
622 | Oid typoutput; |
623 | bool typisvarlena; |
624 | |
625 | for (i = 0; i < natts; ++i) |
626 | { |
627 | attr = slot_getattr(slot, i + 1, &isnull); |
628 | if (isnull) |
629 | continue; |
630 | getTypeOutputInfo(TupleDescAttr(typeinfo, i)->atttypid, |
631 | &typoutput, &typisvarlena); |
632 | |
633 | value = OidOutputFunctionCall(typoutput, attr); |
634 | |
635 | printatt((unsigned) i + 1, TupleDescAttr(typeinfo, i), value); |
636 | } |
637 | printf("\t----\n" ); |
638 | |
639 | return true; |
640 | } |
641 | |
642 | /* ---------------- |
643 | * printtup_internal_20 --- print a binary tuple in protocol 2.0 |
644 | * |
645 | * We use a different message type, i.e. 'B' instead of 'D' to |
646 | * indicate a tuple in internal (binary) form. |
647 | * |
648 | * This is largely same as printtup_20, except we use binary formatting. |
649 | * ---------------- |
650 | */ |
651 | static bool |
652 | printtup_internal_20(TupleTableSlot *slot, DestReceiver *self) |
653 | { |
654 | TupleDesc typeinfo = slot->tts_tupleDescriptor; |
655 | DR_printtup *myState = (DR_printtup *) self; |
656 | MemoryContext oldcontext; |
657 | StringInfo buf = &myState->buf; |
658 | int natts = typeinfo->natts; |
659 | int i, |
660 | j, |
661 | k; |
662 | |
663 | /* Set or update my derived attribute info, if needed */ |
664 | if (myState->attrinfo != typeinfo || myState->nattrs != natts) |
665 | printtup_prepare_info(myState, typeinfo, natts); |
666 | |
667 | /* Make sure the tuple is fully deconstructed */ |
668 | slot_getallattrs(slot); |
669 | |
670 | /* Switch into per-row context so we can recover memory below */ |
671 | oldcontext = MemoryContextSwitchTo(myState->tmpcontext); |
672 | |
673 | /* |
674 | * tell the frontend to expect new tuple data (in binary style) |
675 | */ |
676 | pq_beginmessage_reuse(buf, 'B'); |
677 | |
678 | /* |
679 | * send a bitmap of which attributes are not null |
680 | */ |
681 | j = 0; |
682 | k = 1 << 7; |
683 | for (i = 0; i < natts; ++i) |
684 | { |
685 | if (!slot->tts_isnull[i]) |
686 | j |= k; /* set bit if not null */ |
687 | k >>= 1; |
688 | if (k == 0) /* end of byte? */ |
689 | { |
690 | pq_sendint8(buf, j); |
691 | j = 0; |
692 | k = 1 << 7; |
693 | } |
694 | } |
695 | if (k != (1 << 7)) /* flush last partial byte */ |
696 | pq_sendint8(buf, j); |
697 | |
698 | /* |
699 | * send the attributes of this tuple |
700 | */ |
701 | for (i = 0; i < natts; ++i) |
702 | { |
703 | PrinttupAttrInfo *thisState = myState->myinfo + i; |
704 | Datum attr = slot->tts_values[i]; |
705 | bytea *outputbytes; |
706 | |
707 | if (slot->tts_isnull[i]) |
708 | continue; |
709 | |
710 | Assert(thisState->format == 1); |
711 | |
712 | outputbytes = SendFunctionCall(&thisState->finfo, attr); |
713 | pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ); |
714 | pq_sendbytes(buf, VARDATA(outputbytes), |
715 | VARSIZE(outputbytes) - VARHDRSZ); |
716 | } |
717 | |
718 | pq_endmessage_reuse(buf); |
719 | |
720 | /* Return to caller's context, and flush row's temporary memory */ |
721 | MemoryContextSwitchTo(oldcontext); |
722 | MemoryContextReset(myState->tmpcontext); |
723 | |
724 | return true; |
725 | } |
726 | |