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