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