1 /*-------------------------------------------------------------------------
4 * I/O and comparison functions for generic composite types.
6 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * src/backend/utils/adt/rowtypes.c
13 *-------------------------------------------------------------------------
19 #include "access/detoast.h"
20 #include "access/htup_details.h"
21 #include "catalog/pg_type.h"
23 #include "libpq/pqformat.h"
24 #include "miscadmin.h"
25 #include "utils/builtins.h"
26 #include "utils/datum.h"
27 #include "utils/lsyscache.h"
28 #include "utils/typcache.h"
32 * structure to cache metadata needed for record I/O
34 typedef struct ColumnIOData
43 typedef struct RecordIOData
48 ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
52 * structure to cache metadata needed for record comparison
54 typedef struct ColumnCompareData
56 TypeCacheEntry *typentry; /* has everything we need, actually */
59 typedef struct RecordCompareData
61 int ncolumns; /* allocated length of columns[] */
66 ColumnCompareData columns[FLEXIBLE_ARRAY_MEMBER];
71 * record_in - input routine for any composite type.
74 record_in(PG_FUNCTION_ARGS)
76 char *string = PG_GETARG_CSTRING(0);
77 Oid tupType = PG_GETARG_OID(1);
78 int32 tupTypmod = PG_GETARG_INT32(2);
79 HeapTupleHeader result;
82 RecordIOData *my_extra;
83 bool needComma = false;
91 check_stack_depth(); /* recurses for record-type columns */
94 * Give a friendly error message if we did not get enough info to identify
95 * the target record type. (lookup_rowtype_tupdesc would fail anyway, but
96 * with a non-user-friendly message.) In ordinary SQL usage, we'll get -1
97 * for typmod, since composite types and RECORD have no type modifiers at
98 * the SQL level, and thus must fail for RECORD. However some callers can
99 * supply a valid typmod, and then we can do something useful for RECORD.
101 if (tupType == RECORDOID && tupTypmod < 0)
103 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
104 errmsg("input of anonymous composite types is not implemented")));
107 * This comes from the composite type's pg_type.oid and stores system oids
108 * in user tables, specifically DatumTupleFields. This oid must be
109 * preserved by binary upgrades.
111 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
112 ncolumns = tupdesc->natts;
115 * We arrange to look up the needed I/O info just once per series of
116 * calls, assuming the record type doesn't change underneath us.
118 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
119 if (my_extra == NULL ||
120 my_extra->ncolumns != ncolumns)
122 fcinfo->flinfo->fn_extra =
123 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
124 offsetof(RecordIOData, columns) +
125 ncolumns * sizeof(ColumnIOData));
126 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
127 my_extra->record_type = InvalidOid;
128 my_extra->record_typmod = 0;
131 if (my_extra->record_type != tupType ||
132 my_extra->record_typmod != tupTypmod)
135 offsetof(RecordIOData, columns) +
136 ncolumns * sizeof(ColumnIOData));
137 my_extra->record_type = tupType;
138 my_extra->record_typmod = tupTypmod;
139 my_extra->ncolumns = ncolumns;
142 values = (Datum *) palloc(ncolumns * sizeof(Datum));
143 nulls = (bool *) palloc(ncolumns * sizeof(bool));
146 * Scan the string. We use "buf" to accumulate the de-quoted data for
147 * each column, which is then fed to the appropriate input converter.
150 /* Allow leading whitespace */
151 while (*ptr && isspace((unsigned char) *ptr))
155 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
156 errmsg("malformed record literal: \"%s\"", string),
157 errdetail("Missing left parenthesis.")));
159 initStringInfo(&buf);
161 for (i = 0; i < ncolumns; i++)
163 Form_pg_attribute att = TupleDescAttr(tupdesc, i);
164 ColumnIOData *column_info = &my_extra->columns[i];
165 Oid column_type = att->atttypid;
168 /* Ignore dropped columns in datatype, but fill with nulls */
169 if (att->attisdropped)
171 values[i] = (Datum) 0;
178 /* Skip comma that separates prior field from this one */
182 /* *ptr must be ')' */
184 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
185 errmsg("malformed record literal: \"%s\"", string),
186 errdetail("Too few columns.")));
189 /* Check for null: completely empty input means null */
190 if (*ptr == ',' || *ptr == ')')
197 /* Extract string for this column */
198 bool inquote = false;
200 resetStringInfo(&buf);
201 while (inquote || !(*ptr == ',' || *ptr == ')'))
207 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
208 errmsg("malformed record literal: \"%s\"",
210 errdetail("Unexpected end of input.")));
215 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
216 errmsg("malformed record literal: \"%s\"",
218 errdetail("Unexpected end of input.")));
219 appendStringInfoChar(&buf, *ptr++);
225 else if (*ptr == '"')
227 /* doubled quote within quote sequence */
228 appendStringInfoChar(&buf, *ptr++);
234 appendStringInfoChar(&buf, ch);
237 column_data = buf.data;
242 * Convert the column value
244 if (column_info->column_type != column_type)
246 getTypeInputInfo(column_type,
247 &column_info->typiofunc,
248 &column_info->typioparam);
249 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
250 fcinfo->flinfo->fn_mcxt);
251 column_info->column_type = column_type;
254 values[i] = InputFunctionCall(&column_info->proc,
256 column_info->typioparam,
260 * Prep for next column
267 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
268 errmsg("malformed record literal: \"%s\"", string),
269 errdetail("Too many columns.")));
270 /* Allow trailing whitespace */
271 while (*ptr && isspace((unsigned char) *ptr))
275 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
276 errmsg("malformed record literal: \"%s\"", string),
277 errdetail("Junk after right parenthesis.")));
279 tuple = heap_form_tuple(tupdesc, values, nulls);
282 * We cannot return tuple->t_data because heap_form_tuple allocates it as
283 * part of a larger chunk, and our caller may expect to be able to pfree
284 * our result. So must copy the info into a new palloc chunk.
286 result = (HeapTupleHeader) palloc(tuple->t_len);
287 memcpy(result, tuple->t_data, tuple->t_len);
289 heap_freetuple(tuple);
293 ReleaseTupleDesc(tupdesc);
295 PG_RETURN_HEAPTUPLEHEADER(result);
299 * record_out - output routine for any composite type.
302 record_out(PG_FUNCTION_ARGS)
304 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
309 RecordIOData *my_extra;
310 bool needComma = false;
317 check_stack_depth(); /* recurses for record-type columns */
319 /* Extract type info from the tuple itself */
320 tupType = HeapTupleHeaderGetTypeId(rec);
321 tupTypmod = HeapTupleHeaderGetTypMod(rec);
322 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
323 ncolumns = tupdesc->natts;
325 /* Build a temporary HeapTuple control structure */
326 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
327 ItemPointerSetInvalid(&(tuple.t_self));
328 tuple.t_tableOid = InvalidOid;
332 * We arrange to look up the needed I/O info just once per series of
333 * calls, assuming the record type doesn't change underneath us.
335 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
336 if (my_extra == NULL ||
337 my_extra->ncolumns != ncolumns)
339 fcinfo->flinfo->fn_extra =
340 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
341 offsetof(RecordIOData, columns) +
342 ncolumns * sizeof(ColumnIOData));
343 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
344 my_extra->record_type = InvalidOid;
345 my_extra->record_typmod = 0;
348 if (my_extra->record_type != tupType ||
349 my_extra->record_typmod != tupTypmod)
352 offsetof(RecordIOData, columns) +
353 ncolumns * sizeof(ColumnIOData));
354 my_extra->record_type = tupType;
355 my_extra->record_typmod = tupTypmod;
356 my_extra->ncolumns = ncolumns;
359 values = (Datum *) palloc(ncolumns * sizeof(Datum));
360 nulls = (bool *) palloc(ncolumns * sizeof(bool));
362 /* Break down the tuple into fields */
363 heap_deform_tuple(&tuple, tupdesc, values, nulls);
365 /* And build the result string */
366 initStringInfo(&buf);
368 appendStringInfoChar(&buf, '(');
370 for (i = 0; i < ncolumns; i++)
372 Form_pg_attribute att = TupleDescAttr(tupdesc, i);
373 ColumnIOData *column_info = &my_extra->columns[i];
374 Oid column_type = att->atttypid;
380 /* Ignore dropped columns in datatype */
381 if (att->attisdropped)
385 appendStringInfoChar(&buf, ',');
390 /* emit nothing... */
395 * Convert the column value to text
397 if (column_info->column_type != column_type)
399 getTypeOutputInfo(column_type,
400 &column_info->typiofunc,
401 &column_info->typisvarlena);
402 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
403 fcinfo->flinfo->fn_mcxt);
404 column_info->column_type = column_type;
408 value = OutputFunctionCall(&column_info->proc, attr);
410 /* Detect whether we need double quotes for this value */
411 nq = (value[0] == '\0'); /* force quotes for empty string */
412 for (tmp = value; *tmp; tmp++)
416 if (ch == '"' || ch == '\\' ||
417 ch == '(' || ch == ')' || ch == ',' ||
418 isspace((unsigned char) ch))
425 /* And emit the string */
427 appendStringInfoCharMacro(&buf, '"');
428 for (tmp = value; *tmp; tmp++)
432 if (ch == '"' || ch == '\\')
433 appendStringInfoCharMacro(&buf, ch);
434 appendStringInfoCharMacro(&buf, ch);
437 appendStringInfoCharMacro(&buf, '"');
440 appendStringInfoChar(&buf, ')');
444 ReleaseTupleDesc(tupdesc);
446 PG_RETURN_CSTRING(buf.data);
450 * record_recv - binary input routine for any composite type.
453 record_recv(PG_FUNCTION_ARGS)
455 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
456 Oid tupType = PG_GETARG_OID(1);
457 int32 tupTypmod = PG_GETARG_INT32(2);
458 HeapTupleHeader result;
461 RecordIOData *my_extra;
469 check_stack_depth(); /* recurses for record-type columns */
472 * Give a friendly error message if we did not get enough info to identify
473 * the target record type. (lookup_rowtype_tupdesc would fail anyway, but
474 * with a non-user-friendly message.) In ordinary SQL usage, we'll get -1
475 * for typmod, since composite types and RECORD have no type modifiers at
476 * the SQL level, and thus must fail for RECORD. However some callers can
477 * supply a valid typmod, and then we can do something useful for RECORD.
479 if (tupType == RECORDOID && tupTypmod < 0)
481 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
482 errmsg("input of anonymous composite types is not implemented")));
484 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
485 ncolumns = tupdesc->natts;
488 * We arrange to look up the needed I/O info just once per series of
489 * calls, assuming the record type doesn't change underneath us.
491 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
492 if (my_extra == NULL ||
493 my_extra->ncolumns != ncolumns)
495 fcinfo->flinfo->fn_extra =
496 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
497 offsetof(RecordIOData, columns) +
498 ncolumns * sizeof(ColumnIOData));
499 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
500 my_extra->record_type = InvalidOid;
501 my_extra->record_typmod = 0;
504 if (my_extra->record_type != tupType ||
505 my_extra->record_typmod != tupTypmod)
508 offsetof(RecordIOData, columns) +
509 ncolumns * sizeof(ColumnIOData));
510 my_extra->record_type = tupType;
511 my_extra->record_typmod = tupTypmod;
512 my_extra->ncolumns = ncolumns;
515 values = (Datum *) palloc(ncolumns * sizeof(Datum));
516 nulls = (bool *) palloc(ncolumns * sizeof(bool));
518 /* Fetch number of columns user thinks it has */
519 usercols = pq_getmsgint(buf, 4);
521 /* Need to scan to count nondeleted columns */
523 for (i = 0; i < ncolumns; i++)
525 if (!TupleDescAttr(tupdesc, i)->attisdropped)
528 if (usercols != validcols)
530 (errcode(ERRCODE_DATATYPE_MISMATCH),
531 errmsg("wrong number of columns: %d, expected %d",
532 usercols, validcols)));
534 /* Process each column */
535 for (i = 0; i < ncolumns; i++)
537 Form_pg_attribute att = TupleDescAttr(tupdesc, i);
538 ColumnIOData *column_info = &my_extra->columns[i];
539 Oid column_type = att->atttypid;
542 StringInfoData item_buf;
546 /* Ignore dropped columns in datatype, but fill with nulls */
547 if (att->attisdropped)
549 values[i] = (Datum) 0;
554 /* Verify column datatype */
555 coltypoid = pq_getmsgint(buf, sizeof(Oid));
556 if (coltypoid != column_type)
558 (errcode(ERRCODE_DATATYPE_MISMATCH),
559 errmsg("wrong data type: %u, expected %u",
560 coltypoid, column_type)));
562 /* Get and check the item length */
563 itemlen = pq_getmsgint(buf, 4);
564 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
566 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
567 errmsg("insufficient data left in message")));
571 /* -1 length means NULL */
574 csave = 0; /* keep compiler quiet */
579 * Rather than copying data around, we just set up a phony
580 * StringInfo pointing to the correct portion of the input buffer.
581 * We assume we can scribble on the input buffer so as to maintain
582 * the convention that StringInfos have a trailing null.
584 item_buf.data = &buf->data[buf->cursor];
585 item_buf.maxlen = itemlen + 1;
586 item_buf.len = itemlen;
589 buf->cursor += itemlen;
591 csave = buf->data[buf->cursor];
592 buf->data[buf->cursor] = '\0';
598 /* Now call the column's receiveproc */
599 if (column_info->column_type != column_type)
601 getTypeBinaryInputInfo(column_type,
602 &column_info->typiofunc,
603 &column_info->typioparam);
604 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
605 fcinfo->flinfo->fn_mcxt);
606 column_info->column_type = column_type;
609 values[i] = ReceiveFunctionCall(&column_info->proc,
611 column_info->typioparam,
616 /* Trouble if it didn't eat the whole buffer */
617 if (item_buf.cursor != itemlen)
619 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
620 errmsg("improper binary format in record column %d",
623 buf->data[buf->cursor] = csave;
627 tuple = heap_form_tuple(tupdesc, values, nulls);
630 * We cannot return tuple->t_data because heap_form_tuple allocates it as
631 * part of a larger chunk, and our caller may expect to be able to pfree
632 * our result. So must copy the info into a new palloc chunk.
634 result = (HeapTupleHeader) palloc(tuple->t_len);
635 memcpy(result, tuple->t_data, tuple->t_len);
637 heap_freetuple(tuple);
640 ReleaseTupleDesc(tupdesc);
642 PG_RETURN_HEAPTUPLEHEADER(result);
646 * record_send - binary output routine for any composite type.
649 record_send(PG_FUNCTION_ARGS)
651 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
656 RecordIOData *my_extra;
664 check_stack_depth(); /* recurses for record-type columns */
666 /* Extract type info from the tuple itself */
667 tupType = HeapTupleHeaderGetTypeId(rec);
668 tupTypmod = HeapTupleHeaderGetTypMod(rec);
669 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
670 ncolumns = tupdesc->natts;
672 /* Build a temporary HeapTuple control structure */
673 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
674 ItemPointerSetInvalid(&(tuple.t_self));
675 tuple.t_tableOid = InvalidOid;
679 * We arrange to look up the needed I/O info just once per series of
680 * calls, assuming the record type doesn't change underneath us.
682 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
683 if (my_extra == NULL ||
684 my_extra->ncolumns != ncolumns)
686 fcinfo->flinfo->fn_extra =
687 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
688 offsetof(RecordIOData, columns) +
689 ncolumns * sizeof(ColumnIOData));
690 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
691 my_extra->record_type = InvalidOid;
692 my_extra->record_typmod = 0;
695 if (my_extra->record_type != tupType ||
696 my_extra->record_typmod != tupTypmod)
699 offsetof(RecordIOData, columns) +
700 ncolumns * sizeof(ColumnIOData));
701 my_extra->record_type = tupType;
702 my_extra->record_typmod = tupTypmod;
703 my_extra->ncolumns = ncolumns;
706 values = (Datum *) palloc(ncolumns * sizeof(Datum));
707 nulls = (bool *) palloc(ncolumns * sizeof(bool));
709 /* Break down the tuple into fields */
710 heap_deform_tuple(&tuple, tupdesc, values, nulls);
712 /* And build the result string */
713 pq_begintypsend(&buf);
715 /* Need to scan to count nondeleted columns */
717 for (i = 0; i < ncolumns; i++)
719 if (!TupleDescAttr(tupdesc, i)->attisdropped)
722 pq_sendint32(&buf, validcols);
724 for (i = 0; i < ncolumns; i++)
726 Form_pg_attribute att = TupleDescAttr(tupdesc, i);
727 ColumnIOData *column_info = &my_extra->columns[i];
728 Oid column_type = att->atttypid;
732 /* Ignore dropped columns in datatype */
733 if (att->attisdropped)
736 pq_sendint32(&buf, column_type);
740 /* emit -1 data length to signify a NULL */
741 pq_sendint32(&buf, -1);
746 * Convert the column value to binary
748 if (column_info->column_type != column_type)
750 getTypeBinaryOutputInfo(column_type,
751 &column_info->typiofunc,
752 &column_info->typisvarlena);
753 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
754 fcinfo->flinfo->fn_mcxt);
755 column_info->column_type = column_type;
759 outputbytes = SendFunctionCall(&column_info->proc, attr);
760 pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ);
761 pq_sendbytes(&buf, VARDATA(outputbytes),
762 VARSIZE(outputbytes) - VARHDRSZ);
767 ReleaseTupleDesc(tupdesc);
769 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
775 * Internal comparison function for records.
779 * Do not assume that the two inputs are exactly the same record type;
780 * for instance we might be comparing an anonymous ROW() construct against a
781 * named composite type. We will compare as long as they have the same number
782 * of non-dropped columns of the same types.
785 record_cmp(FunctionCallInfo fcinfo)
787 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
788 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
796 HeapTupleData tuple1;
797 HeapTupleData tuple2;
800 RecordCompareData *my_extra;
810 check_stack_depth(); /* recurses for record-type columns */
812 /* Extract type info from the tuples */
813 tupType1 = HeapTupleHeaderGetTypeId(record1);
814 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
815 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
816 ncolumns1 = tupdesc1->natts;
817 tupType2 = HeapTupleHeaderGetTypeId(record2);
818 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
819 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
820 ncolumns2 = tupdesc2->natts;
822 /* Build temporary HeapTuple control structures */
823 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
824 ItemPointerSetInvalid(&(tuple1.t_self));
825 tuple1.t_tableOid = InvalidOid;
826 tuple1.t_data = record1;
827 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
828 ItemPointerSetInvalid(&(tuple2.t_self));
829 tuple2.t_tableOid = InvalidOid;
830 tuple2.t_data = record2;
833 * We arrange to look up the needed comparison info just once per series
834 * of calls, assuming the record types don't change underneath us.
836 ncols = Max(ncolumns1, ncolumns2);
837 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
838 if (my_extra == NULL ||
839 my_extra->ncolumns < ncols)
841 fcinfo->flinfo->fn_extra =
842 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
843 offsetof(RecordCompareData, columns) +
844 ncols * sizeof(ColumnCompareData));
845 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
846 my_extra->ncolumns = ncols;
847 my_extra->record1_type = InvalidOid;
848 my_extra->record1_typmod = 0;
849 my_extra->record2_type = InvalidOid;
850 my_extra->record2_typmod = 0;
853 if (my_extra->record1_type != tupType1 ||
854 my_extra->record1_typmod != tupTypmod1 ||
855 my_extra->record2_type != tupType2 ||
856 my_extra->record2_typmod != tupTypmod2)
858 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
859 my_extra->record1_type = tupType1;
860 my_extra->record1_typmod = tupTypmod1;
861 my_extra->record2_type = tupType2;
862 my_extra->record2_typmod = tupTypmod2;
865 /* Break down the tuples into fields */
866 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
867 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
868 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
869 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
870 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
871 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
874 * Scan corresponding columns, allowing for dropped columns in different
875 * places in the two rows. i1 and i2 are physical column indexes, j is
876 * the logical column index.
879 while (i1 < ncolumns1 || i2 < ncolumns2)
881 Form_pg_attribute att1;
882 Form_pg_attribute att2;
883 TypeCacheEntry *typentry;
887 * Skip dropped columns
889 if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
894 if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
899 if (i1 >= ncolumns1 || i2 >= ncolumns2)
900 break; /* we'll deal with mismatch below loop */
902 att1 = TupleDescAttr(tupdesc1, i1);
903 att2 = TupleDescAttr(tupdesc2, i2);
906 * Have two matching columns, they must be same type
908 if (att1->atttypid != att2->atttypid)
910 (errcode(ERRCODE_DATATYPE_MISMATCH),
911 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
912 format_type_be(att1->atttypid),
913 format_type_be(att2->atttypid),
917 * If they're not same collation, we don't complain here, but the
918 * comparison function might.
920 collation = att1->attcollation;
921 if (collation != att2->attcollation)
922 collation = InvalidOid;
925 * Lookup the comparison function if not done already
927 typentry = my_extra->columns[j].typentry;
928 if (typentry == NULL ||
929 typentry->type_id != att1->atttypid)
931 typentry = lookup_type_cache(att1->atttypid,
932 TYPECACHE_CMP_PROC_FINFO);
933 if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
935 (errcode(ERRCODE_UNDEFINED_FUNCTION),
936 errmsg("could not identify a comparison function for type %s",
937 format_type_be(typentry->type_id))));
938 my_extra->columns[j].typentry = typentry;
942 * We consider two NULLs equal; NULL > not-NULL.
944 if (!nulls1[i1] || !nulls2[i2])
946 LOCAL_FCINFO(locfcinfo, 2);
951 /* arg1 is greater than arg2 */
957 /* arg1 is less than arg2 */
962 /* Compare the pair of elements */
963 InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2,
964 collation, NULL, NULL);
965 locfcinfo->args[0].value = values1[i1];
966 locfcinfo->args[0].isnull = false;
967 locfcinfo->args[1].value = values2[i2];
968 locfcinfo->args[1].isnull = false;
969 locfcinfo->isnull = false;
970 cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo));
974 /* arg1 is less than arg2 */
978 else if (cmpresult > 0)
980 /* arg1 is greater than arg2 */
986 /* equal, so continue to next column */
991 * If we didn't break out of the loop early, check for column count
992 * mismatch. (We do not report such mismatch if we found unequal column
993 * values; is that a feature or a bug?)
997 if (i1 != ncolumns1 || i2 != ncolumns2)
999 (errcode(ERRCODE_DATATYPE_MISMATCH),
1000 errmsg("cannot compare record types with different numbers of columns")));
1007 ReleaseTupleDesc(tupdesc1);
1008 ReleaseTupleDesc(tupdesc2);
1010 /* Avoid leaking memory when handed toasted input. */
1011 PG_FREE_IF_COPY(record1, 0);
1012 PG_FREE_IF_COPY(record2, 1);
1019 * compares two records for equality
1021 * returns true if the records are equal, false otherwise.
1023 * Note: we do not use record_cmp here, since equality may be meaningful in
1024 * datatypes that don't have a total ordering (and hence no btree support).
1027 record_eq(PG_FUNCTION_ARGS)
1029 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1030 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1038 HeapTupleData tuple1;
1039 HeapTupleData tuple2;
1042 RecordCompareData *my_extra;
1052 check_stack_depth(); /* recurses for record-type columns */
1054 /* Extract type info from the tuples */
1055 tupType1 = HeapTupleHeaderGetTypeId(record1);
1056 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1057 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1058 ncolumns1 = tupdesc1->natts;
1059 tupType2 = HeapTupleHeaderGetTypeId(record2);
1060 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1061 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1062 ncolumns2 = tupdesc2->natts;
1064 /* Build temporary HeapTuple control structures */
1065 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1066 ItemPointerSetInvalid(&(tuple1.t_self));
1067 tuple1.t_tableOid = InvalidOid;
1068 tuple1.t_data = record1;
1069 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1070 ItemPointerSetInvalid(&(tuple2.t_self));
1071 tuple2.t_tableOid = InvalidOid;
1072 tuple2.t_data = record2;
1075 * We arrange to look up the needed comparison info just once per series
1076 * of calls, assuming the record types don't change underneath us.
1078 ncols = Max(ncolumns1, ncolumns2);
1079 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1080 if (my_extra == NULL ||
1081 my_extra->ncolumns < ncols)
1083 fcinfo->flinfo->fn_extra =
1084 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1085 offsetof(RecordCompareData, columns) +
1086 ncols * sizeof(ColumnCompareData));
1087 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1088 my_extra->ncolumns = ncols;
1089 my_extra->record1_type = InvalidOid;
1090 my_extra->record1_typmod = 0;
1091 my_extra->record2_type = InvalidOid;
1092 my_extra->record2_typmod = 0;
1095 if (my_extra->record1_type != tupType1 ||
1096 my_extra->record1_typmod != tupTypmod1 ||
1097 my_extra->record2_type != tupType2 ||
1098 my_extra->record2_typmod != tupTypmod2)
1100 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1101 my_extra->record1_type = tupType1;
1102 my_extra->record1_typmod = tupTypmod1;
1103 my_extra->record2_type = tupType2;
1104 my_extra->record2_typmod = tupTypmod2;
1107 /* Break down the tuples into fields */
1108 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1109 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1110 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1111 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1112 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1113 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1116 * Scan corresponding columns, allowing for dropped columns in different
1117 * places in the two rows. i1 and i2 are physical column indexes, j is
1118 * the logical column index.
1121 while (i1 < ncolumns1 || i2 < ncolumns2)
1123 LOCAL_FCINFO(locfcinfo, 2);
1124 Form_pg_attribute att1;
1125 Form_pg_attribute att2;
1126 TypeCacheEntry *typentry;
1131 * Skip dropped columns
1133 if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1138 if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1143 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1144 break; /* we'll deal with mismatch below loop */
1146 att1 = TupleDescAttr(tupdesc1, i1);
1147 att2 = TupleDescAttr(tupdesc2, i2);
1150 * Have two matching columns, they must be same type
1152 if (att1->atttypid != att2->atttypid)
1154 (errcode(ERRCODE_DATATYPE_MISMATCH),
1155 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1156 format_type_be(att1->atttypid),
1157 format_type_be(att2->atttypid),
1161 * If they're not same collation, we don't complain here, but the
1162 * equality function might.
1164 collation = att1->attcollation;
1165 if (collation != att2->attcollation)
1166 collation = InvalidOid;
1169 * Lookup the equality function if not done already
1171 typentry = my_extra->columns[j].typentry;
1172 if (typentry == NULL ||
1173 typentry->type_id != att1->atttypid)
1175 typentry = lookup_type_cache(att1->atttypid,
1176 TYPECACHE_EQ_OPR_FINFO);
1177 if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1179 (errcode(ERRCODE_UNDEFINED_FUNCTION),
1180 errmsg("could not identify an equality operator for type %s",
1181 format_type_be(typentry->type_id))));
1182 my_extra->columns[j].typentry = typentry;
1186 * We consider two NULLs equal; NULL > not-NULL.
1188 if (!nulls1[i1] || !nulls2[i2])
1190 if (nulls1[i1] || nulls2[i2])
1196 /* Compare the pair of elements */
1197 InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
1198 collation, NULL, NULL);
1199 locfcinfo->args[0].value = values1[i1];
1200 locfcinfo->args[0].isnull = false;
1201 locfcinfo->args[1].value = values2[i2];
1202 locfcinfo->args[1].isnull = false;
1203 locfcinfo->isnull = false;
1204 oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
1212 /* equal, so continue to next column */
1217 * If we didn't break out of the loop early, check for column count
1218 * mismatch. (We do not report such mismatch if we found unequal column
1219 * values; is that a feature or a bug?)
1223 if (i1 != ncolumns1 || i2 != ncolumns2)
1225 (errcode(ERRCODE_DATATYPE_MISMATCH),
1226 errmsg("cannot compare record types with different numbers of columns")));
1233 ReleaseTupleDesc(tupdesc1);
1234 ReleaseTupleDesc(tupdesc2);
1236 /* Avoid leaking memory when handed toasted input. */
1237 PG_FREE_IF_COPY(record1, 0);
1238 PG_FREE_IF_COPY(record2, 1);
1240 PG_RETURN_BOOL(result);
1244 record_ne(PG_FUNCTION_ARGS)
1246 PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
1250 record_lt(PG_FUNCTION_ARGS)
1252 PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
1256 record_gt(PG_FUNCTION_ARGS)
1258 PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
1262 record_le(PG_FUNCTION_ARGS)
1264 PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
1268 record_ge(PG_FUNCTION_ARGS)
1270 PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
1274 btrecordcmp(PG_FUNCTION_ARGS)
1276 PG_RETURN_INT32(record_cmp(fcinfo));
1281 * record_image_cmp :
1282 * Internal byte-oriented comparison function for records.
1284 * Returns -1, 0 or 1
1286 * Note: The normal concepts of "equality" do not apply here; different
1287 * representation of values considered to be equal are not considered to be
1288 * identical. As an example, for the citext type 'A' and 'a' are equal, but
1289 * they are not identical.
1292 record_image_cmp(FunctionCallInfo fcinfo)
1294 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1295 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1303 HeapTupleData tuple1;
1304 HeapTupleData tuple2;
1307 RecordCompareData *my_extra;
1317 /* Extract type info from the tuples */
1318 tupType1 = HeapTupleHeaderGetTypeId(record1);
1319 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1320 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1321 ncolumns1 = tupdesc1->natts;
1322 tupType2 = HeapTupleHeaderGetTypeId(record2);
1323 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1324 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1325 ncolumns2 = tupdesc2->natts;
1327 /* Build temporary HeapTuple control structures */
1328 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1329 ItemPointerSetInvalid(&(tuple1.t_self));
1330 tuple1.t_tableOid = InvalidOid;
1331 tuple1.t_data = record1;
1332 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1333 ItemPointerSetInvalid(&(tuple2.t_self));
1334 tuple2.t_tableOid = InvalidOid;
1335 tuple2.t_data = record2;
1338 * We arrange to look up the needed comparison info just once per series
1339 * of calls, assuming the record types don't change underneath us.
1341 ncols = Max(ncolumns1, ncolumns2);
1342 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1343 if (my_extra == NULL ||
1344 my_extra->ncolumns < ncols)
1346 fcinfo->flinfo->fn_extra =
1347 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1348 offsetof(RecordCompareData, columns) +
1349 ncols * sizeof(ColumnCompareData));
1350 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1351 my_extra->ncolumns = ncols;
1352 my_extra->record1_type = InvalidOid;
1353 my_extra->record1_typmod = 0;
1354 my_extra->record2_type = InvalidOid;
1355 my_extra->record2_typmod = 0;
1358 if (my_extra->record1_type != tupType1 ||
1359 my_extra->record1_typmod != tupTypmod1 ||
1360 my_extra->record2_type != tupType2 ||
1361 my_extra->record2_typmod != tupTypmod2)
1363 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1364 my_extra->record1_type = tupType1;
1365 my_extra->record1_typmod = tupTypmod1;
1366 my_extra->record2_type = tupType2;
1367 my_extra->record2_typmod = tupTypmod2;
1370 /* Break down the tuples into fields */
1371 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1372 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1373 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1374 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1375 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1376 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1379 * Scan corresponding columns, allowing for dropped columns in different
1380 * places in the two rows. i1 and i2 are physical column indexes, j is
1381 * the logical column index.
1384 while (i1 < ncolumns1 || i2 < ncolumns2)
1386 Form_pg_attribute att1;
1387 Form_pg_attribute att2;
1390 * Skip dropped columns
1392 if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1397 if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1402 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1403 break; /* we'll deal with mismatch below loop */
1405 att1 = TupleDescAttr(tupdesc1, i1);
1406 att2 = TupleDescAttr(tupdesc2, i2);
1409 * Have two matching columns, they must be same type
1411 if (att1->atttypid != att2->atttypid)
1413 (errcode(ERRCODE_DATATYPE_MISMATCH),
1414 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1415 format_type_be(att1->atttypid),
1416 format_type_be(att2->atttypid),
1420 * The same type should have the same length (or both should be
1423 Assert(att1->attlen == att2->attlen);
1426 * We consider two NULLs equal; NULL > not-NULL.
1428 if (!nulls1[i1] || !nulls2[i2])
1434 /* arg1 is greater than arg2 */
1440 /* arg1 is less than arg2 */
1445 /* Compare the pair of elements */
1446 if (att1->attlen == -1)
1450 struct varlena *arg1val;
1451 struct varlena *arg2val;
1453 len1 = toast_raw_datum_size(values1[i1]);
1454 len2 = toast_raw_datum_size(values2[i2]);
1455 arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
1456 arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
1458 cmpresult = memcmp(VARDATA_ANY(arg1val),
1459 VARDATA_ANY(arg2val),
1460 Min(len1, len2) - VARHDRSZ);
1461 if ((cmpresult == 0) && (len1 != len2))
1462 cmpresult = (len1 < len2) ? -1 : 1;
1464 if ((Pointer) arg1val != (Pointer) values1[i1])
1466 if ((Pointer) arg2val != (Pointer) values2[i2])
1469 else if (att1->attbyval)
1471 if (values1[i1] != values2[i2])
1472 cmpresult = (values1[i1] < values2[i2]) ? -1 : 1;
1476 cmpresult = memcmp(DatumGetPointer(values1[i1]),
1477 DatumGetPointer(values2[i2]),
1483 /* arg1 is less than arg2 */
1487 else if (cmpresult > 0)
1489 /* arg1 is greater than arg2 */
1495 /* equal, so continue to next column */
1500 * If we didn't break out of the loop early, check for column count
1501 * mismatch. (We do not report such mismatch if we found unequal column
1502 * values; is that a feature or a bug?)
1506 if (i1 != ncolumns1 || i2 != ncolumns2)
1508 (errcode(ERRCODE_DATATYPE_MISMATCH),
1509 errmsg("cannot compare record types with different numbers of columns")));
1516 ReleaseTupleDesc(tupdesc1);
1517 ReleaseTupleDesc(tupdesc2);
1519 /* Avoid leaking memory when handed toasted input. */
1520 PG_FREE_IF_COPY(record1, 0);
1521 PG_FREE_IF_COPY(record2, 1);
1528 * compares two records for identical contents, based on byte images
1530 * returns true if the records are identical, false otherwise.
1532 * Note: we do not use record_image_cmp here, since we can avoid
1533 * de-toasting for unequal lengths this way.
1536 record_image_eq(PG_FUNCTION_ARGS)
1538 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1539 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1547 HeapTupleData tuple1;
1548 HeapTupleData tuple2;
1551 RecordCompareData *my_extra;
1561 /* Extract type info from the tuples */
1562 tupType1 = HeapTupleHeaderGetTypeId(record1);
1563 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1564 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1565 ncolumns1 = tupdesc1->natts;
1566 tupType2 = HeapTupleHeaderGetTypeId(record2);
1567 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1568 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1569 ncolumns2 = tupdesc2->natts;
1571 /* Build temporary HeapTuple control structures */
1572 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1573 ItemPointerSetInvalid(&(tuple1.t_self));
1574 tuple1.t_tableOid = InvalidOid;
1575 tuple1.t_data = record1;
1576 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1577 ItemPointerSetInvalid(&(tuple2.t_self));
1578 tuple2.t_tableOid = InvalidOid;
1579 tuple2.t_data = record2;
1582 * We arrange to look up the needed comparison info just once per series
1583 * of calls, assuming the record types don't change underneath us.
1585 ncols = Max(ncolumns1, ncolumns2);
1586 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1587 if (my_extra == NULL ||
1588 my_extra->ncolumns < ncols)
1590 fcinfo->flinfo->fn_extra =
1591 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1592 offsetof(RecordCompareData, columns) +
1593 ncols * sizeof(ColumnCompareData));
1594 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1595 my_extra->ncolumns = ncols;
1596 my_extra->record1_type = InvalidOid;
1597 my_extra->record1_typmod = 0;
1598 my_extra->record2_type = InvalidOid;
1599 my_extra->record2_typmod = 0;
1602 if (my_extra->record1_type != tupType1 ||
1603 my_extra->record1_typmod != tupTypmod1 ||
1604 my_extra->record2_type != tupType2 ||
1605 my_extra->record2_typmod != tupTypmod2)
1607 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1608 my_extra->record1_type = tupType1;
1609 my_extra->record1_typmod = tupTypmod1;
1610 my_extra->record2_type = tupType2;
1611 my_extra->record2_typmod = tupTypmod2;
1614 /* Break down the tuples into fields */
1615 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1616 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1617 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1618 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1619 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1620 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1623 * Scan corresponding columns, allowing for dropped columns in different
1624 * places in the two rows. i1 and i2 are physical column indexes, j is
1625 * the logical column index.
1628 while (i1 < ncolumns1 || i2 < ncolumns2)
1630 Form_pg_attribute att1;
1631 Form_pg_attribute att2;
1634 * Skip dropped columns
1636 if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1641 if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1646 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1647 break; /* we'll deal with mismatch below loop */
1649 att1 = TupleDescAttr(tupdesc1, i1);
1650 att2 = TupleDescAttr(tupdesc2, i2);
1653 * Have two matching columns, they must be same type
1655 if (att1->atttypid != att2->atttypid)
1657 (errcode(ERRCODE_DATATYPE_MISMATCH),
1658 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1659 format_type_be(att1->atttypid),
1660 format_type_be(att2->atttypid),
1664 * We consider two NULLs equal; NULL > not-NULL.
1666 if (!nulls1[i1] || !nulls2[i2])
1668 if (nulls1[i1] || nulls2[i2])
1674 /* Compare the pair of elements */
1675 result = datum_image_eq(values1[i1], values2[i2], att1->attbyval, att2->attlen);
1680 /* equal, so continue to next column */
1685 * If we didn't break out of the loop early, check for column count
1686 * mismatch. (We do not report such mismatch if we found unequal column
1687 * values; is that a feature or a bug?)
1691 if (i1 != ncolumns1 || i2 != ncolumns2)
1693 (errcode(ERRCODE_DATATYPE_MISMATCH),
1694 errmsg("cannot compare record types with different numbers of columns")));
1701 ReleaseTupleDesc(tupdesc1);
1702 ReleaseTupleDesc(tupdesc2);
1704 /* Avoid leaking memory when handed toasted input. */
1705 PG_FREE_IF_COPY(record1, 0);
1706 PG_FREE_IF_COPY(record2, 1);
1708 PG_RETURN_BOOL(result);
1712 record_image_ne(PG_FUNCTION_ARGS)
1714 PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo)));
1718 record_image_lt(PG_FUNCTION_ARGS)
1720 PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0);
1724 record_image_gt(PG_FUNCTION_ARGS)
1726 PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0);
1730 record_image_le(PG_FUNCTION_ARGS)
1732 PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0);
1736 record_image_ge(PG_FUNCTION_ARGS)
1738 PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0);
1742 btrecordimagecmp(PG_FUNCTION_ARGS)
1744 PG_RETURN_INT32(record_image_cmp(fcinfo));