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