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