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