]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/rowtypes.c
Update CVS HEAD for 2007 copyright. Back branches are typically not
[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.18 2007/01/05 22:19:42 momjian 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                         buf.len = 0;
172                         buf.data[0] = '\0';
173                         while (inquote || !(*ptr == ',' || *ptr == ')'))
174                         {
175                                 char            ch = *ptr++;
176
177                                 if (ch == '\0')
178                                         ereport(ERROR,
179                                                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
180                                                          errmsg("malformed record literal: \"%s\"",
181                                                                         string),
182                                                          errdetail("Unexpected end of input.")));
183                                 if (ch == '\\')
184                                 {
185                                         if (*ptr == '\0')
186                                                 ereport(ERROR,
187                                                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
188                                                                  errmsg("malformed record literal: \"%s\"",
189                                                                                 string),
190                                                                  errdetail("Unexpected end of input.")));
191                                         appendStringInfoChar(&buf, *ptr++);
192                                 }
193                                 else if (ch == '\"')
194                                 {
195                                         if (!inquote)
196                                                 inquote = true;
197                                         else if (*ptr == '\"')
198                                         {
199                                                 /* doubled quote within quote sequence */
200                                                 appendStringInfoChar(&buf, *ptr++);
201                                         }
202                                         else
203                                                 inquote = false;
204                                 }
205                                 else
206                                         appendStringInfoChar(&buf, ch);
207                         }
208
209                         column_data = buf.data;
210                         nulls[i] = ' ';
211                 }
212
213                 /*
214                  * Convert the column value
215                  */
216                 if (column_info->column_type != column_type)
217                 {
218                         getTypeInputInfo(column_type,
219                                                          &column_info->typiofunc,
220                                                          &column_info->typioparam);
221                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
222                                                   fcinfo->flinfo->fn_mcxt);
223                         column_info->column_type = column_type;
224                 }
225
226                 values[i] = InputFunctionCall(&column_info->proc,
227                                                                           column_data,
228                                                                           column_info->typioparam,
229                                                                           tupdesc->attrs[i]->atttypmod);
230
231                 /*
232                  * Prep for next column
233                  */
234                 needComma = true;
235         }
236
237         if (*ptr++ != ')')
238                 ereport(ERROR,
239                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
240                                  errmsg("malformed record literal: \"%s\"", string),
241                                  errdetail("Too many columns.")));
242         /* Allow trailing whitespace */
243         while (*ptr && isspace((unsigned char) *ptr))
244                 ptr++;
245         if (*ptr)
246                 ereport(ERROR,
247                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
248                                  errmsg("malformed record literal: \"%s\"", string),
249                                  errdetail("Junk after right parenthesis.")));
250
251         tuple = heap_formtuple(tupdesc, values, nulls);
252
253         /*
254          * We cannot return tuple->t_data because heap_formtuple allocates it as
255          * part of a larger chunk, and our caller may expect to be able to pfree
256          * our result.  So must copy the info into a new palloc chunk.
257          */
258         result = (HeapTupleHeader) palloc(tuple->t_len);
259         memcpy(result, tuple->t_data, tuple->t_len);
260
261         heap_freetuple(tuple);
262         pfree(buf.data);
263         pfree(values);
264         pfree(nulls);
265         ReleaseTupleDesc(tupdesc);
266
267         PG_RETURN_HEAPTUPLEHEADER(result);
268 }
269
270 /*
271  * record_out           - output routine for any composite type.
272  */
273 Datum
274 record_out(PG_FUNCTION_ARGS)
275 {
276         HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
277         Oid                     tupType;
278         int32           tupTypmod;
279         TupleDesc       tupdesc;
280         HeapTupleData tuple;
281         RecordIOData *my_extra;
282         bool            needComma = false;
283         int                     ncolumns;
284         int                     i;
285         Datum      *values;
286         char       *nulls;
287         StringInfoData buf;
288
289         /* Extract type info from the tuple itself */
290         tupType = HeapTupleHeaderGetTypeId(rec);
291         tupTypmod = HeapTupleHeaderGetTypMod(rec);
292         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
293         ncolumns = tupdesc->natts;
294
295         /* Build a temporary HeapTuple control structure */
296         tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
297         ItemPointerSetInvalid(&(tuple.t_self));
298         tuple.t_tableOid = InvalidOid;
299         tuple.t_data = rec;
300
301         /*
302          * We arrange to look up the needed I/O info just once per series of
303          * calls, assuming the record type doesn't change underneath us.
304          */
305         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
306         if (my_extra == NULL ||
307                 my_extra->ncolumns != ncolumns)
308         {
309                 fcinfo->flinfo->fn_extra =
310                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
311                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
312                                                            + ncolumns * sizeof(ColumnIOData));
313                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
314                 my_extra->record_type = InvalidOid;
315                 my_extra->record_typmod = 0;
316         }
317
318         if (my_extra->record_type != tupType ||
319                 my_extra->record_typmod != tupTypmod)
320         {
321                 MemSet(my_extra, 0,
322                            sizeof(RecordIOData) - sizeof(ColumnIOData)
323                            + ncolumns * sizeof(ColumnIOData));
324                 my_extra->record_type = tupType;
325                 my_extra->record_typmod = tupTypmod;
326                 my_extra->ncolumns = ncolumns;
327         }
328
329         values = (Datum *) palloc(ncolumns * sizeof(Datum));
330         nulls = (char *) palloc(ncolumns * sizeof(char));
331
332         /* Break down the tuple into fields */
333         heap_deformtuple(&tuple, tupdesc, values, nulls);
334
335         /* And build the result string */
336         initStringInfo(&buf);
337
338         appendStringInfoChar(&buf, '(');
339
340         for (i = 0; i < ncolumns; i++)
341         {
342                 ColumnIOData *column_info = &my_extra->columns[i];
343                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
344                 char       *value;
345                 char       *tmp;
346                 bool            nq;
347
348                 /* Ignore dropped columns in datatype */
349                 if (tupdesc->attrs[i]->attisdropped)
350                         continue;
351
352                 if (needComma)
353                         appendStringInfoChar(&buf, ',');
354                 needComma = true;
355
356                 if (nulls[i] == 'n')
357                 {
358                         /* emit nothing... */
359                         continue;
360                 }
361
362                 /*
363                  * Convert the column value to text
364                  */
365                 if (column_info->column_type != column_type)
366                 {
367                         bool            typIsVarlena;
368
369                         getTypeOutputInfo(column_type,
370                                                           &column_info->typiofunc,
371                                                           &typIsVarlena);
372                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
373                                                   fcinfo->flinfo->fn_mcxt);
374                         column_info->column_type = column_type;
375                 }
376
377                 value = OutputFunctionCall(&column_info->proc, values[i]);
378
379                 /* Detect whether we need double quotes for this value */
380                 nq = (value[0] == '\0');        /* force quotes for empty string */
381                 for (tmp = value; *tmp; tmp++)
382                 {
383                         char            ch = *tmp;
384
385                         if (ch == '"' || ch == '\\' ||
386                                 ch == '(' || ch == ')' || ch == ',' ||
387                                 isspace((unsigned char) ch))
388                         {
389                                 nq = true;
390                                 break;
391                         }
392                 }
393
394                 /* And emit the string */
395                 if (nq)
396                         appendStringInfoChar(&buf, '"');
397                 for (tmp = value; *tmp; tmp++)
398                 {
399                         char            ch = *tmp;
400
401                         if (ch == '"' || ch == '\\')
402                                 appendStringInfoChar(&buf, ch);
403                         appendStringInfoChar(&buf, ch);
404                 }
405                 if (nq)
406                         appendStringInfoChar(&buf, '"');
407         }
408
409         appendStringInfoChar(&buf, ')');
410
411         pfree(values);
412         pfree(nulls);
413         ReleaseTupleDesc(tupdesc);
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         ReleaseTupleDesc(tupdesc);
609
610         PG_RETURN_HEAPTUPLEHEADER(result);
611 }
612
613 /*
614  * record_send          - binary output routine for any composite type.
615  */
616 Datum
617 record_send(PG_FUNCTION_ARGS)
618 {
619         HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
620         Oid                     tupType;
621         int32           tupTypmod;
622         TupleDesc       tupdesc;
623         HeapTupleData tuple;
624         RecordIOData *my_extra;
625         int                     ncolumns;
626         int                     validcols;
627         int                     i;
628         Datum      *values;
629         char       *nulls;
630         StringInfoData buf;
631
632         /* Extract type info from the tuple itself */
633         tupType = HeapTupleHeaderGetTypeId(rec);
634         tupTypmod = HeapTupleHeaderGetTypMod(rec);
635         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
636         ncolumns = tupdesc->natts;
637
638         /* Build a temporary HeapTuple control structure */
639         tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
640         ItemPointerSetInvalid(&(tuple.t_self));
641         tuple.t_tableOid = InvalidOid;
642         tuple.t_data = rec;
643
644         /*
645          * We arrange to look up the needed I/O info just once per series of
646          * calls, assuming the record type doesn't change underneath us.
647          */
648         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
649         if (my_extra == NULL ||
650                 my_extra->ncolumns != ncolumns)
651         {
652                 fcinfo->flinfo->fn_extra =
653                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
654                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
655                                                            + ncolumns * sizeof(ColumnIOData));
656                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
657                 my_extra->record_type = InvalidOid;
658                 my_extra->record_typmod = 0;
659         }
660
661         if (my_extra->record_type != tupType ||
662                 my_extra->record_typmod != tupTypmod)
663         {
664                 MemSet(my_extra, 0,
665                            sizeof(RecordIOData) - sizeof(ColumnIOData)
666                            + ncolumns * sizeof(ColumnIOData));
667                 my_extra->record_type = tupType;
668                 my_extra->record_typmod = tupTypmod;
669                 my_extra->ncolumns = ncolumns;
670         }
671
672         values = (Datum *) palloc(ncolumns * sizeof(Datum));
673         nulls = (char *) palloc(ncolumns * sizeof(char));
674
675         /* Break down the tuple into fields */
676         heap_deformtuple(&tuple, tupdesc, values, nulls);
677
678         /* And build the result string */
679         pq_begintypsend(&buf);
680
681         /* Need to scan to count nondeleted columns */
682         validcols = 0;
683         for (i = 0; i < ncolumns; i++)
684         {
685                 if (!tupdesc->attrs[i]->attisdropped)
686                         validcols++;
687         }
688         pq_sendint(&buf, validcols, 4);
689
690         for (i = 0; i < ncolumns; i++)
691         {
692                 ColumnIOData *column_info = &my_extra->columns[i];
693                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
694                 bytea      *outputbytes;
695
696                 /* Ignore dropped columns in datatype */
697                 if (tupdesc->attrs[i]->attisdropped)
698                         continue;
699
700                 pq_sendint(&buf, column_type, sizeof(Oid));
701
702                 if (nulls[i] == 'n')
703                 {
704                         /* emit -1 data length to signify a NULL */
705                         pq_sendint(&buf, -1, 4);
706                         continue;
707                 }
708
709                 /*
710                  * Convert the column value to binary
711                  */
712                 if (column_info->column_type != column_type)
713                 {
714                         bool            typIsVarlena;
715
716                         getTypeBinaryOutputInfo(column_type,
717                                                                         &column_info->typiofunc,
718                                                                         &typIsVarlena);
719                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
720                                                   fcinfo->flinfo->fn_mcxt);
721                         column_info->column_type = column_type;
722                 }
723
724                 outputbytes = SendFunctionCall(&column_info->proc, values[i]);
725
726                 /* We assume the result will not have been toasted */
727                 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
728                 pq_sendbytes(&buf, VARDATA(outputbytes),
729                                          VARSIZE(outputbytes) - VARHDRSZ);
730                 pfree(outputbytes);
731         }
732
733         pfree(values);
734         pfree(nulls);
735         ReleaseTupleDesc(tupdesc);
736
737         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
738 }