]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/rowtypes.c
Prevent stack overflow in container-type functions.
[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-2015, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        src/backend/utils/adt/rowtypes.c
12  *
13  *-------------------------------------------------------------------------
14  */
15 #include "postgres.h"
16
17 #include <ctype.h>
18
19 #include "access/htup_details.h"
20 #include "access/tuptoaster.h"
21 #include "catalog/pg_type.h"
22 #include "funcapi.h"
23 #include "libpq/pqformat.h"
24 #include "miscadmin.h"
25 #include "utils/builtins.h"
26 #include "utils/lsyscache.h"
27 #include "utils/typcache.h"
28
29
30 /*
31  * structure to cache metadata needed for record I/O
32  */
33 typedef struct ColumnIOData
34 {
35         Oid                     column_type;
36         Oid                     typiofunc;
37         Oid                     typioparam;
38         bool            typisvarlena;
39         FmgrInfo        proc;
40 } ColumnIOData;
41
42 typedef struct RecordIOData
43 {
44         Oid                     record_type;
45         int32           record_typmod;
46         int                     ncolumns;
47         ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
48 } RecordIOData;
49
50 /*
51  * structure to cache metadata needed for record comparison
52  */
53 typedef struct ColumnCompareData
54 {
55         TypeCacheEntry *typentry;       /* has everything we need, actually */
56 } ColumnCompareData;
57
58 typedef struct RecordCompareData
59 {
60         int                     ncolumns;               /* allocated length of columns[] */
61         Oid                     record1_type;
62         int32           record1_typmod;
63         Oid                     record2_type;
64         int32           record2_typmod;
65         ColumnCompareData columns[FLEXIBLE_ARRAY_MEMBER];
66 } RecordCompareData;
67
68
69 /*
70  * record_in            - input routine for any composite type.
71  */
72 Datum
73 record_in(PG_FUNCTION_ARGS)
74 {
75         char       *string = PG_GETARG_CSTRING(0);
76         Oid                     tupType = PG_GETARG_OID(1);
77         int32           tupTypmod = PG_GETARG_INT32(2);
78         HeapTupleHeader result;
79         TupleDesc       tupdesc;
80         HeapTuple       tuple;
81         RecordIOData *my_extra;
82         bool            needComma = false;
83         int                     ncolumns;
84         int                     i;
85         char       *ptr;
86         Datum      *values;
87         bool       *nulls;
88         StringInfoData buf;
89
90         check_stack_depth();            /* recurses for record-type columns */
91
92         /*
93          * Give a friendly error message if we did not get enough info to identify
94          * the target record type.  (lookup_rowtype_tupdesc would fail anyway, but
95          * with a non-user-friendly message.)  In ordinary SQL usage, we'll get -1
96          * for typmod, since composite types and RECORD have no type modifiers at
97          * the SQL level, and thus must fail for RECORD.  However some callers can
98          * supply a valid typmod, and then we can do something useful for RECORD.
99          */
100         if (tupType == RECORDOID && tupTypmod < 0)
101                 ereport(ERROR,
102                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
103                    errmsg("input of anonymous composite types is not implemented")));
104
105         /*
106          * This comes from the composite type's pg_type.oid and stores system oids
107          * in user tables, specifically DatumTupleFields. This oid must be
108          * preserved by binary upgrades.
109          */
110         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
111         ncolumns = tupdesc->natts;
112
113         /*
114          * We arrange to look up the needed I/O info just once per series of
115          * calls, assuming the record type doesn't change underneath us.
116          */
117         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
118         if (my_extra == NULL ||
119                 my_extra->ncolumns != ncolumns)
120         {
121                 fcinfo->flinfo->fn_extra =
122                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
123                                                            offsetof(RecordIOData, columns) +
124                                                            ncolumns * sizeof(ColumnIOData));
125                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
126                 my_extra->record_type = InvalidOid;
127                 my_extra->record_typmod = 0;
128         }
129
130         if (my_extra->record_type != tupType ||
131                 my_extra->record_typmod != tupTypmod)
132         {
133                 MemSet(my_extra, 0,
134                            offsetof(RecordIOData, columns) +
135                            ncolumns * sizeof(ColumnIOData));
136                 my_extra->record_type = tupType;
137                 my_extra->record_typmod = tupTypmod;
138                 my_extra->ncolumns = ncolumns;
139         }
140
141         values = (Datum *) palloc(ncolumns * sizeof(Datum));
142         nulls = (bool *) palloc(ncolumns * sizeof(bool));
143
144         /*
145          * Scan the string.  We use "buf" to accumulate the de-quoted data for
146          * each column, which is then fed to the appropriate input converter.
147          */
148         ptr = string;
149         /* Allow leading whitespace */
150         while (*ptr && isspace((unsigned char) *ptr))
151                 ptr++;
152         if (*ptr++ != '(')
153                 ereport(ERROR,
154                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
155                                  errmsg("malformed record literal: \"%s\"", string),
156                                  errdetail("Missing left parenthesis.")));
157
158         initStringInfo(&buf);
159
160         for (i = 0; i < ncolumns; i++)
161         {
162                 ColumnIOData *column_info = &my_extra->columns[i];
163                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
164                 char       *column_data;
165
166                 /* Ignore dropped columns in datatype, but fill with nulls */
167                 if (tupdesc->attrs[i]->attisdropped)
168                 {
169                         values[i] = (Datum) 0;
170                         nulls[i] = true;
171                         continue;
172                 }
173
174                 if (needComma)
175                 {
176                         /* Skip comma that separates prior field from this one */
177                         if (*ptr == ',')
178                                 ptr++;
179                         else
180                                 /* *ptr must be ')' */
181                                 ereport(ERROR,
182                                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
183                                                  errmsg("malformed record literal: \"%s\"", string),
184                                                  errdetail("Too few columns.")));
185                 }
186
187                 /* Check for null: completely empty input means null */
188                 if (*ptr == ',' || *ptr == ')')
189                 {
190                         column_data = NULL;
191                         nulls[i] = true;
192                 }
193                 else
194                 {
195                         /* Extract string for this column */
196                         bool            inquote = false;
197
198                         resetStringInfo(&buf);
199                         while (inquote || !(*ptr == ',' || *ptr == ')'))
200                         {
201                                 char            ch = *ptr++;
202
203                                 if (ch == '\0')
204                                         ereport(ERROR,
205                                                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
206                                                          errmsg("malformed record literal: \"%s\"",
207                                                                         string),
208                                                          errdetail("Unexpected end of input.")));
209                                 if (ch == '\\')
210                                 {
211                                         if (*ptr == '\0')
212                                                 ereport(ERROR,
213                                                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
214                                                                  errmsg("malformed record literal: \"%s\"",
215                                                                                 string),
216                                                                  errdetail("Unexpected end of input.")));
217                                         appendStringInfoChar(&buf, *ptr++);
218                                 }
219                                 else if (ch == '\"')
220                                 {
221                                         if (!inquote)
222                                                 inquote = true;
223                                         else if (*ptr == '\"')
224                                         {
225                                                 /* doubled quote within quote sequence */
226                                                 appendStringInfoChar(&buf, *ptr++);
227                                         }
228                                         else
229                                                 inquote = false;
230                                 }
231                                 else
232                                         appendStringInfoChar(&buf, ch);
233                         }
234
235                         column_data = buf.data;
236                         nulls[i] = false;
237                 }
238
239                 /*
240                  * Convert the column value
241                  */
242                 if (column_info->column_type != column_type)
243                 {
244                         getTypeInputInfo(column_type,
245                                                          &column_info->typiofunc,
246                                                          &column_info->typioparam);
247                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
248                                                   fcinfo->flinfo->fn_mcxt);
249                         column_info->column_type = column_type;
250                 }
251
252                 values[i] = InputFunctionCall(&column_info->proc,
253                                                                           column_data,
254                                                                           column_info->typioparam,
255                                                                           tupdesc->attrs[i]->atttypmod);
256
257                 /*
258                  * Prep for next column
259                  */
260                 needComma = true;
261         }
262
263         if (*ptr++ != ')')
264                 ereport(ERROR,
265                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
266                                  errmsg("malformed record literal: \"%s\"", string),
267                                  errdetail("Too many columns.")));
268         /* Allow trailing whitespace */
269         while (*ptr && isspace((unsigned char) *ptr))
270                 ptr++;
271         if (*ptr)
272                 ereport(ERROR,
273                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
274                                  errmsg("malformed record literal: \"%s\"", string),
275                                  errdetail("Junk after right parenthesis.")));
276
277         tuple = heap_form_tuple(tupdesc, values, nulls);
278
279         /*
280          * We cannot return tuple->t_data because heap_form_tuple allocates it as
281          * part of a larger chunk, and our caller may expect to be able to pfree
282          * our result.  So must copy the info into a new palloc chunk.
283          */
284         result = (HeapTupleHeader) palloc(tuple->t_len);
285         memcpy(result, tuple->t_data, tuple->t_len);
286
287         heap_freetuple(tuple);
288         pfree(buf.data);
289         pfree(values);
290         pfree(nulls);
291         ReleaseTupleDesc(tupdesc);
292
293         PG_RETURN_HEAPTUPLEHEADER(result);
294 }
295
296 /*
297  * record_out           - output routine for any composite type.
298  */
299 Datum
300 record_out(PG_FUNCTION_ARGS)
301 {
302         HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
303         Oid                     tupType;
304         int32           tupTypmod;
305         TupleDesc       tupdesc;
306         HeapTupleData tuple;
307         RecordIOData *my_extra;
308         bool            needComma = false;
309         int                     ncolumns;
310         int                     i;
311         Datum      *values;
312         bool       *nulls;
313         StringInfoData buf;
314
315         check_stack_depth();            /* recurses for record-type columns */
316
317         /* Extract type info from the tuple itself */
318         tupType = HeapTupleHeaderGetTypeId(rec);
319         tupTypmod = HeapTupleHeaderGetTypMod(rec);
320         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
321         ncolumns = tupdesc->natts;
322
323         /* Build a temporary HeapTuple control structure */
324         tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
325         ItemPointerSetInvalid(&(tuple.t_self));
326         tuple.t_tableOid = InvalidOid;
327         tuple.t_data = rec;
328
329         /*
330          * We arrange to look up the needed I/O info just once per series of
331          * calls, assuming the record type doesn't change underneath us.
332          */
333         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
334         if (my_extra == NULL ||
335                 my_extra->ncolumns != ncolumns)
336         {
337                 fcinfo->flinfo->fn_extra =
338                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
339                                                            offsetof(RecordIOData, columns) +
340                                                            ncolumns * sizeof(ColumnIOData));
341                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
342                 my_extra->record_type = InvalidOid;
343                 my_extra->record_typmod = 0;
344         }
345
346         if (my_extra->record_type != tupType ||
347                 my_extra->record_typmod != tupTypmod)
348         {
349                 MemSet(my_extra, 0,
350                            offsetof(RecordIOData, columns) +
351                            ncolumns * sizeof(ColumnIOData));
352                 my_extra->record_type = tupType;
353                 my_extra->record_typmod = tupTypmod;
354                 my_extra->ncolumns = ncolumns;
355         }
356
357         values = (Datum *) palloc(ncolumns * sizeof(Datum));
358         nulls = (bool *) palloc(ncolumns * sizeof(bool));
359
360         /* Break down the tuple into fields */
361         heap_deform_tuple(&tuple, tupdesc, values, nulls);
362
363         /* And build the result string */
364         initStringInfo(&buf);
365
366         appendStringInfoChar(&buf, '(');
367
368         for (i = 0; i < ncolumns; i++)
369         {
370                 ColumnIOData *column_info = &my_extra->columns[i];
371                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
372                 Datum           attr;
373                 char       *value;
374                 char       *tmp;
375                 bool            nq;
376
377                 /* Ignore dropped columns in datatype */
378                 if (tupdesc->attrs[i]->attisdropped)
379                         continue;
380
381                 if (needComma)
382                         appendStringInfoChar(&buf, ',');
383                 needComma = true;
384
385                 if (nulls[i])
386                 {
387                         /* emit nothing... */
388                         continue;
389                 }
390
391                 /*
392                  * Convert the column value to text
393                  */
394                 if (column_info->column_type != column_type)
395                 {
396                         getTypeOutputInfo(column_type,
397                                                           &column_info->typiofunc,
398                                                           &column_info->typisvarlena);
399                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
400                                                   fcinfo->flinfo->fn_mcxt);
401                         column_info->column_type = column_type;
402                 }
403
404                 attr = values[i];
405                 value = OutputFunctionCall(&column_info->proc, attr);
406
407                 /* Detect whether we need double quotes for this value */
408                 nq = (value[0] == '\0');        /* force quotes for empty string */
409                 for (tmp = value; *tmp; tmp++)
410                 {
411                         char            ch = *tmp;
412
413                         if (ch == '"' || ch == '\\' ||
414                                 ch == '(' || ch == ')' || ch == ',' ||
415                                 isspace((unsigned char) ch))
416                         {
417                                 nq = true;
418                                 break;
419                         }
420                 }
421
422                 /* And emit the string */
423                 if (nq)
424                         appendStringInfoCharMacro(&buf, '"');
425                 for (tmp = value; *tmp; tmp++)
426                 {
427                         char            ch = *tmp;
428
429                         if (ch == '"' || ch == '\\')
430                                 appendStringInfoCharMacro(&buf, ch);
431                         appendStringInfoCharMacro(&buf, ch);
432                 }
433                 if (nq)
434                         appendStringInfoCharMacro(&buf, '"');
435         }
436
437         appendStringInfoChar(&buf, ')');
438
439         pfree(values);
440         pfree(nulls);
441         ReleaseTupleDesc(tupdesc);
442
443         PG_RETURN_CSTRING(buf.data);
444 }
445
446 /*
447  * record_recv          - binary input routine for any composite type.
448  */
449 Datum
450 record_recv(PG_FUNCTION_ARGS)
451 {
452         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
453         Oid                     tupType = PG_GETARG_OID(1);
454         int32           tupTypmod = PG_GETARG_INT32(2);
455         HeapTupleHeader result;
456         TupleDesc       tupdesc;
457         HeapTuple       tuple;
458         RecordIOData *my_extra;
459         int                     ncolumns;
460         int                     usercols;
461         int                     validcols;
462         int                     i;
463         Datum      *values;
464         bool       *nulls;
465
466         check_stack_depth();            /* recurses for record-type columns */
467
468         /*
469          * Give a friendly error message if we did not get enough info to identify
470          * the target record type.  (lookup_rowtype_tupdesc would fail anyway, but
471          * with a non-user-friendly message.)  In ordinary SQL usage, we'll get -1
472          * for typmod, since composite types and RECORD have no type modifiers at
473          * the SQL level, and thus must fail for RECORD.  However some callers can
474          * supply a valid typmod, and then we can do something useful for RECORD.
475          */
476         if (tupType == RECORDOID && tupTypmod < 0)
477                 ereport(ERROR,
478                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
479                    errmsg("input of anonymous composite types is not implemented")));
480
481         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
482         ncolumns = tupdesc->natts;
483
484         /*
485          * We arrange to look up the needed I/O info just once per series of
486          * calls, assuming the record type doesn't change underneath us.
487          */
488         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
489         if (my_extra == NULL ||
490                 my_extra->ncolumns != ncolumns)
491         {
492                 fcinfo->flinfo->fn_extra =
493                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
494                                                            offsetof(RecordIOData, columns) +
495                                                            ncolumns * sizeof(ColumnIOData));
496                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
497                 my_extra->record_type = InvalidOid;
498                 my_extra->record_typmod = 0;
499         }
500
501         if (my_extra->record_type != tupType ||
502                 my_extra->record_typmod != tupTypmod)
503         {
504                 MemSet(my_extra, 0,
505                            offsetof(RecordIOData, columns) +
506                            ncolumns * sizeof(ColumnIOData));
507                 my_extra->record_type = tupType;
508                 my_extra->record_typmod = tupTypmod;
509                 my_extra->ncolumns = ncolumns;
510         }
511
512         values = (Datum *) palloc(ncolumns * sizeof(Datum));
513         nulls = (bool *) palloc(ncolumns * sizeof(bool));
514
515         /* Fetch number of columns user thinks it has */
516         usercols = pq_getmsgint(buf, 4);
517
518         /* Need to scan to count nondeleted columns */
519         validcols = 0;
520         for (i = 0; i < ncolumns; i++)
521         {
522                 if (!tupdesc->attrs[i]->attisdropped)
523                         validcols++;
524         }
525         if (usercols != validcols)
526                 ereport(ERROR,
527                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
528                                  errmsg("wrong number of columns: %d, expected %d",
529                                                 usercols, validcols)));
530
531         /* Process each column */
532         for (i = 0; i < ncolumns; i++)
533         {
534                 ColumnIOData *column_info = &my_extra->columns[i];
535                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
536                 Oid                     coltypoid;
537                 int                     itemlen;
538                 StringInfoData item_buf;
539                 StringInfo      bufptr;
540                 char            csave;
541
542                 /* Ignore dropped columns in datatype, but fill with nulls */
543                 if (tupdesc->attrs[i]->attisdropped)
544                 {
545                         values[i] = (Datum) 0;
546                         nulls[i] = true;
547                         continue;
548                 }
549
550                 /* Verify column datatype */
551                 coltypoid = pq_getmsgint(buf, sizeof(Oid));
552                 if (coltypoid != column_type)
553                         ereport(ERROR,
554                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
555                                          errmsg("wrong data type: %u, expected %u",
556                                                         coltypoid, column_type)));
557
558                 /* Get and check the item length */
559                 itemlen = pq_getmsgint(buf, 4);
560                 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
561                         ereport(ERROR,
562                                         (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
563                                          errmsg("insufficient data left in message")));
564
565                 if (itemlen == -1)
566                 {
567                         /* -1 length means NULL */
568                         bufptr = NULL;
569                         nulls[i] = true;
570                         csave = 0;                      /* keep compiler quiet */
571                 }
572                 else
573                 {
574                         /*
575                          * Rather than copying data around, we just set up a phony
576                          * StringInfo pointing to the correct portion of the input buffer.
577                          * We assume we can scribble on the input buffer so as to maintain
578                          * the convention that StringInfos have a trailing null.
579                          */
580                         item_buf.data = &buf->data[buf->cursor];
581                         item_buf.maxlen = itemlen + 1;
582                         item_buf.len = itemlen;
583                         item_buf.cursor = 0;
584
585                         buf->cursor += itemlen;
586
587                         csave = buf->data[buf->cursor];
588                         buf->data[buf->cursor] = '\0';
589
590                         bufptr = &item_buf;
591                         nulls[i] = false;
592                 }
593
594                 /* Now call the column's receiveproc */
595                 if (column_info->column_type != column_type)
596                 {
597                         getTypeBinaryInputInfo(column_type,
598                                                                    &column_info->typiofunc,
599                                                                    &column_info->typioparam);
600                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
601                                                   fcinfo->flinfo->fn_mcxt);
602                         column_info->column_type = column_type;
603                 }
604
605                 values[i] = ReceiveFunctionCall(&column_info->proc,
606                                                                                 bufptr,
607                                                                                 column_info->typioparam,
608                                                                                 tupdesc->attrs[i]->atttypmod);
609
610                 if (bufptr)
611                 {
612                         /* Trouble if it didn't eat the whole buffer */
613                         if (item_buf.cursor != itemlen)
614                                 ereport(ERROR,
615                                                 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
616                                                  errmsg("improper binary format in record column %d",
617                                                                 i + 1)));
618
619                         buf->data[buf->cursor] = csave;
620                 }
621         }
622
623         tuple = heap_form_tuple(tupdesc, values, nulls);
624
625         /*
626          * We cannot return tuple->t_data because heap_form_tuple allocates it as
627          * part of a larger chunk, and our caller may expect to be able to pfree
628          * our result.  So must copy the info into a new palloc chunk.
629          */
630         result = (HeapTupleHeader) palloc(tuple->t_len);
631         memcpy(result, tuple->t_data, tuple->t_len);
632
633         heap_freetuple(tuple);
634         pfree(values);
635         pfree(nulls);
636         ReleaseTupleDesc(tupdesc);
637
638         PG_RETURN_HEAPTUPLEHEADER(result);
639 }
640
641 /*
642  * record_send          - binary output routine for any composite type.
643  */
644 Datum
645 record_send(PG_FUNCTION_ARGS)
646 {
647         HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
648         Oid                     tupType;
649         int32           tupTypmod;
650         TupleDesc       tupdesc;
651         HeapTupleData tuple;
652         RecordIOData *my_extra;
653         int                     ncolumns;
654         int                     validcols;
655         int                     i;
656         Datum      *values;
657         bool       *nulls;
658         StringInfoData buf;
659
660         check_stack_depth();            /* recurses for record-type columns */
661
662         /* Extract type info from the tuple itself */
663         tupType = HeapTupleHeaderGetTypeId(rec);
664         tupTypmod = HeapTupleHeaderGetTypMod(rec);
665         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
666         ncolumns = tupdesc->natts;
667
668         /* Build a temporary HeapTuple control structure */
669         tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
670         ItemPointerSetInvalid(&(tuple.t_self));
671         tuple.t_tableOid = InvalidOid;
672         tuple.t_data = rec;
673
674         /*
675          * We arrange to look up the needed I/O info just once per series of
676          * calls, assuming the record type doesn't change underneath us.
677          */
678         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
679         if (my_extra == NULL ||
680                 my_extra->ncolumns != ncolumns)
681         {
682                 fcinfo->flinfo->fn_extra =
683                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
684                                                            offsetof(RecordIOData, columns) +
685                                                            ncolumns * sizeof(ColumnIOData));
686                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
687                 my_extra->record_type = InvalidOid;
688                 my_extra->record_typmod = 0;
689         }
690
691         if (my_extra->record_type != tupType ||
692                 my_extra->record_typmod != tupTypmod)
693         {
694                 MemSet(my_extra, 0,
695                            offsetof(RecordIOData, columns) +
696                            ncolumns * sizeof(ColumnIOData));
697                 my_extra->record_type = tupType;
698                 my_extra->record_typmod = tupTypmod;
699                 my_extra->ncolumns = ncolumns;
700         }
701
702         values = (Datum *) palloc(ncolumns * sizeof(Datum));
703         nulls = (bool *) palloc(ncolumns * sizeof(bool));
704
705         /* Break down the tuple into fields */
706         heap_deform_tuple(&tuple, tupdesc, values, nulls);
707
708         /* And build the result string */
709         pq_begintypsend(&buf);
710
711         /* Need to scan to count nondeleted columns */
712         validcols = 0;
713         for (i = 0; i < ncolumns; i++)
714         {
715                 if (!tupdesc->attrs[i]->attisdropped)
716                         validcols++;
717         }
718         pq_sendint(&buf, validcols, 4);
719
720         for (i = 0; i < ncolumns; i++)
721         {
722                 ColumnIOData *column_info = &my_extra->columns[i];
723                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
724                 Datum           attr;
725                 bytea      *outputbytes;
726
727                 /* Ignore dropped columns in datatype */
728                 if (tupdesc->attrs[i]->attisdropped)
729                         continue;
730
731                 pq_sendint(&buf, column_type, sizeof(Oid));
732
733                 if (nulls[i])
734                 {
735                         /* emit -1 data length to signify a NULL */
736                         pq_sendint(&buf, -1, 4);
737                         continue;
738                 }
739
740                 /*
741                  * Convert the column value to binary
742                  */
743                 if (column_info->column_type != column_type)
744                 {
745                         getTypeBinaryOutputInfo(column_type,
746                                                                         &column_info->typiofunc,
747                                                                         &column_info->typisvarlena);
748                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
749                                                   fcinfo->flinfo->fn_mcxt);
750                         column_info->column_type = column_type;
751                 }
752
753                 attr = values[i];
754                 outputbytes = SendFunctionCall(&column_info->proc, attr);
755                 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
756                 pq_sendbytes(&buf, VARDATA(outputbytes),
757                                          VARSIZE(outputbytes) - VARHDRSZ);
758         }
759
760         pfree(values);
761         pfree(nulls);
762         ReleaseTupleDesc(tupdesc);
763
764         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
765 }
766
767
768 /*
769  * record_cmp()
770  * Internal comparison function for records.
771  *
772  * Returns -1, 0 or 1
773  *
774  * Do not assume that the two inputs are exactly the same record type;
775  * for instance we might be comparing an anonymous ROW() construct against a
776  * named composite type.  We will compare as long as they have the same number
777  * of non-dropped columns of the same types.
778  */
779 static int
780 record_cmp(FunctionCallInfo fcinfo)
781 {
782         HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
783         HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
784         int                     result = 0;
785         Oid                     tupType1;
786         Oid                     tupType2;
787         int32           tupTypmod1;
788         int32           tupTypmod2;
789         TupleDesc       tupdesc1;
790         TupleDesc       tupdesc2;
791         HeapTupleData tuple1;
792         HeapTupleData tuple2;
793         int                     ncolumns1;
794         int                     ncolumns2;
795         RecordCompareData *my_extra;
796         int                     ncols;
797         Datum      *values1;
798         Datum      *values2;
799         bool       *nulls1;
800         bool       *nulls2;
801         int                     i1;
802         int                     i2;
803         int                     j;
804
805         check_stack_depth();            /* recurses for record-type columns */
806
807         /* Extract type info from the tuples */
808         tupType1 = HeapTupleHeaderGetTypeId(record1);
809         tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
810         tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
811         ncolumns1 = tupdesc1->natts;
812         tupType2 = HeapTupleHeaderGetTypeId(record2);
813         tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
814         tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
815         ncolumns2 = tupdesc2->natts;
816
817         /* Build temporary HeapTuple control structures */
818         tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
819         ItemPointerSetInvalid(&(tuple1.t_self));
820         tuple1.t_tableOid = InvalidOid;
821         tuple1.t_data = record1;
822         tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
823         ItemPointerSetInvalid(&(tuple2.t_self));
824         tuple2.t_tableOid = InvalidOid;
825         tuple2.t_data = record2;
826
827         /*
828          * We arrange to look up the needed comparison info just once per series
829          * of calls, assuming the record types don't change underneath us.
830          */
831         ncols = Max(ncolumns1, ncolumns2);
832         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
833         if (my_extra == NULL ||
834                 my_extra->ncolumns < ncols)
835         {
836                 fcinfo->flinfo->fn_extra =
837                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
838                                                            offsetof(RecordCompareData, columns) +
839                                                            ncols * sizeof(ColumnCompareData));
840                 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
841                 my_extra->ncolumns = ncols;
842                 my_extra->record1_type = InvalidOid;
843                 my_extra->record1_typmod = 0;
844                 my_extra->record2_type = InvalidOid;
845                 my_extra->record2_typmod = 0;
846         }
847
848         if (my_extra->record1_type != tupType1 ||
849                 my_extra->record1_typmod != tupTypmod1 ||
850                 my_extra->record2_type != tupType2 ||
851                 my_extra->record2_typmod != tupTypmod2)
852         {
853                 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
854                 my_extra->record1_type = tupType1;
855                 my_extra->record1_typmod = tupTypmod1;
856                 my_extra->record2_type = tupType2;
857                 my_extra->record2_typmod = tupTypmod2;
858         }
859
860         /* Break down the tuples into fields */
861         values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
862         nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
863         heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
864         values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
865         nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
866         heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
867
868         /*
869          * Scan corresponding columns, allowing for dropped columns in different
870          * places in the two rows.  i1 and i2 are physical column indexes, j is
871          * the logical column index.
872          */
873         i1 = i2 = j = 0;
874         while (i1 < ncolumns1 || i2 < ncolumns2)
875         {
876                 TypeCacheEntry *typentry;
877                 Oid                     collation;
878
879                 /*
880                  * Skip dropped columns
881                  */
882                 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
883                 {
884                         i1++;
885                         continue;
886                 }
887                 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
888                 {
889                         i2++;
890                         continue;
891                 }
892                 if (i1 >= ncolumns1 || i2 >= ncolumns2)
893                         break;                          /* we'll deal with mismatch below loop */
894
895                 /*
896                  * Have two matching columns, they must be same type
897                  */
898                 if (tupdesc1->attrs[i1]->atttypid !=
899                         tupdesc2->attrs[i2]->atttypid)
900                         ereport(ERROR,
901                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
902                                          errmsg("cannot compare dissimilar column types %s and %s at record column %d",
903                                                         format_type_be(tupdesc1->attrs[i1]->atttypid),
904                                                         format_type_be(tupdesc2->attrs[i2]->atttypid),
905                                                         j + 1)));
906
907                 /*
908                  * If they're not same collation, we don't complain here, but the
909                  * comparison function might.
910                  */
911                 collation = tupdesc1->attrs[i1]->attcollation;
912                 if (collation != tupdesc2->attrs[i2]->attcollation)
913                         collation = InvalidOid;
914
915                 /*
916                  * Lookup the comparison function if not done already
917                  */
918                 typentry = my_extra->columns[j].typentry;
919                 if (typentry == NULL ||
920                         typentry->type_id != tupdesc1->attrs[i1]->atttypid)
921                 {
922                         typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
923                                                                                  TYPECACHE_CMP_PROC_FINFO);
924                         if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
925                                 ereport(ERROR,
926                                                 (errcode(ERRCODE_UNDEFINED_FUNCTION),
927                                 errmsg("could not identify a comparison function for type %s",
928                                            format_type_be(typentry->type_id))));
929                         my_extra->columns[j].typentry = typentry;
930                 }
931
932                 /*
933                  * We consider two NULLs equal; NULL > not-NULL.
934                  */
935                 if (!nulls1[i1] || !nulls2[i2])
936                 {
937                         FunctionCallInfoData locfcinfo;
938                         int32           cmpresult;
939
940                         if (nulls1[i1])
941                         {
942                                 /* arg1 is greater than arg2 */
943                                 result = 1;
944                                 break;
945                         }
946                         if (nulls2[i2])
947                         {
948                                 /* arg1 is less than arg2 */
949                                 result = -1;
950                                 break;
951                         }
952
953                         /* Compare the pair of elements */
954                         InitFunctionCallInfoData(locfcinfo, &typentry->cmp_proc_finfo, 2,
955                                                                          collation, NULL, NULL);
956                         locfcinfo.arg[0] = values1[i1];
957                         locfcinfo.arg[1] = values2[i2];
958                         locfcinfo.argnull[0] = false;
959                         locfcinfo.argnull[1] = false;
960                         locfcinfo.isnull = false;
961                         cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
962
963                         if (cmpresult < 0)
964                         {
965                                 /* arg1 is less than arg2 */
966                                 result = -1;
967                                 break;
968                         }
969                         else if (cmpresult > 0)
970                         {
971                                 /* arg1 is greater than arg2 */
972                                 result = 1;
973                                 break;
974                         }
975                 }
976
977                 /* equal, so continue to next column */
978                 i1++, i2++, j++;
979         }
980
981         /*
982          * If we didn't break out of the loop early, check for column count
983          * mismatch.  (We do not report such mismatch if we found unequal column
984          * values; is that a feature or a bug?)
985          */
986         if (result == 0)
987         {
988                 if (i1 != ncolumns1 || i2 != ncolumns2)
989                         ereport(ERROR,
990                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
991                                          errmsg("cannot compare record types with different numbers of columns")));
992         }
993
994         pfree(values1);
995         pfree(nulls1);
996         pfree(values2);
997         pfree(nulls2);
998         ReleaseTupleDesc(tupdesc1);
999         ReleaseTupleDesc(tupdesc2);
1000
1001         /* Avoid leaking memory when handed toasted input. */
1002         PG_FREE_IF_COPY(record1, 0);
1003         PG_FREE_IF_COPY(record2, 1);
1004
1005         return result;
1006 }
1007
1008 /*
1009  * record_eq :
1010  *                compares two records for equality
1011  * result :
1012  *                returns true if the records are equal, false otherwise.
1013  *
1014  * Note: we do not use record_cmp here, since equality may be meaningful in
1015  * datatypes that don't have a total ordering (and hence no btree support).
1016  */
1017 Datum
1018 record_eq(PG_FUNCTION_ARGS)
1019 {
1020         HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1021         HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1022         bool            result = true;
1023         Oid                     tupType1;
1024         Oid                     tupType2;
1025         int32           tupTypmod1;
1026         int32           tupTypmod2;
1027         TupleDesc       tupdesc1;
1028         TupleDesc       tupdesc2;
1029         HeapTupleData tuple1;
1030         HeapTupleData tuple2;
1031         int                     ncolumns1;
1032         int                     ncolumns2;
1033         RecordCompareData *my_extra;
1034         int                     ncols;
1035         Datum      *values1;
1036         Datum      *values2;
1037         bool       *nulls1;
1038         bool       *nulls2;
1039         int                     i1;
1040         int                     i2;
1041         int                     j;
1042
1043         check_stack_depth();            /* recurses for record-type columns */
1044
1045         /* Extract type info from the tuples */
1046         tupType1 = HeapTupleHeaderGetTypeId(record1);
1047         tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1048         tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1049         ncolumns1 = tupdesc1->natts;
1050         tupType2 = HeapTupleHeaderGetTypeId(record2);
1051         tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1052         tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1053         ncolumns2 = tupdesc2->natts;
1054
1055         /* Build temporary HeapTuple control structures */
1056         tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1057         ItemPointerSetInvalid(&(tuple1.t_self));
1058         tuple1.t_tableOid = InvalidOid;
1059         tuple1.t_data = record1;
1060         tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1061         ItemPointerSetInvalid(&(tuple2.t_self));
1062         tuple2.t_tableOid = InvalidOid;
1063         tuple2.t_data = record2;
1064
1065         /*
1066          * We arrange to look up the needed comparison info just once per series
1067          * of calls, assuming the record types don't change underneath us.
1068          */
1069         ncols = Max(ncolumns1, ncolumns2);
1070         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1071         if (my_extra == NULL ||
1072                 my_extra->ncolumns < ncols)
1073         {
1074                 fcinfo->flinfo->fn_extra =
1075                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1076                                                            offsetof(RecordCompareData, columns) +
1077                                                            ncols * sizeof(ColumnCompareData));
1078                 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1079                 my_extra->ncolumns = ncols;
1080                 my_extra->record1_type = InvalidOid;
1081                 my_extra->record1_typmod = 0;
1082                 my_extra->record2_type = InvalidOid;
1083                 my_extra->record2_typmod = 0;
1084         }
1085
1086         if (my_extra->record1_type != tupType1 ||
1087                 my_extra->record1_typmod != tupTypmod1 ||
1088                 my_extra->record2_type != tupType2 ||
1089                 my_extra->record2_typmod != tupTypmod2)
1090         {
1091                 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1092                 my_extra->record1_type = tupType1;
1093                 my_extra->record1_typmod = tupTypmod1;
1094                 my_extra->record2_type = tupType2;
1095                 my_extra->record2_typmod = tupTypmod2;
1096         }
1097
1098         /* Break down the tuples into fields */
1099         values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1100         nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1101         heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1102         values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1103         nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1104         heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1105
1106         /*
1107          * Scan corresponding columns, allowing for dropped columns in different
1108          * places in the two rows.  i1 and i2 are physical column indexes, j is
1109          * the logical column index.
1110          */
1111         i1 = i2 = j = 0;
1112         while (i1 < ncolumns1 || i2 < ncolumns2)
1113         {
1114                 TypeCacheEntry *typentry;
1115                 Oid                     collation;
1116                 FunctionCallInfoData locfcinfo;
1117                 bool            oprresult;
1118
1119                 /*
1120                  * Skip dropped columns
1121                  */
1122                 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1123                 {
1124                         i1++;
1125                         continue;
1126                 }
1127                 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1128                 {
1129                         i2++;
1130                         continue;
1131                 }
1132                 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1133                         break;                          /* we'll deal with mismatch below loop */
1134
1135                 /*
1136                  * Have two matching columns, they must be same type
1137                  */
1138                 if (tupdesc1->attrs[i1]->atttypid !=
1139                         tupdesc2->attrs[i2]->atttypid)
1140                         ereport(ERROR,
1141                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
1142                                          errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1143                                                         format_type_be(tupdesc1->attrs[i1]->atttypid),
1144                                                         format_type_be(tupdesc2->attrs[i2]->atttypid),
1145                                                         j + 1)));
1146
1147                 /*
1148                  * If they're not same collation, we don't complain here, but the
1149                  * equality function might.
1150                  */
1151                 collation = tupdesc1->attrs[i1]->attcollation;
1152                 if (collation != tupdesc2->attrs[i2]->attcollation)
1153                         collation = InvalidOid;
1154
1155                 /*
1156                  * Lookup the equality function if not done already
1157                  */
1158                 typentry = my_extra->columns[j].typentry;
1159                 if (typentry == NULL ||
1160                         typentry->type_id != tupdesc1->attrs[i1]->atttypid)
1161                 {
1162                         typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
1163                                                                                  TYPECACHE_EQ_OPR_FINFO);
1164                         if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1165                                 ereport(ERROR,
1166                                                 (errcode(ERRCODE_UNDEFINED_FUNCTION),
1167                                 errmsg("could not identify an equality operator for type %s",
1168                                            format_type_be(typentry->type_id))));
1169                         my_extra->columns[j].typentry = typentry;
1170                 }
1171
1172                 /*
1173                  * We consider two NULLs equal; NULL > not-NULL.
1174                  */
1175                 if (!nulls1[i1] || !nulls2[i2])
1176                 {
1177                         if (nulls1[i1] || nulls2[i2])
1178                         {
1179                                 result = false;
1180                                 break;
1181                         }
1182
1183                         /* Compare the pair of elements */
1184                         InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
1185                                                                          collation, NULL, NULL);
1186                         locfcinfo.arg[0] = values1[i1];
1187                         locfcinfo.arg[1] = values2[i2];
1188                         locfcinfo.argnull[0] = false;
1189                         locfcinfo.argnull[1] = false;
1190                         locfcinfo.isnull = false;
1191                         oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
1192                         if (!oprresult)
1193                         {
1194                                 result = false;
1195                                 break;
1196                         }
1197                 }
1198
1199                 /* equal, so continue to next column */
1200                 i1++, i2++, j++;
1201         }
1202
1203         /*
1204          * If we didn't break out of the loop early, check for column count
1205          * mismatch.  (We do not report such mismatch if we found unequal column
1206          * values; is that a feature or a bug?)
1207          */
1208         if (result)
1209         {
1210                 if (i1 != ncolumns1 || i2 != ncolumns2)
1211                         ereport(ERROR,
1212                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
1213                                          errmsg("cannot compare record types with different numbers of columns")));
1214         }
1215
1216         pfree(values1);
1217         pfree(nulls1);
1218         pfree(values2);
1219         pfree(nulls2);
1220         ReleaseTupleDesc(tupdesc1);
1221         ReleaseTupleDesc(tupdesc2);
1222
1223         /* Avoid leaking memory when handed toasted input. */
1224         PG_FREE_IF_COPY(record1, 0);
1225         PG_FREE_IF_COPY(record2, 1);
1226
1227         PG_RETURN_BOOL(result);
1228 }
1229
1230 Datum
1231 record_ne(PG_FUNCTION_ARGS)
1232 {
1233         PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
1234 }
1235
1236 Datum
1237 record_lt(PG_FUNCTION_ARGS)
1238 {
1239         PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
1240 }
1241
1242 Datum
1243 record_gt(PG_FUNCTION_ARGS)
1244 {
1245         PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
1246 }
1247
1248 Datum
1249 record_le(PG_FUNCTION_ARGS)
1250 {
1251         PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
1252 }
1253
1254 Datum
1255 record_ge(PG_FUNCTION_ARGS)
1256 {
1257         PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
1258 }
1259
1260 Datum
1261 btrecordcmp(PG_FUNCTION_ARGS)
1262 {
1263         PG_RETURN_INT32(record_cmp(fcinfo));
1264 }
1265
1266
1267 /*
1268  * record_image_cmp :
1269  * Internal byte-oriented comparison function for records.
1270  *
1271  * Returns -1, 0 or 1
1272  *
1273  * Note: The normal concepts of "equality" do not apply here; different
1274  * representation of values considered to be equal are not considered to be
1275  * identical.  As an example, for the citext type 'A' and 'a' are equal, but
1276  * they are not identical.
1277  */
1278 static int
1279 record_image_cmp(FunctionCallInfo fcinfo)
1280 {
1281         HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1282         HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1283         int                     result = 0;
1284         Oid                     tupType1;
1285         Oid                     tupType2;
1286         int32           tupTypmod1;
1287         int32           tupTypmod2;
1288         TupleDesc       tupdesc1;
1289         TupleDesc       tupdesc2;
1290         HeapTupleData tuple1;
1291         HeapTupleData tuple2;
1292         int                     ncolumns1;
1293         int                     ncolumns2;
1294         RecordCompareData *my_extra;
1295         int                     ncols;
1296         Datum      *values1;
1297         Datum      *values2;
1298         bool       *nulls1;
1299         bool       *nulls2;
1300         int                     i1;
1301         int                     i2;
1302         int                     j;
1303
1304         /* Extract type info from the tuples */
1305         tupType1 = HeapTupleHeaderGetTypeId(record1);
1306         tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1307         tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1308         ncolumns1 = tupdesc1->natts;
1309         tupType2 = HeapTupleHeaderGetTypeId(record2);
1310         tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1311         tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1312         ncolumns2 = tupdesc2->natts;
1313
1314         /* Build temporary HeapTuple control structures */
1315         tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1316         ItemPointerSetInvalid(&(tuple1.t_self));
1317         tuple1.t_tableOid = InvalidOid;
1318         tuple1.t_data = record1;
1319         tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1320         ItemPointerSetInvalid(&(tuple2.t_self));
1321         tuple2.t_tableOid = InvalidOid;
1322         tuple2.t_data = record2;
1323
1324         /*
1325          * We arrange to look up the needed comparison info just once per series
1326          * of calls, assuming the record types don't change underneath us.
1327          */
1328         ncols = Max(ncolumns1, ncolumns2);
1329         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1330         if (my_extra == NULL ||
1331                 my_extra->ncolumns < ncols)
1332         {
1333                 fcinfo->flinfo->fn_extra =
1334                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1335                                                            offsetof(RecordCompareData, columns) +
1336                                                            ncols * sizeof(ColumnCompareData));
1337                 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1338                 my_extra->ncolumns = ncols;
1339                 my_extra->record1_type = InvalidOid;
1340                 my_extra->record1_typmod = 0;
1341                 my_extra->record2_type = InvalidOid;
1342                 my_extra->record2_typmod = 0;
1343         }
1344
1345         if (my_extra->record1_type != tupType1 ||
1346                 my_extra->record1_typmod != tupTypmod1 ||
1347                 my_extra->record2_type != tupType2 ||
1348                 my_extra->record2_typmod != tupTypmod2)
1349         {
1350                 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1351                 my_extra->record1_type = tupType1;
1352                 my_extra->record1_typmod = tupTypmod1;
1353                 my_extra->record2_type = tupType2;
1354                 my_extra->record2_typmod = tupTypmod2;
1355         }
1356
1357         /* Break down the tuples into fields */
1358         values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1359         nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1360         heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1361         values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1362         nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1363         heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1364
1365         /*
1366          * Scan corresponding columns, allowing for dropped columns in different
1367          * places in the two rows.  i1 and i2 are physical column indexes, j is
1368          * the logical column index.
1369          */
1370         i1 = i2 = j = 0;
1371         while (i1 < ncolumns1 || i2 < ncolumns2)
1372         {
1373                 /*
1374                  * Skip dropped columns
1375                  */
1376                 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1377                 {
1378                         i1++;
1379                         continue;
1380                 }
1381                 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1382                 {
1383                         i2++;
1384                         continue;
1385                 }
1386                 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1387                         break;                          /* we'll deal with mismatch below loop */
1388
1389                 /*
1390                  * Have two matching columns, they must be same type
1391                  */
1392                 if (tupdesc1->attrs[i1]->atttypid !=
1393                         tupdesc2->attrs[i2]->atttypid)
1394                         ereport(ERROR,
1395                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
1396                                          errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1397                                                         format_type_be(tupdesc1->attrs[i1]->atttypid),
1398                                                         format_type_be(tupdesc2->attrs[i2]->atttypid),
1399                                                         j + 1)));
1400
1401                 /*
1402                  * The same type should have the same length (or both should be
1403                  * variable).
1404                  */
1405                 Assert(tupdesc1->attrs[i1]->attlen ==
1406                            tupdesc2->attrs[i2]->attlen);
1407
1408                 /*
1409                  * We consider two NULLs equal; NULL > not-NULL.
1410                  */
1411                 if (!nulls1[i1] || !nulls2[i2])
1412                 {
1413                         int                     cmpresult = 0;
1414
1415                         if (nulls1[i1])
1416                         {
1417                                 /* arg1 is greater than arg2 */
1418                                 result = 1;
1419                                 break;
1420                         }
1421                         if (nulls2[i2])
1422                         {
1423                                 /* arg1 is less than arg2 */
1424                                 result = -1;
1425                                 break;
1426                         }
1427
1428                         /* Compare the pair of elements */
1429                         if (tupdesc1->attrs[i1]->attlen == -1)
1430                         {
1431                                 Size            len1,
1432                                                         len2;
1433                                 struct varlena *arg1val;
1434                                 struct varlena *arg2val;
1435
1436                                 len1 = toast_raw_datum_size(values1[i1]);
1437                                 len2 = toast_raw_datum_size(values2[i2]);
1438                                 arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
1439                                 arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
1440
1441                                 cmpresult = memcmp(VARDATA_ANY(arg1val),
1442                                                                    VARDATA_ANY(arg2val),
1443                                                                    Min(len1, len2) - VARHDRSZ);
1444                                 if ((cmpresult == 0) && (len1 != len2))
1445                                         cmpresult = (len1 < len2) ? -1 : 1;
1446
1447                                 if ((Pointer) arg1val != (Pointer) values1[i1])
1448                                         pfree(arg1val);
1449                                 if ((Pointer) arg2val != (Pointer) values2[i2])
1450                                         pfree(arg2val);
1451                         }
1452                         else if (tupdesc1->attrs[i1]->attbyval)
1453                         {
1454                                 switch (tupdesc1->attrs[i1]->attlen)
1455                                 {
1456                                         case 1:
1457                                                 if (GET_1_BYTE(values1[i1]) !=
1458                                                         GET_1_BYTE(values2[i2]))
1459                                                 {
1460                                                         cmpresult = (GET_1_BYTE(values1[i1]) <
1461                                                                                  GET_1_BYTE(values2[i2])) ? -1 : 1;
1462                                                 }
1463                                                 break;
1464                                         case 2:
1465                                                 if (GET_2_BYTES(values1[i1]) !=
1466                                                         GET_2_BYTES(values2[i2]))
1467                                                 {
1468                                                         cmpresult = (GET_2_BYTES(values1[i1]) <
1469                                                                                  GET_2_BYTES(values2[i2])) ? -1 : 1;
1470                                                 }
1471                                                 break;
1472                                         case 4:
1473                                                 if (GET_4_BYTES(values1[i1]) !=
1474                                                         GET_4_BYTES(values2[i2]))
1475                                                 {
1476                                                         cmpresult = (GET_4_BYTES(values1[i1]) <
1477                                                                                  GET_4_BYTES(values2[i2])) ? -1 : 1;
1478                                                 }
1479                                                 break;
1480 #if SIZEOF_DATUM == 8
1481                                         case 8:
1482                                                 if (GET_8_BYTES(values1[i1]) !=
1483                                                         GET_8_BYTES(values2[i2]))
1484                                                 {
1485                                                         cmpresult = (GET_8_BYTES(values1[i1]) <
1486                                                                                  GET_8_BYTES(values2[i2])) ? -1 : 1;
1487                                                 }
1488                                                 break;
1489 #endif
1490                                         default:
1491                                                 Assert(false);  /* cannot happen */
1492                                 }
1493                         }
1494                         else
1495                         {
1496                                 cmpresult = memcmp(DatumGetPointer(values1[i1]),
1497                                                                    DatumGetPointer(values2[i2]),
1498                                                                    tupdesc1->attrs[i1]->attlen);
1499                         }
1500
1501                         if (cmpresult < 0)
1502                         {
1503                                 /* arg1 is less than arg2 */
1504                                 result = -1;
1505                                 break;
1506                         }
1507                         else if (cmpresult > 0)
1508                         {
1509                                 /* arg1 is greater than arg2 */
1510                                 result = 1;
1511                                 break;
1512                         }
1513                 }
1514
1515                 /* equal, so continue to next column */
1516                 i1++, i2++, j++;
1517         }
1518
1519         /*
1520          * If we didn't break out of the loop early, check for column count
1521          * mismatch.  (We do not report such mismatch if we found unequal column
1522          * values; is that a feature or a bug?)
1523          */
1524         if (result == 0)
1525         {
1526                 if (i1 != ncolumns1 || i2 != ncolumns2)
1527                         ereport(ERROR,
1528                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
1529                                          errmsg("cannot compare record types with different numbers of columns")));
1530         }
1531
1532         pfree(values1);
1533         pfree(nulls1);
1534         pfree(values2);
1535         pfree(nulls2);
1536         ReleaseTupleDesc(tupdesc1);
1537         ReleaseTupleDesc(tupdesc2);
1538
1539         /* Avoid leaking memory when handed toasted input. */
1540         PG_FREE_IF_COPY(record1, 0);
1541         PG_FREE_IF_COPY(record2, 1);
1542
1543         return result;
1544 }
1545
1546 /*
1547  * record_image_eq :
1548  *                compares two records for identical contents, based on byte images
1549  * result :
1550  *                returns true if the records are identical, false otherwise.
1551  *
1552  * Note: we do not use record_image_cmp here, since we can avoid
1553  * de-toasting for unequal lengths this way.
1554  */
1555 Datum
1556 record_image_eq(PG_FUNCTION_ARGS)
1557 {
1558         HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1559         HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1560         bool            result = true;
1561         Oid                     tupType1;
1562         Oid                     tupType2;
1563         int32           tupTypmod1;
1564         int32           tupTypmod2;
1565         TupleDesc       tupdesc1;
1566         TupleDesc       tupdesc2;
1567         HeapTupleData tuple1;
1568         HeapTupleData tuple2;
1569         int                     ncolumns1;
1570         int                     ncolumns2;
1571         RecordCompareData *my_extra;
1572         int                     ncols;
1573         Datum      *values1;
1574         Datum      *values2;
1575         bool       *nulls1;
1576         bool       *nulls2;
1577         int                     i1;
1578         int                     i2;
1579         int                     j;
1580
1581         /* Extract type info from the tuples */
1582         tupType1 = HeapTupleHeaderGetTypeId(record1);
1583         tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1584         tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1585         ncolumns1 = tupdesc1->natts;
1586         tupType2 = HeapTupleHeaderGetTypeId(record2);
1587         tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1588         tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1589         ncolumns2 = tupdesc2->natts;
1590
1591         /* Build temporary HeapTuple control structures */
1592         tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1593         ItemPointerSetInvalid(&(tuple1.t_self));
1594         tuple1.t_tableOid = InvalidOid;
1595         tuple1.t_data = record1;
1596         tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1597         ItemPointerSetInvalid(&(tuple2.t_self));
1598         tuple2.t_tableOid = InvalidOid;
1599         tuple2.t_data = record2;
1600
1601         /*
1602          * We arrange to look up the needed comparison info just once per series
1603          * of calls, assuming the record types don't change underneath us.
1604          */
1605         ncols = Max(ncolumns1, ncolumns2);
1606         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1607         if (my_extra == NULL ||
1608                 my_extra->ncolumns < ncols)
1609         {
1610                 fcinfo->flinfo->fn_extra =
1611                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1612                                                            offsetof(RecordCompareData, columns) +
1613                                                            ncols * sizeof(ColumnCompareData));
1614                 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1615                 my_extra->ncolumns = ncols;
1616                 my_extra->record1_type = InvalidOid;
1617                 my_extra->record1_typmod = 0;
1618                 my_extra->record2_type = InvalidOid;
1619                 my_extra->record2_typmod = 0;
1620         }
1621
1622         if (my_extra->record1_type != tupType1 ||
1623                 my_extra->record1_typmod != tupTypmod1 ||
1624                 my_extra->record2_type != tupType2 ||
1625                 my_extra->record2_typmod != tupTypmod2)
1626         {
1627                 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1628                 my_extra->record1_type = tupType1;
1629                 my_extra->record1_typmod = tupTypmod1;
1630                 my_extra->record2_type = tupType2;
1631                 my_extra->record2_typmod = tupTypmod2;
1632         }
1633
1634         /* Break down the tuples into fields */
1635         values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1636         nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1637         heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1638         values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1639         nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1640         heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1641
1642         /*
1643          * Scan corresponding columns, allowing for dropped columns in different
1644          * places in the two rows.  i1 and i2 are physical column indexes, j is
1645          * the logical column index.
1646          */
1647         i1 = i2 = j = 0;
1648         while (i1 < ncolumns1 || i2 < ncolumns2)
1649         {
1650                 /*
1651                  * Skip dropped columns
1652                  */
1653                 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1654                 {
1655                         i1++;
1656                         continue;
1657                 }
1658                 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1659                 {
1660                         i2++;
1661                         continue;
1662                 }
1663                 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1664                         break;                          /* we'll deal with mismatch below loop */
1665
1666                 /*
1667                  * Have two matching columns, they must be same type
1668                  */
1669                 if (tupdesc1->attrs[i1]->atttypid !=
1670                         tupdesc2->attrs[i2]->atttypid)
1671                         ereport(ERROR,
1672                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
1673                                          errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1674                                                         format_type_be(tupdesc1->attrs[i1]->atttypid),
1675                                                         format_type_be(tupdesc2->attrs[i2]->atttypid),
1676                                                         j + 1)));
1677
1678                 /*
1679                  * We consider two NULLs equal; NULL > not-NULL.
1680                  */
1681                 if (!nulls1[i1] || !nulls2[i2])
1682                 {
1683                         if (nulls1[i1] || nulls2[i2])
1684                         {
1685                                 result = false;
1686                                 break;
1687                         }
1688
1689                         /* Compare the pair of elements */
1690                         if (tupdesc1->attrs[i1]->attlen == -1)
1691                         {
1692                                 Size            len1,
1693                                                         len2;
1694
1695                                 len1 = toast_raw_datum_size(values1[i1]);
1696                                 len2 = toast_raw_datum_size(values2[i2]);
1697                                 /* No need to de-toast if lengths don't match. */
1698                                 if (len1 != len2)
1699                                         result = false;
1700                                 else
1701                                 {
1702                                         struct varlena *arg1val;
1703                                         struct varlena *arg2val;
1704
1705                                         arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
1706                                         arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
1707
1708                                         result = (memcmp(VARDATA_ANY(arg1val),
1709                                                                          VARDATA_ANY(arg2val),
1710                                                                          len1 - VARHDRSZ) == 0);
1711
1712                                         /* Only free memory if it's a copy made here. */
1713                                         if ((Pointer) arg1val != (Pointer) values1[i1])
1714                                                 pfree(arg1val);
1715                                         if ((Pointer) arg2val != (Pointer) values2[i2])
1716                                                 pfree(arg2val);
1717                                 }
1718                         }
1719                         else if (tupdesc1->attrs[i1]->attbyval)
1720                         {
1721                                 switch (tupdesc1->attrs[i1]->attlen)
1722                                 {
1723                                         case 1:
1724                                                 result = (GET_1_BYTE(values1[i1]) ==
1725                                                                   GET_1_BYTE(values2[i2]));
1726                                                 break;
1727                                         case 2:
1728                                                 result = (GET_2_BYTES(values1[i1]) ==
1729                                                                   GET_2_BYTES(values2[i2]));
1730                                                 break;
1731                                         case 4:
1732                                                 result = (GET_4_BYTES(values1[i1]) ==
1733                                                                   GET_4_BYTES(values2[i2]));
1734                                                 break;
1735 #if SIZEOF_DATUM == 8
1736                                         case 8:
1737                                                 result = (GET_8_BYTES(values1[i1]) ==
1738                                                                   GET_8_BYTES(values2[i2]));
1739                                                 break;
1740 #endif
1741                                         default:
1742                                                 Assert(false);  /* cannot happen */
1743                                 }
1744                         }
1745                         else
1746                         {
1747                                 result = (memcmp(DatumGetPointer(values1[i1]),
1748                                                                  DatumGetPointer(values2[i2]),
1749                                                                  tupdesc1->attrs[i1]->attlen) == 0);
1750                         }
1751                         if (!result)
1752                                 break;
1753                 }
1754
1755                 /* equal, so continue to next column */
1756                 i1++, i2++, j++;
1757         }
1758
1759         /*
1760          * If we didn't break out of the loop early, check for column count
1761          * mismatch.  (We do not report such mismatch if we found unequal column
1762          * values; is that a feature or a bug?)
1763          */
1764         if (result)
1765         {
1766                 if (i1 != ncolumns1 || i2 != ncolumns2)
1767                         ereport(ERROR,
1768                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
1769                                          errmsg("cannot compare record types with different numbers of columns")));
1770         }
1771
1772         pfree(values1);
1773         pfree(nulls1);
1774         pfree(values2);
1775         pfree(nulls2);
1776         ReleaseTupleDesc(tupdesc1);
1777         ReleaseTupleDesc(tupdesc2);
1778
1779         /* Avoid leaking memory when handed toasted input. */
1780         PG_FREE_IF_COPY(record1, 0);
1781         PG_FREE_IF_COPY(record2, 1);
1782
1783         PG_RETURN_BOOL(result);
1784 }
1785
1786 Datum
1787 record_image_ne(PG_FUNCTION_ARGS)
1788 {
1789         PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo)));
1790 }
1791
1792 Datum
1793 record_image_lt(PG_FUNCTION_ARGS)
1794 {
1795         PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0);
1796 }
1797
1798 Datum
1799 record_image_gt(PG_FUNCTION_ARGS)
1800 {
1801         PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0);
1802 }
1803
1804 Datum
1805 record_image_le(PG_FUNCTION_ARGS)
1806 {
1807         PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0);
1808 }
1809
1810 Datum
1811 record_image_ge(PG_FUNCTION_ARGS)
1812 {
1813         PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0);
1814 }
1815
1816 Datum
1817 btrecordimagecmp(PG_FUNCTION_ARGS)
1818 {
1819         PG_RETURN_INT32(record_image_cmp(fcinfo));
1820 }