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