]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/rowtypes.c
binary migration: pg_migrator
[postgresql] / src / backend / utils / adt / rowtypes.c
1 /*-------------------------------------------------------------------------
2  *
3  * rowtypes.c
4  *        I/O and comparison functions for generic composite types.
5  *
6  * Portions Copyright (c) 1996-2009, 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.26 2009/12/19 00:47:57 momjian 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  * structure to cache metadata needed for record comparison
47  */
48 typedef struct ColumnCompareData
49 {
50         TypeCacheEntry *typentry;       /* has everything we need, actually */
51 } ColumnCompareData;
52
53 typedef struct RecordCompareData
54 {
55         int                     ncolumns;               /* allocated length of columns[] */
56         Oid                     record1_type;
57         int32           record1_typmod;
58         Oid                     record2_type;
59         int32           record2_typmod;
60         ColumnCompareData columns[1];           /* VARIABLE LENGTH ARRAY */
61 } RecordCompareData;
62
63
64 /*
65  * record_in            - input routine for any composite type.
66  */
67 Datum
68 record_in(PG_FUNCTION_ARGS)
69 {
70         char       *string = PG_GETARG_CSTRING(0);
71         Oid                     tupType = PG_GETARG_OID(1);
72
73 #ifdef NOT_USED
74         int32           typmod = PG_GETARG_INT32(2);
75 #endif
76         HeapTupleHeader result;
77         int32           tupTypmod;
78         TupleDesc       tupdesc;
79         HeapTuple       tuple;
80         RecordIOData *my_extra;
81         bool            needComma = false;
82         int                     ncolumns;
83         int                     i;
84         char       *ptr;
85         Datum      *values;
86         bool       *nulls;
87         StringInfoData buf;
88
89         /*
90          * Use the passed type unless it's RECORD; we can't support input of
91          * anonymous types, mainly because there's no good way to figure out which
92          * anonymous type is wanted.  Note that for RECORD, what we'll probably
93          * actually get is RECORD's typelem, ie, zero.
94          */
95         if (tupType == InvalidOid || tupType == RECORDOID)
96                 ereport(ERROR,
97                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
98                    errmsg("input of anonymous composite types is not implemented")));
99         tupTypmod = -1;                         /* for all non-anonymous types */
100         /*
101          *      This comes from the composite type's pg_type.oid and
102          *      stores system oids in user tables, specifically DatumTupleFields.
103          *      This oid must be preserved by binary upgrades.
104          */
105         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
106         ncolumns = tupdesc->natts;
107
108         /*
109          * We arrange to look up the needed I/O info just once per series of
110          * calls, assuming the record type doesn't change underneath us.
111          */
112         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
113         if (my_extra == NULL ||
114                 my_extra->ncolumns != ncolumns)
115         {
116                 fcinfo->flinfo->fn_extra =
117                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
118                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
119                                                            + ncolumns * sizeof(ColumnIOData));
120                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
121                 my_extra->record_type = InvalidOid;
122                 my_extra->record_typmod = 0;
123         }
124
125         if (my_extra->record_type != tupType ||
126                 my_extra->record_typmod != tupTypmod)
127         {
128                 MemSet(my_extra, 0,
129                            sizeof(RecordIOData) - sizeof(ColumnIOData)
130                            + ncolumns * sizeof(ColumnIOData));
131                 my_extra->record_type = tupType;
132                 my_extra->record_typmod = tupTypmod;
133                 my_extra->ncolumns = ncolumns;
134         }
135
136         values = (Datum *) palloc(ncolumns * sizeof(Datum));
137         nulls = (bool *) palloc(ncolumns * sizeof(bool));
138
139         /*
140          * Scan the string.  We use "buf" to accumulate the de-quoted data for
141          * each column, which is then fed to the appropriate input converter.
142          */
143         ptr = string;
144         /* Allow leading whitespace */
145         while (*ptr && isspace((unsigned char) *ptr))
146                 ptr++;
147         if (*ptr++ != '(')
148                 ereport(ERROR,
149                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
150                                  errmsg("malformed record literal: \"%s\"", string),
151                                  errdetail("Missing left parenthesis.")));
152
153         initStringInfo(&buf);
154
155         for (i = 0; i < ncolumns; i++)
156         {
157                 ColumnIOData *column_info = &my_extra->columns[i];
158                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
159                 char       *column_data;
160
161                 /* Ignore dropped columns in datatype, but fill with nulls */
162                 if (tupdesc->attrs[i]->attisdropped)
163                 {
164                         values[i] = (Datum) 0;
165                         nulls[i] = true;
166                         continue;
167                 }
168
169                 if (needComma)
170                 {
171                         /* Skip comma that separates prior field from this one */
172                         if (*ptr == ',')
173                                 ptr++;
174                         else
175                                 /* *ptr must be ')' */
176                                 ereport(ERROR,
177                                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
178                                                  errmsg("malformed record literal: \"%s\"", string),
179                                                  errdetail("Too few columns.")));
180                 }
181
182                 /* Check for null: completely empty input means null */
183                 if (*ptr == ',' || *ptr == ')')
184                 {
185                         column_data = NULL;
186                         nulls[i] = true;
187                 }
188                 else
189                 {
190                         /* Extract string for this column */
191                         bool            inquote = false;
192
193                         resetStringInfo(&buf);
194                         while (inquote || !(*ptr == ',' || *ptr == ')'))
195                         {
196                                 char            ch = *ptr++;
197
198                                 if (ch == '\0')
199                                         ereport(ERROR,
200                                                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
201                                                          errmsg("malformed record literal: \"%s\"",
202                                                                         string),
203                                                          errdetail("Unexpected end of input.")));
204                                 if (ch == '\\')
205                                 {
206                                         if (*ptr == '\0')
207                                                 ereport(ERROR,
208                                                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
209                                                                  errmsg("malformed record literal: \"%s\"",
210                                                                                 string),
211                                                                  errdetail("Unexpected end of input.")));
212                                         appendStringInfoChar(&buf, *ptr++);
213                                 }
214                                 else if (ch == '\"')
215                                 {
216                                         if (!inquote)
217                                                 inquote = true;
218                                         else if (*ptr == '\"')
219                                         {
220                                                 /* doubled quote within quote sequence */
221                                                 appendStringInfoChar(&buf, *ptr++);
222                                         }
223                                         else
224                                                 inquote = false;
225                                 }
226                                 else
227                                         appendStringInfoChar(&buf, ch);
228                         }
229
230                         column_data = buf.data;
231                         nulls[i] = false;
232                 }
233
234                 /*
235                  * Convert the column value
236                  */
237                 if (column_info->column_type != column_type)
238                 {
239                         getTypeInputInfo(column_type,
240                                                          &column_info->typiofunc,
241                                                          &column_info->typioparam);
242                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
243                                                   fcinfo->flinfo->fn_mcxt);
244                         column_info->column_type = column_type;
245                 }
246
247                 values[i] = InputFunctionCall(&column_info->proc,
248                                                                           column_data,
249                                                                           column_info->typioparam,
250                                                                           tupdesc->attrs[i]->atttypmod);
251
252                 /*
253                  * Prep for next column
254                  */
255                 needComma = true;
256         }
257
258         if (*ptr++ != ')')
259                 ereport(ERROR,
260                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
261                                  errmsg("malformed record literal: \"%s\"", string),
262                                  errdetail("Too many columns.")));
263         /* Allow trailing whitespace */
264         while (*ptr && isspace((unsigned char) *ptr))
265                 ptr++;
266         if (*ptr)
267                 ereport(ERROR,
268                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
269                                  errmsg("malformed record literal: \"%s\"", string),
270                                  errdetail("Junk after right parenthesis.")));
271
272         tuple = heap_form_tuple(tupdesc, values, nulls);
273
274         /*
275          * We cannot return tuple->t_data because heap_form_tuple allocates it as
276          * part of a larger chunk, and our caller may expect to be able to pfree
277          * our result.  So must copy the info into a new palloc chunk.
278          */
279         result = (HeapTupleHeader) palloc(tuple->t_len);
280         memcpy(result, tuple->t_data, tuple->t_len);
281
282         heap_freetuple(tuple);
283         pfree(buf.data);
284         pfree(values);
285         pfree(nulls);
286         ReleaseTupleDesc(tupdesc);
287
288         PG_RETURN_HEAPTUPLEHEADER(result);
289 }
290
291 /*
292  * record_out           - output routine for any composite type.
293  */
294 Datum
295 record_out(PG_FUNCTION_ARGS)
296 {
297         HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
298         Oid                     tupType;
299         int32           tupTypmod;
300         TupleDesc       tupdesc;
301         HeapTupleData tuple;
302         RecordIOData *my_extra;
303         bool            needComma = false;
304         int                     ncolumns;
305         int                     i;
306         Datum      *values;
307         bool       *nulls;
308         StringInfoData buf;
309
310         /* Extract type info from the tuple itself */
311         tupType = HeapTupleHeaderGetTypeId(rec);
312         tupTypmod = HeapTupleHeaderGetTypMod(rec);
313         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
314         ncolumns = tupdesc->natts;
315
316         /* Build a temporary HeapTuple control structure */
317         tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
318         ItemPointerSetInvalid(&(tuple.t_self));
319         tuple.t_tableOid = InvalidOid;
320         tuple.t_data = rec;
321
322         /*
323          * We arrange to look up the needed I/O info just once per series of
324          * calls, assuming the record type doesn't change underneath us.
325          */
326         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
327         if (my_extra == NULL ||
328                 my_extra->ncolumns != ncolumns)
329         {
330                 fcinfo->flinfo->fn_extra =
331                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
332                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
333                                                            + ncolumns * sizeof(ColumnIOData));
334                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
335                 my_extra->record_type = InvalidOid;
336                 my_extra->record_typmod = 0;
337         }
338
339         if (my_extra->record_type != tupType ||
340                 my_extra->record_typmod != tupTypmod)
341         {
342                 MemSet(my_extra, 0,
343                            sizeof(RecordIOData) - sizeof(ColumnIOData)
344                            + ncolumns * sizeof(ColumnIOData));
345                 my_extra->record_type = tupType;
346                 my_extra->record_typmod = tupTypmod;
347                 my_extra->ncolumns = ncolumns;
348         }
349
350         values = (Datum *) palloc(ncolumns * sizeof(Datum));
351         nulls = (bool *) palloc(ncolumns * sizeof(bool));
352
353         /* Break down the tuple into fields */
354         heap_deform_tuple(&tuple, tupdesc, values, nulls);
355
356         /* And build the result string */
357         initStringInfo(&buf);
358
359         appendStringInfoChar(&buf, '(');
360
361         for (i = 0; i < ncolumns; i++)
362         {
363                 ColumnIOData *column_info = &my_extra->columns[i];
364                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
365                 char       *value;
366                 char       *tmp;
367                 bool            nq;
368
369                 /* Ignore dropped columns in datatype */
370                 if (tupdesc->attrs[i]->attisdropped)
371                         continue;
372
373                 if (needComma)
374                         appendStringInfoChar(&buf, ',');
375                 needComma = true;
376
377                 if (nulls[i])
378                 {
379                         /* emit nothing... */
380                         continue;
381                 }
382
383                 /*
384                  * Convert the column value to text
385                  */
386                 if (column_info->column_type != column_type)
387                 {
388                         bool            typIsVarlena;
389
390                         getTypeOutputInfo(column_type,
391                                                           &column_info->typiofunc,
392                                                           &typIsVarlena);
393                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
394                                                   fcinfo->flinfo->fn_mcxt);
395                         column_info->column_type = column_type;
396                 }
397
398                 value = OutputFunctionCall(&column_info->proc, values[i]);
399
400                 /* Detect whether we need double quotes for this value */
401                 nq = (value[0] == '\0');        /* force quotes for empty string */
402                 for (tmp = value; *tmp; tmp++)
403                 {
404                         char            ch = *tmp;
405
406                         if (ch == '"' || ch == '\\' ||
407                                 ch == '(' || ch == ')' || ch == ',' ||
408                                 isspace((unsigned char) ch))
409                         {
410                                 nq = true;
411                                 break;
412                         }
413                 }
414
415                 /* And emit the string */
416                 if (nq)
417                         appendStringInfoChar(&buf, '"');
418                 for (tmp = value; *tmp; tmp++)
419                 {
420                         char            ch = *tmp;
421
422                         if (ch == '"' || ch == '\\')
423                                 appendStringInfoChar(&buf, ch);
424                         appendStringInfoChar(&buf, ch);
425                 }
426                 if (nq)
427                         appendStringInfoChar(&buf, '"');
428         }
429
430         appendStringInfoChar(&buf, ')');
431
432         pfree(values);
433         pfree(nulls);
434         ReleaseTupleDesc(tupdesc);
435
436         PG_RETURN_CSTRING(buf.data);
437 }
438
439 /*
440  * record_recv          - binary input routine for any composite type.
441  */
442 Datum
443 record_recv(PG_FUNCTION_ARGS)
444 {
445         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
446         Oid                     tupType = PG_GETARG_OID(1);
447
448 #ifdef NOT_USED
449         int32           typmod = PG_GETARG_INT32(2);
450 #endif
451         HeapTupleHeader result;
452         int32           tupTypmod;
453         TupleDesc       tupdesc;
454         HeapTuple       tuple;
455         RecordIOData *my_extra;
456         int                     ncolumns;
457         int                     usercols;
458         int                     validcols;
459         int                     i;
460         Datum      *values;
461         bool       *nulls;
462
463         /*
464          * Use the passed type unless it's RECORD; we can't support input of
465          * anonymous types, mainly because there's no good way to figure out which
466          * anonymous type is wanted.  Note that for RECORD, what we'll probably
467          * actually get is RECORD's typelem, ie, zero.
468          */
469         if (tupType == InvalidOid || tupType == RECORDOID)
470                 ereport(ERROR,
471                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
472                    errmsg("input of anonymous composite types is not implemented")));
473         tupTypmod = -1;                         /* for all non-anonymous types */
474         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
475         ncolumns = tupdesc->natts;
476
477         /*
478          * We arrange to look up the needed I/O info just once per series of
479          * calls, assuming the record type doesn't change underneath us.
480          */
481         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
482         if (my_extra == NULL ||
483                 my_extra->ncolumns != ncolumns)
484         {
485                 fcinfo->flinfo->fn_extra =
486                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
487                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
488                                                            + ncolumns * sizeof(ColumnIOData));
489                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
490                 my_extra->record_type = InvalidOid;
491                 my_extra->record_typmod = 0;
492         }
493
494         if (my_extra->record_type != tupType ||
495                 my_extra->record_typmod != tupTypmod)
496         {
497                 MemSet(my_extra, 0,
498                            sizeof(RecordIOData) - sizeof(ColumnIOData)
499                            + ncolumns * sizeof(ColumnIOData));
500                 my_extra->record_type = tupType;
501                 my_extra->record_typmod = tupTypmod;
502                 my_extra->ncolumns = ncolumns;
503         }
504
505         values = (Datum *) palloc(ncolumns * sizeof(Datum));
506         nulls = (bool *) palloc(ncolumns * sizeof(bool));
507
508         /* Fetch number of columns user thinks it has */
509         usercols = pq_getmsgint(buf, 4);
510
511         /* Need to scan to count nondeleted columns */
512         validcols = 0;
513         for (i = 0; i < ncolumns; i++)
514         {
515                 if (!tupdesc->attrs[i]->attisdropped)
516                         validcols++;
517         }
518         if (usercols != validcols)
519                 ereport(ERROR,
520                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
521                                  errmsg("wrong number of columns: %d, expected %d",
522                                                 usercols, validcols)));
523
524         /* Process each column */
525         for (i = 0; i < ncolumns; i++)
526         {
527                 ColumnIOData *column_info = &my_extra->columns[i];
528                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
529                 Oid                     coltypoid;
530                 int                     itemlen;
531                 StringInfoData item_buf;
532                 StringInfo      bufptr;
533                 char            csave;
534
535                 /* Ignore dropped columns in datatype, but fill with nulls */
536                 if (tupdesc->attrs[i]->attisdropped)
537                 {
538                         values[i] = (Datum) 0;
539                         nulls[i] = true;
540                         continue;
541                 }
542
543                 /* Verify column datatype */
544                 coltypoid = pq_getmsgint(buf, sizeof(Oid));
545                 if (coltypoid != column_type)
546                         ereport(ERROR,
547                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
548                                          errmsg("wrong data type: %u, expected %u",
549                                                         coltypoid, column_type)));
550
551                 /* Get and check the item length */
552                 itemlen = pq_getmsgint(buf, 4);
553                 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
554                         ereport(ERROR,
555                                         (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
556                                          errmsg("insufficient data left in message")));
557
558                 if (itemlen == -1)
559                 {
560                         /* -1 length means NULL */
561                         bufptr = NULL;
562                         nulls[i] = true;
563                         csave = 0;                      /* keep compiler quiet */
564                 }
565                 else
566                 {
567                         /*
568                          * Rather than copying data around, we just set up a phony
569                          * StringInfo pointing to the correct portion of the input buffer.
570                          * We assume we can scribble on the input buffer so as to maintain
571                          * the convention that StringInfos have a trailing null.
572                          */
573                         item_buf.data = &buf->data[buf->cursor];
574                         item_buf.maxlen = itemlen + 1;
575                         item_buf.len = itemlen;
576                         item_buf.cursor = 0;
577
578                         buf->cursor += itemlen;
579
580                         csave = buf->data[buf->cursor];
581                         buf->data[buf->cursor] = '\0';
582
583                         bufptr = &item_buf;
584                         nulls[i] = false;
585                 }
586
587                 /* Now call the column's receiveproc */
588                 if (column_info->column_type != column_type)
589                 {
590                         getTypeBinaryInputInfo(column_type,
591                                                                    &column_info->typiofunc,
592                                                                    &column_info->typioparam);
593                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
594                                                   fcinfo->flinfo->fn_mcxt);
595                         column_info->column_type = column_type;
596                 }
597
598                 values[i] = ReceiveFunctionCall(&column_info->proc,
599                                                                                 bufptr,
600                                                                                 column_info->typioparam,
601                                                                                 tupdesc->attrs[i]->atttypmod);
602
603                 if (bufptr)
604                 {
605                         /* Trouble if it didn't eat the whole buffer */
606                         if (item_buf.cursor != itemlen)
607                                 ereport(ERROR,
608                                                 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
609                                                  errmsg("improper binary format in record column %d",
610                                                                 i + 1)));
611
612                         buf->data[buf->cursor] = csave;
613                 }
614         }
615
616         tuple = heap_form_tuple(tupdesc, values, nulls);
617
618         /*
619          * We cannot return tuple->t_data because heap_form_tuple allocates it as
620          * part of a larger chunk, and our caller may expect to be able to pfree
621          * our result.  So must copy the info into a new palloc chunk.
622          */
623         result = (HeapTupleHeader) palloc(tuple->t_len);
624         memcpy(result, tuple->t_data, tuple->t_len);
625
626         heap_freetuple(tuple);
627         pfree(values);
628         pfree(nulls);
629         ReleaseTupleDesc(tupdesc);
630
631         PG_RETURN_HEAPTUPLEHEADER(result);
632 }
633
634 /*
635  * record_send          - binary output routine for any composite type.
636  */
637 Datum
638 record_send(PG_FUNCTION_ARGS)
639 {
640         HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
641         Oid                     tupType;
642         int32           tupTypmod;
643         TupleDesc       tupdesc;
644         HeapTupleData tuple;
645         RecordIOData *my_extra;
646         int                     ncolumns;
647         int                     validcols;
648         int                     i;
649         Datum      *values;
650         bool       *nulls;
651         StringInfoData buf;
652
653         /* Extract type info from the tuple itself */
654         tupType = HeapTupleHeaderGetTypeId(rec);
655         tupTypmod = HeapTupleHeaderGetTypMod(rec);
656         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
657         ncolumns = tupdesc->natts;
658
659         /* Build a temporary HeapTuple control structure */
660         tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
661         ItemPointerSetInvalid(&(tuple.t_self));
662         tuple.t_tableOid = InvalidOid;
663         tuple.t_data = rec;
664
665         /*
666          * We arrange to look up the needed I/O info just once per series of
667          * calls, assuming the record type doesn't change underneath us.
668          */
669         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
670         if (my_extra == NULL ||
671                 my_extra->ncolumns != ncolumns)
672         {
673                 fcinfo->flinfo->fn_extra =
674                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
675                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
676                                                            + ncolumns * sizeof(ColumnIOData));
677                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
678                 my_extra->record_type = InvalidOid;
679                 my_extra->record_typmod = 0;
680         }
681
682         if (my_extra->record_type != tupType ||
683                 my_extra->record_typmod != tupTypmod)
684         {
685                 MemSet(my_extra, 0,
686                            sizeof(RecordIOData) - sizeof(ColumnIOData)
687                            + ncolumns * sizeof(ColumnIOData));
688                 my_extra->record_type = tupType;
689                 my_extra->record_typmod = tupTypmod;
690                 my_extra->ncolumns = ncolumns;
691         }
692
693         values = (Datum *) palloc(ncolumns * sizeof(Datum));
694         nulls = (bool *) palloc(ncolumns * sizeof(bool));
695
696         /* Break down the tuple into fields */
697         heap_deform_tuple(&tuple, tupdesc, values, nulls);
698
699         /* And build the result string */
700         pq_begintypsend(&buf);
701
702         /* Need to scan to count nondeleted columns */
703         validcols = 0;
704         for (i = 0; i < ncolumns; i++)
705         {
706                 if (!tupdesc->attrs[i]->attisdropped)
707                         validcols++;
708         }
709         pq_sendint(&buf, validcols, 4);
710
711         for (i = 0; i < ncolumns; i++)
712         {
713                 ColumnIOData *column_info = &my_extra->columns[i];
714                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
715                 bytea      *outputbytes;
716
717                 /* Ignore dropped columns in datatype */
718                 if (tupdesc->attrs[i]->attisdropped)
719                         continue;
720
721                 pq_sendint(&buf, column_type, sizeof(Oid));
722
723                 if (nulls[i])
724                 {
725                         /* emit -1 data length to signify a NULL */
726                         pq_sendint(&buf, -1, 4);
727                         continue;
728                 }
729
730                 /*
731                  * Convert the column value to binary
732                  */
733                 if (column_info->column_type != column_type)
734                 {
735                         bool            typIsVarlena;
736
737                         getTypeBinaryOutputInfo(column_type,
738                                                                         &column_info->typiofunc,
739                                                                         &typIsVarlena);
740                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
741                                                   fcinfo->flinfo->fn_mcxt);
742                         column_info->column_type = column_type;
743                 }
744
745                 outputbytes = SendFunctionCall(&column_info->proc, values[i]);
746
747                 /* We assume the result will not have been toasted */
748                 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
749                 pq_sendbytes(&buf, VARDATA(outputbytes),
750                                          VARSIZE(outputbytes) - VARHDRSZ);
751                 pfree(outputbytes);
752         }
753
754         pfree(values);
755         pfree(nulls);
756         ReleaseTupleDesc(tupdesc);
757
758         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
759 }
760
761
762 /*
763  * record_cmp()
764  * Internal comparison function for records.
765  *
766  * Returns -1, 0 or 1
767  *
768  * Do not assume that the two inputs are exactly the same record type;
769  * for instance we might be comparing an anonymous ROW() construct against a
770  * named composite type.  We will compare as long as they have the same number
771  * of non-dropped columns of the same types.
772  */
773 static int
774 record_cmp(FunctionCallInfo fcinfo)
775 {
776         HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
777         HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
778         int                     result = 0;
779         Oid                     tupType1;
780         Oid                     tupType2;
781         int32           tupTypmod1;
782         int32           tupTypmod2;
783         TupleDesc       tupdesc1;
784         TupleDesc       tupdesc2;
785         HeapTupleData tuple1;
786         HeapTupleData tuple2;
787         int                     ncolumns1;
788         int                     ncolumns2;
789         RecordCompareData *my_extra;
790         int                     ncols;
791         Datum      *values1;
792         Datum      *values2;
793         bool       *nulls1;
794         bool       *nulls2;
795         int                     i1;
796         int                     i2;
797         int                     j;
798
799         /* Extract type info from the tuples */
800         tupType1 = HeapTupleHeaderGetTypeId(record1);
801         tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
802         tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
803         ncolumns1 = tupdesc1->natts;
804         tupType2 = HeapTupleHeaderGetTypeId(record2);
805         tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
806         tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
807         ncolumns2 = tupdesc2->natts;
808
809         /* Build temporary HeapTuple control structures */
810         tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
811         ItemPointerSetInvalid(&(tuple1.t_self));
812         tuple1.t_tableOid = InvalidOid;
813         tuple1.t_data = record1;
814         tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
815         ItemPointerSetInvalid(&(tuple2.t_self));
816         tuple2.t_tableOid = InvalidOid;
817         tuple2.t_data = record2;
818
819         /*
820          * We arrange to look up the needed comparison info just once per series
821          * of calls, assuming the record types don't change underneath us.
822          */
823         ncols = Max(ncolumns1, ncolumns2);
824         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
825         if (my_extra == NULL ||
826                 my_extra->ncolumns < ncols)
827         {
828                 fcinfo->flinfo->fn_extra =
829                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
830                                                 sizeof(RecordCompareData) - sizeof(ColumnCompareData)
831                                                            + ncols * sizeof(ColumnCompareData));
832                 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
833                 my_extra->ncolumns = ncols;
834                 my_extra->record1_type = InvalidOid;
835                 my_extra->record1_typmod = 0;
836                 my_extra->record2_type = InvalidOid;
837                 my_extra->record2_typmod = 0;
838         }
839
840         if (my_extra->record1_type != tupType1 ||
841                 my_extra->record1_typmod != tupTypmod1 ||
842                 my_extra->record2_type != tupType2 ||
843                 my_extra->record2_typmod != tupTypmod2)
844         {
845                 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
846                 my_extra->record1_type = tupType1;
847                 my_extra->record1_typmod = tupTypmod1;
848                 my_extra->record2_type = tupType2;
849                 my_extra->record2_typmod = tupTypmod2;
850         }
851
852         /* Break down the tuples into fields */
853         values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
854         nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
855         heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
856         values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
857         nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
858         heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
859
860         /*
861          * Scan corresponding columns, allowing for dropped columns in different
862          * places in the two rows.      i1 and i2 are physical column indexes, j is
863          * the logical column index.
864          */
865         i1 = i2 = j = 0;
866         while (i1 < ncolumns1 || i2 < ncolumns2)
867         {
868                 TypeCacheEntry *typentry;
869                 FunctionCallInfoData locfcinfo;
870                 int32           cmpresult;
871
872                 /*
873                  * Skip dropped columns
874                  */
875                 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
876                 {
877                         i1++;
878                         continue;
879                 }
880                 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
881                 {
882                         i2++;
883                         continue;
884                 }
885                 if (i1 >= ncolumns1 || i2 >= ncolumns2)
886                         break;                          /* we'll deal with mismatch below loop */
887
888                 /*
889                  * Have two matching columns, they must be same type
890                  */
891                 if (tupdesc1->attrs[i1]->atttypid !=
892                         tupdesc2->attrs[i2]->atttypid)
893                         ereport(ERROR,
894                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
895                                          errmsg("cannot compare dissimilar column types %s and %s at record column %d",
896                                                         format_type_be(tupdesc1->attrs[i1]->atttypid),
897                                                         format_type_be(tupdesc2->attrs[i2]->atttypid),
898                                                         j + 1)));
899
900                 /*
901                  * Lookup the comparison function if not done already
902                  */
903                 typentry = my_extra->columns[j].typentry;
904                 if (typentry == NULL ||
905                         typentry->type_id != tupdesc1->attrs[i1]->atttypid)
906                 {
907                         typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
908                                                                                  TYPECACHE_CMP_PROC_FINFO);
909                         if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
910                                 ereport(ERROR,
911                                                 (errcode(ERRCODE_UNDEFINED_FUNCTION),
912                                 errmsg("could not identify a comparison function for type %s",
913                                            format_type_be(typentry->type_id))));
914                         my_extra->columns[j].typentry = typentry;
915                 }
916
917                 /*
918                  * We consider two NULLs equal; NULL > not-NULL.
919                  */
920                 if (!nulls1[i1] || !nulls2[i2])
921                 {
922                         if (nulls1[i1])
923                         {
924                                 /* arg1 is greater than arg2 */
925                                 result = 1;
926                                 break;
927                         }
928                         if (nulls2[i2])
929                         {
930                                 /* arg1 is less than arg2 */
931                                 result = -1;
932                                 break;
933                         }
934
935                         /* Compare the pair of elements */
936                         InitFunctionCallInfoData(locfcinfo, &typentry->cmp_proc_finfo, 2,
937                                                                          NULL, NULL);
938                         locfcinfo.arg[0] = values1[i1];
939                         locfcinfo.arg[1] = values2[i2];
940                         locfcinfo.argnull[0] = false;
941                         locfcinfo.argnull[1] = false;
942                         locfcinfo.isnull = false;
943                         cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
944
945                         if (cmpresult < 0)
946                         {
947                                 /* arg1 is less than arg2 */
948                                 result = -1;
949                                 break;
950                         }
951                         else if (cmpresult > 0)
952                         {
953                                 /* arg1 is greater than arg2 */
954                                 result = 1;
955                                 break;
956                         }
957                 }
958
959                 /* equal, so continue to next column */
960                 i1++, i2++, j++;
961         }
962
963         /*
964          * If we didn't break out of the loop early, check for column count
965          * mismatch.  (We do not report such mismatch if we found unequal column
966          * values; is that a feature or a bug?)
967          */
968         if (result == 0)
969         {
970                 if (i1 != ncolumns1 || i2 != ncolumns2)
971                         ereport(ERROR,
972                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
973                                          errmsg("cannot compare record types with different numbers of columns")));
974         }
975
976         pfree(values1);
977         pfree(nulls1);
978         pfree(values2);
979         pfree(nulls2);
980         ReleaseTupleDesc(tupdesc1);
981         ReleaseTupleDesc(tupdesc2);
982
983         /* Avoid leaking memory when handed toasted input. */
984         PG_FREE_IF_COPY(record1, 0);
985         PG_FREE_IF_COPY(record2, 1);
986
987         return result;
988 }
989
990 /*
991  * record_eq :
992  *                compares two records for equality
993  * result :
994  *                returns true if the records are equal, false otherwise.
995  *
996  * Note: we do not use record_cmp here, since equality may be meaningful in
997  * datatypes that don't have a total ordering (and hence no btree support).
998  */
999 Datum
1000 record_eq(PG_FUNCTION_ARGS)
1001 {
1002         HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1003         HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1004         bool            result = true;
1005         Oid                     tupType1;
1006         Oid                     tupType2;
1007         int32           tupTypmod1;
1008         int32           tupTypmod2;
1009         TupleDesc       tupdesc1;
1010         TupleDesc       tupdesc2;
1011         HeapTupleData tuple1;
1012         HeapTupleData tuple2;
1013         int                     ncolumns1;
1014         int                     ncolumns2;
1015         RecordCompareData *my_extra;
1016         int                     ncols;
1017         Datum      *values1;
1018         Datum      *values2;
1019         bool       *nulls1;
1020         bool       *nulls2;
1021         int                     i1;
1022         int                     i2;
1023         int                     j;
1024
1025         /* Extract type info from the tuples */
1026         tupType1 = HeapTupleHeaderGetTypeId(record1);
1027         tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1028         tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1029         ncolumns1 = tupdesc1->natts;
1030         tupType2 = HeapTupleHeaderGetTypeId(record2);
1031         tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1032         tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1033         ncolumns2 = tupdesc2->natts;
1034
1035         /* Build temporary HeapTuple control structures */
1036         tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1037         ItemPointerSetInvalid(&(tuple1.t_self));
1038         tuple1.t_tableOid = InvalidOid;
1039         tuple1.t_data = record1;
1040         tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1041         ItemPointerSetInvalid(&(tuple2.t_self));
1042         tuple2.t_tableOid = InvalidOid;
1043         tuple2.t_data = record2;
1044
1045         /*
1046          * We arrange to look up the needed comparison info just once per series
1047          * of calls, assuming the record types don't change underneath us.
1048          */
1049         ncols = Max(ncolumns1, ncolumns2);
1050         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1051         if (my_extra == NULL ||
1052                 my_extra->ncolumns < ncols)
1053         {
1054                 fcinfo->flinfo->fn_extra =
1055                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1056                                                 sizeof(RecordCompareData) - sizeof(ColumnCompareData)
1057                                                            + ncols * sizeof(ColumnCompareData));
1058                 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1059                 my_extra->ncolumns = ncols;
1060                 my_extra->record1_type = InvalidOid;
1061                 my_extra->record1_typmod = 0;
1062                 my_extra->record2_type = InvalidOid;
1063                 my_extra->record2_typmod = 0;
1064         }
1065
1066         if (my_extra->record1_type != tupType1 ||
1067                 my_extra->record1_typmod != tupTypmod1 ||
1068                 my_extra->record2_type != tupType2 ||
1069                 my_extra->record2_typmod != tupTypmod2)
1070         {
1071                 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1072                 my_extra->record1_type = tupType1;
1073                 my_extra->record1_typmod = tupTypmod1;
1074                 my_extra->record2_type = tupType2;
1075                 my_extra->record2_typmod = tupTypmod2;
1076         }
1077
1078         /* Break down the tuples into fields */
1079         values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1080         nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1081         heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1082         values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1083         nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1084         heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1085
1086         /*
1087          * Scan corresponding columns, allowing for dropped columns in different
1088          * places in the two rows.      i1 and i2 are physical column indexes, j is
1089          * the logical column index.
1090          */
1091         i1 = i2 = j = 0;
1092         while (i1 < ncolumns1 || i2 < ncolumns2)
1093         {
1094                 TypeCacheEntry *typentry;
1095                 FunctionCallInfoData locfcinfo;
1096                 bool            oprresult;
1097
1098                 /*
1099                  * Skip dropped columns
1100                  */
1101                 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1102                 {
1103                         i1++;
1104                         continue;
1105                 }
1106                 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1107                 {
1108                         i2++;
1109                         continue;
1110                 }
1111                 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1112                         break;                          /* we'll deal with mismatch below loop */
1113
1114                 /*
1115                  * Have two matching columns, they must be same type
1116                  */
1117                 if (tupdesc1->attrs[i1]->atttypid !=
1118                         tupdesc2->attrs[i2]->atttypid)
1119                         ereport(ERROR,
1120                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
1121                                          errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1122                                                         format_type_be(tupdesc1->attrs[i1]->atttypid),
1123                                                         format_type_be(tupdesc2->attrs[i2]->atttypid),
1124                                                         j + 1)));
1125
1126                 /*
1127                  * Lookup the equality function if not done already
1128                  */
1129                 typentry = my_extra->columns[j].typentry;
1130                 if (typentry == NULL ||
1131                         typentry->type_id != tupdesc1->attrs[i1]->atttypid)
1132                 {
1133                         typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
1134                                                                                  TYPECACHE_EQ_OPR_FINFO);
1135                         if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1136                                 ereport(ERROR,
1137                                                 (errcode(ERRCODE_UNDEFINED_FUNCTION),
1138                                 errmsg("could not identify an equality operator for type %s",
1139                                            format_type_be(typentry->type_id))));
1140                         my_extra->columns[j].typentry = typentry;
1141                 }
1142
1143                 /*
1144                  * We consider two NULLs equal; NULL > not-NULL.
1145                  */
1146                 if (!nulls1[i1] || !nulls2[i2])
1147                 {
1148                         if (nulls1[i1] || nulls2[i2])
1149                         {
1150                                 result = false;
1151                                 break;
1152                         }
1153
1154                         /* Compare the pair of elements */
1155                         InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
1156                                                                          NULL, NULL);
1157                         locfcinfo.arg[0] = values1[i1];
1158                         locfcinfo.arg[1] = values2[i2];
1159                         locfcinfo.argnull[0] = false;
1160                         locfcinfo.argnull[1] = false;
1161                         locfcinfo.isnull = false;
1162                         oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
1163                         if (!oprresult)
1164                         {
1165                                 result = false;
1166                                 break;
1167                         }
1168                 }
1169
1170                 /* equal, so continue to next column */
1171                 i1++, i2++, j++;
1172         }
1173
1174         /*
1175          * If we didn't break out of the loop early, check for column count
1176          * mismatch.  (We do not report such mismatch if we found unequal column
1177          * values; is that a feature or a bug?)
1178          */
1179         if (result)
1180         {
1181                 if (i1 != ncolumns1 || i2 != ncolumns2)
1182                         ereport(ERROR,
1183                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
1184                                          errmsg("cannot compare record types with different numbers of columns")));
1185         }
1186
1187         pfree(values1);
1188         pfree(nulls1);
1189         pfree(values2);
1190         pfree(nulls2);
1191         ReleaseTupleDesc(tupdesc1);
1192         ReleaseTupleDesc(tupdesc2);
1193
1194         /* Avoid leaking memory when handed toasted input. */
1195         PG_FREE_IF_COPY(record1, 0);
1196         PG_FREE_IF_COPY(record2, 1);
1197
1198         PG_RETURN_BOOL(result);
1199 }
1200
1201 Datum
1202 record_ne(PG_FUNCTION_ARGS)
1203 {
1204         PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
1205 }
1206
1207 Datum
1208 record_lt(PG_FUNCTION_ARGS)
1209 {
1210         PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
1211 }
1212
1213 Datum
1214 record_gt(PG_FUNCTION_ARGS)
1215 {
1216         PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
1217 }
1218
1219 Datum
1220 record_le(PG_FUNCTION_ARGS)
1221 {
1222         PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
1223 }
1224
1225 Datum
1226 record_ge(PG_FUNCTION_ARGS)
1227 {
1228         PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
1229 }
1230
1231 Datum
1232 btrecordcmp(PG_FUNCTION_ARGS)
1233 {
1234         PG_RETURN_INT32(record_cmp(fcinfo));
1235 }