]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/rowtypes.c
Pgindent run for 8.0.
[postgresql] / src / backend / utils / adt / rowtypes.c
1 /*-------------------------------------------------------------------------
2  *
3  * rowtypes.c
4  *        I/O functions for generic composite types.
5  *
6  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.6 2004/08/29 05:06:49 momjian Exp $
12  *
13  *-------------------------------------------------------------------------
14  */
15 #include "postgres.h"
16
17 #include <ctype.h>
18
19 #include "access/heapam.h"
20 #include "access/htup.h"
21 #include "catalog/pg_type.h"
22 #include "lib/stringinfo.h"
23 #include "libpq/pqformat.h"
24 #include "utils/builtins.h"
25 #include "utils/lsyscache.h"
26 #include "utils/typcache.h"
27
28
29 /*
30  * structure to cache metadata needed for record I/O
31  */
32 typedef struct ColumnIOData
33 {
34         Oid                     column_type;
35         Oid                     typiofunc;
36         Oid                     typioparam;
37         FmgrInfo        proc;
38 } ColumnIOData;
39
40 typedef struct RecordIOData
41 {
42         Oid                     record_type;
43         int32           record_typmod;
44         int                     ncolumns;
45         ColumnIOData columns[1];        /* VARIABLE LENGTH ARRAY */
46 } RecordIOData;
47
48
49 /*
50  * record_in            - input routine for any composite type.
51  */
52 Datum
53 record_in(PG_FUNCTION_ARGS)
54 {
55         char       *string = PG_GETARG_CSTRING(0);
56         Oid                     tupType = PG_GETARG_OID(1);
57         int32           tupTypmod;
58         TupleDesc       tupdesc;
59         HeapTuple       tuple;
60         RecordIOData *my_extra;
61         bool            needComma = false;
62         int                     ncolumns;
63         int                     i;
64         char       *ptr;
65         Datum      *values;
66         char       *nulls;
67         StringInfoData buf;
68
69         /*
70          * Use the passed type unless it's RECORD; we can't support input of
71          * anonymous types, mainly because there's no good way to figure out
72          * which anonymous type is wanted.      Note that for RECORD, what we'll
73          * probably actually get is RECORD's typelem, ie, zero.
74          */
75         if (tupType == InvalidOid || tupType == RECORDOID)
76                 ereport(ERROR,
77                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
78                 errmsg("input of anonymous composite types is not implemented")));
79         tupTypmod = -1;                         /* for all non-anonymous types */
80         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
81         ncolumns = tupdesc->natts;
82
83         /*
84          * We arrange to look up the needed I/O info just once per series of
85          * calls, assuming the record type doesn't change underneath us.
86          */
87         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
88         if (my_extra == NULL ||
89                 my_extra->ncolumns != ncolumns)
90         {
91                 fcinfo->flinfo->fn_extra =
92                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
93                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
94                                                            + ncolumns * sizeof(ColumnIOData));
95                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
96                 my_extra->record_type = InvalidOid;
97                 my_extra->record_typmod = 0;
98         }
99
100         if (my_extra->record_type != tupType ||
101                 my_extra->record_typmod != tupTypmod)
102         {
103                 MemSet(my_extra, 0,
104                            sizeof(RecordIOData) - sizeof(ColumnIOData)
105                            + ncolumns * sizeof(ColumnIOData));
106                 my_extra->record_type = tupType;
107                 my_extra->record_typmod = tupTypmod;
108                 my_extra->ncolumns = ncolumns;
109         }
110
111         values = (Datum *) palloc(ncolumns * sizeof(Datum));
112         nulls = (char *) palloc(ncolumns * sizeof(char));
113
114         /*
115          * Scan the string.  We use "buf" to accumulate the de-quoted data for
116          * each column, which is then fed to the appropriate input converter.
117          */
118         ptr = string;
119         /* Allow leading whitespace */
120         while (*ptr && isspace((unsigned char) *ptr))
121                 ptr++;
122         if (*ptr++ != '(')
123                 ereport(ERROR,
124                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
125                                  errmsg("malformed record literal: \"%s\"", string),
126                                  errdetail("Missing left parenthesis.")));
127
128         initStringInfo(&buf);
129
130         for (i = 0; i < ncolumns; i++)
131         {
132                 ColumnIOData *column_info = &my_extra->columns[i];
133                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
134
135                 /* Ignore dropped columns in datatype, but fill with nulls */
136                 if (tupdesc->attrs[i]->attisdropped)
137                 {
138                         values[i] = (Datum) 0;
139                         nulls[i] = 'n';
140                         continue;
141                 }
142
143                 if (needComma)
144                 {
145                         /* Skip comma that separates prior field from this one */
146                         if (*ptr == ',')
147                                 ptr++;
148                         else
149 /* *ptr must be ')' */
150                                 ereport(ERROR,
151                                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
152                                           errmsg("malformed record literal: \"%s\"", string),
153                                                  errdetail("Too few columns.")));
154                 }
155
156                 /* Check for null: completely empty input means null */
157                 if (*ptr == ',' || *ptr == ')')
158                 {
159                         values[i] = (Datum) 0;
160                         nulls[i] = 'n';
161                 }
162                 else
163                 {
164                         /* Extract string for this column */
165                         bool            inquote = false;
166
167                         buf.len = 0;
168                         buf.data[0] = '\0';
169                         while (inquote || !(*ptr == ',' || *ptr == ')'))
170                         {
171                                 char            ch = *ptr++;
172
173                                 if (ch == '\0')
174                                         ereport(ERROR,
175                                                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
176                                                          errmsg("malformed record literal: \"%s\"",
177                                                                         string),
178                                                          errdetail("Unexpected end of input.")));
179                                 if (ch == '\\')
180                                 {
181                                         if (*ptr == '\0')
182                                                 ereport(ERROR,
183                                                    (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
184                                                         errmsg("malformed record literal: \"%s\"",
185                                                                    string),
186                                                         errdetail("Unexpected end of input.")));
187                                         appendStringInfoChar(&buf, *ptr++);
188                                 }
189                                 else if (ch == '\"')
190                                 {
191                                         if (!inquote)
192                                                 inquote = true;
193                                         else if (*ptr == '\"')
194                                         {
195                                                 /* doubled quote within quote sequence */
196                                                 appendStringInfoChar(&buf, *ptr++);
197                                         }
198                                         else
199                                                 inquote = false;
200                                 }
201                                 else
202                                         appendStringInfoChar(&buf, ch);
203                         }
204
205                         /*
206                          * Convert the column value
207                          */
208                         if (column_info->column_type != column_type)
209                         {
210                                 getTypeInputInfo(column_type,
211                                                                  &column_info->typiofunc,
212                                                                  &column_info->typioparam);
213                                 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
214                                                           fcinfo->flinfo->fn_mcxt);
215                                 column_info->column_type = column_type;
216                         }
217
218                         values[i] = FunctionCall3(&column_info->proc,
219                                                                           CStringGetDatum(buf.data),
220                                                            ObjectIdGetDatum(column_info->typioparam),
221                                                         Int32GetDatum(tupdesc->attrs[i]->atttypmod));
222                         nulls[i] = ' ';
223                 }
224
225                 /*
226                  * Prep for next column
227                  */
228                 needComma = true;
229         }
230
231         if (*ptr++ != ')')
232                 ereport(ERROR,
233                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
234                                  errmsg("malformed record literal: \"%s\"", string),
235                                  errdetail("Too many columns.")));
236         /* Allow trailing whitespace */
237         while (*ptr && isspace((unsigned char) *ptr))
238                 ptr++;
239         if (*ptr)
240                 ereport(ERROR,
241                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
242                                  errmsg("malformed record literal: \"%s\"", string),
243                                  errdetail("Junk after right parenthesis.")));
244
245         tuple = heap_formtuple(tupdesc, values, nulls);
246
247         pfree(buf.data);
248         pfree(values);
249         pfree(nulls);
250
251         PG_RETURN_HEAPTUPLEHEADER(tuple->t_data);
252 }
253
254 /*
255  * record_out           - output routine for any composite type.
256  */
257 Datum
258 record_out(PG_FUNCTION_ARGS)
259 {
260         HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
261         Oid                     tupType = PG_GETARG_OID(1);
262         int32           tupTypmod;
263         TupleDesc       tupdesc;
264         HeapTupleData tuple;
265         RecordIOData *my_extra;
266         bool            needComma = false;
267         int                     ncolumns;
268         int                     i;
269         Datum      *values;
270         char       *nulls;
271         StringInfoData buf;
272
273         /*
274          * Use the passed type unless it's RECORD; in that case, we'd better
275          * get the type info out of the datum itself.  Note that for RECORD,
276          * what we'll probably actually get is RECORD's typelem, ie, zero.
277          */
278         if (tupType == InvalidOid || tupType == RECORDOID)
279         {
280                 tupType = HeapTupleHeaderGetTypeId(rec);
281                 tupTypmod = HeapTupleHeaderGetTypMod(rec);
282         }
283         else
284                 tupTypmod = -1;
285         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
286         ncolumns = tupdesc->natts;
287
288         /* Build a temporary HeapTuple control structure */
289         tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
290         ItemPointerSetInvalid(&(tuple.t_self));
291         tuple.t_tableOid = InvalidOid;
292         tuple.t_data = rec;
293
294         /*
295          * We arrange to look up the needed I/O info just once per series of
296          * calls, assuming the record type doesn't change underneath us.
297          */
298         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
299         if (my_extra == NULL ||
300                 my_extra->ncolumns != ncolumns)
301         {
302                 fcinfo->flinfo->fn_extra =
303                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
304                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
305                                                            + ncolumns * sizeof(ColumnIOData));
306                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
307                 my_extra->record_type = InvalidOid;
308                 my_extra->record_typmod = 0;
309         }
310
311         if (my_extra->record_type != tupType ||
312                 my_extra->record_typmod != tupTypmod)
313         {
314                 MemSet(my_extra, 0,
315                            sizeof(RecordIOData) - sizeof(ColumnIOData)
316                            + ncolumns * sizeof(ColumnIOData));
317                 my_extra->record_type = tupType;
318                 my_extra->record_typmod = tupTypmod;
319                 my_extra->ncolumns = ncolumns;
320         }
321
322         values = (Datum *) palloc(ncolumns * sizeof(Datum));
323         nulls = (char *) palloc(ncolumns * sizeof(char));
324
325         /* Break down the tuple into fields */
326         heap_deformtuple(&tuple, tupdesc, values, nulls);
327
328         /* And build the result string */
329         initStringInfo(&buf);
330
331         appendStringInfoChar(&buf, '(');
332
333         for (i = 0; i < ncolumns; i++)
334         {
335                 ColumnIOData *column_info = &my_extra->columns[i];
336                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
337                 char       *value;
338                 char       *tmp;
339                 bool            nq;
340
341                 /* Ignore dropped columns in datatype */
342                 if (tupdesc->attrs[i]->attisdropped)
343                         continue;
344
345                 if (needComma)
346                         appendStringInfoChar(&buf, ',');
347                 needComma = true;
348
349                 if (nulls[i] == 'n')
350                 {
351                         /* emit nothing... */
352                         continue;
353                 }
354
355                 /*
356                  * Convert the column value to text
357                  */
358                 if (column_info->column_type != column_type)
359                 {
360                         bool            typIsVarlena;
361
362                         getTypeOutputInfo(column_type,
363                                                           &column_info->typiofunc,
364                                                           &column_info->typioparam,
365                                                           &typIsVarlena);
366                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
367                                                   fcinfo->flinfo->fn_mcxt);
368                         column_info->column_type = column_type;
369                 }
370
371                 value = DatumGetCString(FunctionCall3(&column_info->proc,
372                                                                                           values[i],
373                                                            ObjectIdGetDatum(column_info->typioparam),
374                                                    Int32GetDatum(tupdesc->attrs[i]->atttypmod)));
375
376                 /* Detect whether we need double quotes for this value */
377                 nq = (value[0] == '\0');        /* force quotes for empty string */
378                 for (tmp = value; *tmp; tmp++)
379                 {
380                         char            ch = *tmp;
381
382                         if (ch == '"' || ch == '\\' ||
383                                 ch == '(' || ch == ')' || ch == ',' ||
384                                 isspace((unsigned char) ch))
385                         {
386                                 nq = true;
387                                 break;
388                         }
389                 }
390
391                 /* And emit the string */
392                 if (nq)
393                         appendStringInfoChar(&buf, '"');
394                 for (tmp = value; *tmp; tmp++)
395                 {
396                         char            ch = *tmp;
397
398                         if (ch == '"' || ch == '\\')
399                                 appendStringInfoChar(&buf, ch);
400                         appendStringInfoChar(&buf, ch);
401                 }
402                 if (nq)
403                         appendStringInfoChar(&buf, '"');
404         }
405
406         appendStringInfoChar(&buf, ')');
407
408         pfree(values);
409         pfree(nulls);
410
411         PG_RETURN_CSTRING(buf.data);
412 }
413
414 /*
415  * record_recv          - binary input routine for any composite type.
416  */
417 Datum
418 record_recv(PG_FUNCTION_ARGS)
419 {
420         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
421         Oid                     tupType = PG_GETARG_OID(1);
422         int32           tupTypmod;
423         TupleDesc       tupdesc;
424         HeapTuple       tuple;
425         RecordIOData *my_extra;
426         int                     ncolumns;
427         int                     usercols;
428         int                     validcols;
429         int                     i;
430         Datum      *values;
431         char       *nulls;
432
433         /*
434          * Use the passed type unless it's RECORD; we can't support input of
435          * anonymous types, mainly because there's no good way to figure out
436          * which anonymous type is wanted.      Note that for RECORD, what we'll
437          * probably actually get is RECORD's typelem, ie, zero.
438          */
439         if (tupType == InvalidOid || tupType == RECORDOID)
440                 ereport(ERROR,
441                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
442                 errmsg("input of anonymous composite types is not implemented")));
443         tupTypmod = -1;                         /* for all non-anonymous types */
444         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
445         ncolumns = tupdesc->natts;
446
447         /*
448          * We arrange to look up the needed I/O info just once per series of
449          * calls, assuming the record type doesn't change underneath us.
450          */
451         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
452         if (my_extra == NULL ||
453                 my_extra->ncolumns != ncolumns)
454         {
455                 fcinfo->flinfo->fn_extra =
456                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
457                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
458                                                            + ncolumns * sizeof(ColumnIOData));
459                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
460                 my_extra->record_type = InvalidOid;
461                 my_extra->record_typmod = 0;
462         }
463
464         if (my_extra->record_type != tupType ||
465                 my_extra->record_typmod != tupTypmod)
466         {
467                 MemSet(my_extra, 0,
468                            sizeof(RecordIOData) - sizeof(ColumnIOData)
469                            + ncolumns * sizeof(ColumnIOData));
470                 my_extra->record_type = tupType;
471                 my_extra->record_typmod = tupTypmod;
472                 my_extra->ncolumns = ncolumns;
473         }
474
475         values = (Datum *) palloc(ncolumns * sizeof(Datum));
476         nulls = (char *) palloc(ncolumns * sizeof(char));
477
478         /* Fetch number of columns user thinks it has */
479         usercols = pq_getmsgint(buf, 4);
480
481         /* Need to scan to count nondeleted columns */
482         validcols = 0;
483         for (i = 0; i < ncolumns; i++)
484         {
485                 if (!tupdesc->attrs[i]->attisdropped)
486                         validcols++;
487         }
488         if (usercols != validcols)
489                 ereport(ERROR,
490                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
491                                  errmsg("wrong number of columns: %d, expected %d",
492                                                 usercols, validcols)));
493
494         /* Process each column */
495         for (i = 0; i < ncolumns; i++)
496         {
497                 ColumnIOData *column_info = &my_extra->columns[i];
498                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
499                 Oid                     coltypoid;
500                 int                     itemlen;
501
502                 /* Ignore dropped columns in datatype, but fill with nulls */
503                 if (tupdesc->attrs[i]->attisdropped)
504                 {
505                         values[i] = (Datum) 0;
506                         nulls[i] = 'n';
507                         continue;
508                 }
509
510                 /* Verify column datatype */
511                 coltypoid = pq_getmsgint(buf, sizeof(Oid));
512                 if (coltypoid != column_type)
513                         ereport(ERROR,
514                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
515                                          errmsg("wrong data type: %u, expected %u",
516                                                         coltypoid, column_type)));
517
518                 /* Get and check the item length */
519                 itemlen = pq_getmsgint(buf, 4);
520                 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
521                         ereport(ERROR,
522                                         (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
523                                          errmsg("insufficient data left in message")));
524
525                 if (itemlen == -1)
526                 {
527                         /* -1 length means NULL */
528                         values[i] = (Datum) 0;
529                         nulls[i] = 'n';
530                 }
531                 else
532                 {
533                         /*
534                          * Rather than copying data around, we just set up a phony
535                          * StringInfo pointing to the correct portion of the input
536                          * buffer. We assume we can scribble on the input buffer so as
537                          * to maintain the convention that StringInfos have a trailing
538                          * null.
539                          */
540                         StringInfoData item_buf;
541                         char            csave;
542
543                         item_buf.data = &buf->data[buf->cursor];
544                         item_buf.maxlen = itemlen + 1;
545                         item_buf.len = itemlen;
546                         item_buf.cursor = 0;
547
548                         buf->cursor += itemlen;
549
550                         csave = buf->data[buf->cursor];
551                         buf->data[buf->cursor] = '\0';
552
553                         /* Now call the column's receiveproc */
554                         if (column_info->column_type != column_type)
555                         {
556                                 getTypeBinaryInputInfo(column_type,
557                                                                            &column_info->typiofunc,
558                                                                            &column_info->typioparam);
559                                 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
560                                                           fcinfo->flinfo->fn_mcxt);
561                                 column_info->column_type = column_type;
562                         }
563
564                         values[i] = FunctionCall2(&column_info->proc,
565                                                                           PointerGetDatum(&item_buf),
566                                                           ObjectIdGetDatum(column_info->typioparam));
567
568                         nulls[i] = ' ';
569
570                         /* Trouble if it didn't eat the whole buffer */
571                         if (item_buf.cursor != itemlen)
572                                 ereport(ERROR,
573                                                 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
574                                          errmsg("improper binary format in record column %d",
575                                                         i + 1)));
576
577                         buf->data[buf->cursor] = csave;
578                 }
579         }
580
581         tuple = heap_formtuple(tupdesc, values, nulls);
582
583         pfree(values);
584         pfree(nulls);
585
586         PG_RETURN_HEAPTUPLEHEADER(tuple->t_data);
587 }
588
589 /*
590  * record_send          - binary output routine for any composite type.
591  */
592 Datum
593 record_send(PG_FUNCTION_ARGS)
594 {
595         HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
596         Oid                     tupType = PG_GETARG_OID(1);
597         int32           tupTypmod;
598         TupleDesc       tupdesc;
599         HeapTupleData tuple;
600         RecordIOData *my_extra;
601         int                     ncolumns;
602         int                     validcols;
603         int                     i;
604         Datum      *values;
605         char       *nulls;
606         StringInfoData buf;
607
608         /*
609          * Use the passed type unless it's RECORD; in that case, we'd better
610          * get the type info out of the datum itself.  Note that for RECORD,
611          * what we'll probably actually get is RECORD's typelem, ie, zero.
612          */
613         if (tupType == InvalidOid || tupType == RECORDOID)
614         {
615                 tupType = HeapTupleHeaderGetTypeId(rec);
616                 tupTypmod = HeapTupleHeaderGetTypMod(rec);
617         }
618         else
619                 tupTypmod = -1;
620         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
621         ncolumns = tupdesc->natts;
622
623         /* Build a temporary HeapTuple control structure */
624         tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
625         ItemPointerSetInvalid(&(tuple.t_self));
626         tuple.t_tableOid = InvalidOid;
627         tuple.t_data = rec;
628
629         /*
630          * We arrange to look up the needed I/O info just once per series of
631          * calls, assuming the record type doesn't change underneath us.
632          */
633         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
634         if (my_extra == NULL ||
635                 my_extra->ncolumns != ncolumns)
636         {
637                 fcinfo->flinfo->fn_extra =
638                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
639                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
640                                                            + ncolumns * sizeof(ColumnIOData));
641                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
642                 my_extra->record_type = InvalidOid;
643                 my_extra->record_typmod = 0;
644         }
645
646         if (my_extra->record_type != tupType ||
647                 my_extra->record_typmod != tupTypmod)
648         {
649                 MemSet(my_extra, 0,
650                            sizeof(RecordIOData) - sizeof(ColumnIOData)
651                            + ncolumns * sizeof(ColumnIOData));
652                 my_extra->record_type = tupType;
653                 my_extra->record_typmod = tupTypmod;
654                 my_extra->ncolumns = ncolumns;
655         }
656
657         values = (Datum *) palloc(ncolumns * sizeof(Datum));
658         nulls = (char *) palloc(ncolumns * sizeof(char));
659
660         /* Break down the tuple into fields */
661         heap_deformtuple(&tuple, tupdesc, values, nulls);
662
663         /* And build the result string */
664         pq_begintypsend(&buf);
665
666         /* Need to scan to count nondeleted columns */
667         validcols = 0;
668         for (i = 0; i < ncolumns; i++)
669         {
670                 if (!tupdesc->attrs[i]->attisdropped)
671                         validcols++;
672         }
673         pq_sendint(&buf, validcols, 4);
674
675         for (i = 0; i < ncolumns; i++)
676         {
677                 ColumnIOData *column_info = &my_extra->columns[i];
678                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
679                 bytea      *outputbytes;
680
681                 /* Ignore dropped columns in datatype */
682                 if (tupdesc->attrs[i]->attisdropped)
683                         continue;
684
685                 pq_sendint(&buf, column_type, sizeof(Oid));
686
687                 if (nulls[i] == 'n')
688                 {
689                         /* emit -1 data length to signify a NULL */
690                         pq_sendint(&buf, -1, 4);
691                         continue;
692                 }
693
694                 /*
695                  * Convert the column value to binary
696                  */
697                 if (column_info->column_type != column_type)
698                 {
699                         bool            typIsVarlena;
700
701                         getTypeBinaryOutputInfo(column_type,
702                                                                         &column_info->typiofunc,
703                                                                         &column_info->typioparam,
704                                                                         &typIsVarlena);
705                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
706                                                   fcinfo->flinfo->fn_mcxt);
707                         column_info->column_type = column_type;
708                 }
709
710                 outputbytes = DatumGetByteaP(FunctionCall2(&column_info->proc,
711                                                                                                    values[i],
712                                                          ObjectIdGetDatum(column_info->typioparam)));
713
714                 /* We assume the result will not have been toasted */
715                 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
716                 pq_sendbytes(&buf, VARDATA(outputbytes),
717                                          VARSIZE(outputbytes) - VARHDRSZ);
718                 pfree(outputbytes);
719         }
720
721         pfree(values);
722         pfree(nulls);
723
724         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
725 }