1 /*-------------------------------------------------------------------------
4 * I/O and comparison functions for generic composite types.
6 * Portions Copyright (c) 1996-2017, 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/htup_details.h"
20 #include "access/tuptoaster.h"
21 #include "catalog/pg_type.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"
31 * structure to cache metadata needed for record I/O
33 typedef struct ColumnIOData
42 typedef struct RecordIOData
47 ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
51 * structure to cache metadata needed for record comparison
53 typedef struct ColumnCompareData
55 TypeCacheEntry *typentry; /* has everything we need, actually */
58 typedef struct RecordCompareData
60 int ncolumns; /* allocated length of columns[] */
65 ColumnCompareData columns[FLEXIBLE_ARRAY_MEMBER];
70 * record_in - input routine for any composite type.
73 record_in(PG_FUNCTION_ARGS)
75 char *string = PG_GETARG_CSTRING(0);
76 Oid tupType = PG_GETARG_OID(1);
77 int32 tupTypmod = PG_GETARG_INT32(2);
78 HeapTupleHeader result;
81 RecordIOData *my_extra;
82 bool needComma = false;
90 check_stack_depth(); /* recurses for record-type columns */
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.
100 if (tupType == RECORDOID && tupTypmod < 0)
102 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
103 errmsg("input of anonymous composite types is not implemented")));
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.
110 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
111 ncolumns = tupdesc->natts;
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.
117 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
118 if (my_extra == NULL ||
119 my_extra->ncolumns != ncolumns)
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;
130 if (my_extra->record_type != tupType ||
131 my_extra->record_typmod != tupTypmod)
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;
141 values = (Datum *) palloc(ncolumns * sizeof(Datum));
142 nulls = (bool *) palloc(ncolumns * sizeof(bool));
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.
149 /* Allow leading whitespace */
150 while (*ptr && isspace((unsigned char) *ptr))
154 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
155 errmsg("malformed record literal: \"%s\"", string),
156 errdetail("Missing left parenthesis.")));
158 initStringInfo(&buf);
160 for (i = 0; i < ncolumns; i++)
162 Form_pg_attribute att = TupleDescAttr(tupdesc, i);
163 ColumnIOData *column_info = &my_extra->columns[i];
164 Oid column_type = att->atttypid;
167 /* Ignore dropped columns in datatype, but fill with nulls */
168 if (att->attisdropped)
170 values[i] = (Datum) 0;
177 /* Skip comma that separates prior field from this one */
181 /* *ptr must be ')' */
183 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
184 errmsg("malformed record literal: \"%s\"", string),
185 errdetail("Too few columns.")));
188 /* Check for null: completely empty input means null */
189 if (*ptr == ',' || *ptr == ')')
196 /* Extract string for this column */
197 bool inquote = false;
199 resetStringInfo(&buf);
200 while (inquote || !(*ptr == ',' || *ptr == ')'))
206 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
207 errmsg("malformed record literal: \"%s\"",
209 errdetail("Unexpected end of input.")));
214 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
215 errmsg("malformed record literal: \"%s\"",
217 errdetail("Unexpected end of input.")));
218 appendStringInfoChar(&buf, *ptr++);
224 else if (*ptr == '"')
226 /* doubled quote within quote sequence */
227 appendStringInfoChar(&buf, *ptr++);
233 appendStringInfoChar(&buf, ch);
236 column_data = buf.data;
241 * Convert the column value
243 if (column_info->column_type != column_type)
245 getTypeInputInfo(column_type,
246 &column_info->typiofunc,
247 &column_info->typioparam);
248 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
249 fcinfo->flinfo->fn_mcxt);
250 column_info->column_type = column_type;
253 values[i] = InputFunctionCall(&column_info->proc,
255 column_info->typioparam,
259 * Prep for next column
266 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
267 errmsg("malformed record literal: \"%s\"", string),
268 errdetail("Too many columns.")));
269 /* Allow trailing whitespace */
270 while (*ptr && isspace((unsigned char) *ptr))
274 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
275 errmsg("malformed record literal: \"%s\"", string),
276 errdetail("Junk after right parenthesis.")));
278 tuple = heap_form_tuple(tupdesc, values, nulls);
281 * We cannot return tuple->t_data because heap_form_tuple allocates it as
282 * part of a larger chunk, and our caller may expect to be able to pfree
283 * our result. So must copy the info into a new palloc chunk.
285 result = (HeapTupleHeader) palloc(tuple->t_len);
286 memcpy(result, tuple->t_data, tuple->t_len);
288 heap_freetuple(tuple);
292 ReleaseTupleDesc(tupdesc);
294 PG_RETURN_HEAPTUPLEHEADER(result);
298 * record_out - output routine for any composite type.
301 record_out(PG_FUNCTION_ARGS)
303 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
308 RecordIOData *my_extra;
309 bool needComma = false;
316 check_stack_depth(); /* recurses for record-type columns */
318 /* Extract type info from the tuple itself */
319 tupType = HeapTupleHeaderGetTypeId(rec);
320 tupTypmod = HeapTupleHeaderGetTypMod(rec);
321 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
322 ncolumns = tupdesc->natts;
324 /* Build a temporary HeapTuple control structure */
325 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
326 ItemPointerSetInvalid(&(tuple.t_self));
327 tuple.t_tableOid = InvalidOid;
331 * We arrange to look up the needed I/O info just once per series of
332 * calls, assuming the record type doesn't change underneath us.
334 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
335 if (my_extra == NULL ||
336 my_extra->ncolumns != ncolumns)
338 fcinfo->flinfo->fn_extra =
339 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
340 offsetof(RecordIOData, columns) +
341 ncolumns * sizeof(ColumnIOData));
342 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
343 my_extra->record_type = InvalidOid;
344 my_extra->record_typmod = 0;
347 if (my_extra->record_type != tupType ||
348 my_extra->record_typmod != tupTypmod)
351 offsetof(RecordIOData, columns) +
352 ncolumns * sizeof(ColumnIOData));
353 my_extra->record_type = tupType;
354 my_extra->record_typmod = tupTypmod;
355 my_extra->ncolumns = ncolumns;
358 values = (Datum *) palloc(ncolumns * sizeof(Datum));
359 nulls = (bool *) palloc(ncolumns * sizeof(bool));
361 /* Break down the tuple into fields */
362 heap_deform_tuple(&tuple, tupdesc, values, nulls);
364 /* And build the result string */
365 initStringInfo(&buf);
367 appendStringInfoChar(&buf, '(');
369 for (i = 0; i < ncolumns; i++)
371 Form_pg_attribute att = TupleDescAttr(tupdesc, i);
372 ColumnIOData *column_info = &my_extra->columns[i];
373 Oid column_type = att->atttypid;
379 /* Ignore dropped columns in datatype */
380 if (att->attisdropped)
384 appendStringInfoChar(&buf, ',');
389 /* emit nothing... */
394 * Convert the column value to text
396 if (column_info->column_type != column_type)
398 getTypeOutputInfo(column_type,
399 &column_info->typiofunc,
400 &column_info->typisvarlena);
401 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
402 fcinfo->flinfo->fn_mcxt);
403 column_info->column_type = column_type;
407 value = OutputFunctionCall(&column_info->proc, attr);
409 /* Detect whether we need double quotes for this value */
410 nq = (value[0] == '\0'); /* force quotes for empty string */
411 for (tmp = value; *tmp; tmp++)
415 if (ch == '"' || ch == '\\' ||
416 ch == '(' || ch == ')' || ch == ',' ||
417 isspace((unsigned char) ch))
424 /* And emit the string */
426 appendStringInfoCharMacro(&buf, '"');
427 for (tmp = value; *tmp; tmp++)
431 if (ch == '"' || ch == '\\')
432 appendStringInfoCharMacro(&buf, ch);
433 appendStringInfoCharMacro(&buf, ch);
436 appendStringInfoCharMacro(&buf, '"');
439 appendStringInfoChar(&buf, ')');
443 ReleaseTupleDesc(tupdesc);
445 PG_RETURN_CSTRING(buf.data);
449 * record_recv - binary input routine for any composite type.
452 record_recv(PG_FUNCTION_ARGS)
454 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
455 Oid tupType = PG_GETARG_OID(1);
456 int32 tupTypmod = PG_GETARG_INT32(2);
457 HeapTupleHeader result;
460 RecordIOData *my_extra;
468 check_stack_depth(); /* recurses for record-type columns */
471 * Give a friendly error message if we did not get enough info to identify
472 * the target record type. (lookup_rowtype_tupdesc would fail anyway, but
473 * with a non-user-friendly message.) In ordinary SQL usage, we'll get -1
474 * for typmod, since composite types and RECORD have no type modifiers at
475 * the SQL level, and thus must fail for RECORD. However some callers can
476 * supply a valid typmod, and then we can do something useful for RECORD.
478 if (tupType == RECORDOID && tupTypmod < 0)
480 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
481 errmsg("input of anonymous composite types is not implemented")));
483 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
484 ncolumns = tupdesc->natts;
487 * We arrange to look up the needed I/O info just once per series of
488 * calls, assuming the record type doesn't change underneath us.
490 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
491 if (my_extra == NULL ||
492 my_extra->ncolumns != ncolumns)
494 fcinfo->flinfo->fn_extra =
495 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
496 offsetof(RecordIOData, columns) +
497 ncolumns * sizeof(ColumnIOData));
498 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
499 my_extra->record_type = InvalidOid;
500 my_extra->record_typmod = 0;
503 if (my_extra->record_type != tupType ||
504 my_extra->record_typmod != tupTypmod)
507 offsetof(RecordIOData, columns) +
508 ncolumns * sizeof(ColumnIOData));
509 my_extra->record_type = tupType;
510 my_extra->record_typmod = tupTypmod;
511 my_extra->ncolumns = ncolumns;
514 values = (Datum *) palloc(ncolumns * sizeof(Datum));
515 nulls = (bool *) palloc(ncolumns * sizeof(bool));
517 /* Fetch number of columns user thinks it has */
518 usercols = pq_getmsgint(buf, 4);
520 /* Need to scan to count nondeleted columns */
522 for (i = 0; i < ncolumns; i++)
524 if (!TupleDescAttr(tupdesc, i)->attisdropped)
527 if (usercols != validcols)
529 (errcode(ERRCODE_DATATYPE_MISMATCH),
530 errmsg("wrong number of columns: %d, expected %d",
531 usercols, validcols)));
533 /* Process each column */
534 for (i = 0; i < ncolumns; i++)
536 Form_pg_attribute att = TupleDescAttr(tupdesc, i);
537 ColumnIOData *column_info = &my_extra->columns[i];
538 Oid column_type = att->atttypid;
541 StringInfoData item_buf;
545 /* Ignore dropped columns in datatype, but fill with nulls */
546 if (att->attisdropped)
548 values[i] = (Datum) 0;
553 /* Verify column datatype */
554 coltypoid = pq_getmsgint(buf, sizeof(Oid));
555 if (coltypoid != column_type)
557 (errcode(ERRCODE_DATATYPE_MISMATCH),
558 errmsg("wrong data type: %u, expected %u",
559 coltypoid, column_type)));
561 /* Get and check the item length */
562 itemlen = pq_getmsgint(buf, 4);
563 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
565 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
566 errmsg("insufficient data left in message")));
570 /* -1 length means NULL */
573 csave = 0; /* keep compiler quiet */
578 * Rather than copying data around, we just set up a phony
579 * StringInfo pointing to the correct portion of the input buffer.
580 * We assume we can scribble on the input buffer so as to maintain
581 * the convention that StringInfos have a trailing null.
583 item_buf.data = &buf->data[buf->cursor];
584 item_buf.maxlen = itemlen + 1;
585 item_buf.len = itemlen;
588 buf->cursor += itemlen;
590 csave = buf->data[buf->cursor];
591 buf->data[buf->cursor] = '\0';
597 /* Now call the column's receiveproc */
598 if (column_info->column_type != column_type)
600 getTypeBinaryInputInfo(column_type,
601 &column_info->typiofunc,
602 &column_info->typioparam);
603 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
604 fcinfo->flinfo->fn_mcxt);
605 column_info->column_type = column_type;
608 values[i] = ReceiveFunctionCall(&column_info->proc,
610 column_info->typioparam,
615 /* Trouble if it didn't eat the whole buffer */
616 if (item_buf.cursor != itemlen)
618 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
619 errmsg("improper binary format in record column %d",
622 buf->data[buf->cursor] = csave;
626 tuple = heap_form_tuple(tupdesc, values, nulls);
629 * We cannot return tuple->t_data because heap_form_tuple allocates it as
630 * part of a larger chunk, and our caller may expect to be able to pfree
631 * our result. So must copy the info into a new palloc chunk.
633 result = (HeapTupleHeader) palloc(tuple->t_len);
634 memcpy(result, tuple->t_data, tuple->t_len);
636 heap_freetuple(tuple);
639 ReleaseTupleDesc(tupdesc);
641 PG_RETURN_HEAPTUPLEHEADER(result);
645 * record_send - binary output routine for any composite type.
648 record_send(PG_FUNCTION_ARGS)
650 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
655 RecordIOData *my_extra;
663 check_stack_depth(); /* recurses for record-type columns */
665 /* Extract type info from the tuple itself */
666 tupType = HeapTupleHeaderGetTypeId(rec);
667 tupTypmod = HeapTupleHeaderGetTypMod(rec);
668 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
669 ncolumns = tupdesc->natts;
671 /* Build a temporary HeapTuple control structure */
672 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
673 ItemPointerSetInvalid(&(tuple.t_self));
674 tuple.t_tableOid = InvalidOid;
678 * We arrange to look up the needed I/O info just once per series of
679 * calls, assuming the record type doesn't change underneath us.
681 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
682 if (my_extra == NULL ||
683 my_extra->ncolumns != ncolumns)
685 fcinfo->flinfo->fn_extra =
686 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
687 offsetof(RecordIOData, columns) +
688 ncolumns * sizeof(ColumnIOData));
689 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
690 my_extra->record_type = InvalidOid;
691 my_extra->record_typmod = 0;
694 if (my_extra->record_type != tupType ||
695 my_extra->record_typmod != tupTypmod)
698 offsetof(RecordIOData, columns) +
699 ncolumns * sizeof(ColumnIOData));
700 my_extra->record_type = tupType;
701 my_extra->record_typmod = tupTypmod;
702 my_extra->ncolumns = ncolumns;
705 values = (Datum *) palloc(ncolumns * sizeof(Datum));
706 nulls = (bool *) palloc(ncolumns * sizeof(bool));
708 /* Break down the tuple into fields */
709 heap_deform_tuple(&tuple, tupdesc, values, nulls);
711 /* And build the result string */
712 pq_begintypsend(&buf);
714 /* Need to scan to count nondeleted columns */
716 for (i = 0; i < ncolumns; i++)
718 if (!TupleDescAttr(tupdesc, i)->attisdropped)
721 pq_sendint32(&buf, validcols);
723 for (i = 0; i < ncolumns; i++)
725 Form_pg_attribute att = TupleDescAttr(tupdesc, i);
726 ColumnIOData *column_info = &my_extra->columns[i];
727 Oid column_type = att->atttypid;
731 /* Ignore dropped columns in datatype */
732 if (att->attisdropped)
735 pq_sendint32(&buf, column_type);
739 /* emit -1 data length to signify a NULL */
740 pq_sendint32(&buf, -1);
745 * Convert the column value to binary
747 if (column_info->column_type != column_type)
749 getTypeBinaryOutputInfo(column_type,
750 &column_info->typiofunc,
751 &column_info->typisvarlena);
752 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
753 fcinfo->flinfo->fn_mcxt);
754 column_info->column_type = column_type;
758 outputbytes = SendFunctionCall(&column_info->proc, attr);
759 pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ);
760 pq_sendbytes(&buf, VARDATA(outputbytes),
761 VARSIZE(outputbytes) - VARHDRSZ);
766 ReleaseTupleDesc(tupdesc);
768 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
774 * Internal comparison function for records.
778 * Do not assume that the two inputs are exactly the same record type;
779 * for instance we might be comparing an anonymous ROW() construct against a
780 * named composite type. We will compare as long as they have the same number
781 * of non-dropped columns of the same types.
784 record_cmp(FunctionCallInfo fcinfo)
786 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
787 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
795 HeapTupleData tuple1;
796 HeapTupleData tuple2;
799 RecordCompareData *my_extra;
809 check_stack_depth(); /* recurses for record-type columns */
811 /* Extract type info from the tuples */
812 tupType1 = HeapTupleHeaderGetTypeId(record1);
813 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
814 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
815 ncolumns1 = tupdesc1->natts;
816 tupType2 = HeapTupleHeaderGetTypeId(record2);
817 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
818 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
819 ncolumns2 = tupdesc2->natts;
821 /* Build temporary HeapTuple control structures */
822 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
823 ItemPointerSetInvalid(&(tuple1.t_self));
824 tuple1.t_tableOid = InvalidOid;
825 tuple1.t_data = record1;
826 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
827 ItemPointerSetInvalid(&(tuple2.t_self));
828 tuple2.t_tableOid = InvalidOid;
829 tuple2.t_data = record2;
832 * We arrange to look up the needed comparison info just once per series
833 * of calls, assuming the record types don't change underneath us.
835 ncols = Max(ncolumns1, ncolumns2);
836 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
837 if (my_extra == NULL ||
838 my_extra->ncolumns < ncols)
840 fcinfo->flinfo->fn_extra =
841 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
842 offsetof(RecordCompareData, columns) +
843 ncols * sizeof(ColumnCompareData));
844 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
845 my_extra->ncolumns = ncols;
846 my_extra->record1_type = InvalidOid;
847 my_extra->record1_typmod = 0;
848 my_extra->record2_type = InvalidOid;
849 my_extra->record2_typmod = 0;
852 if (my_extra->record1_type != tupType1 ||
853 my_extra->record1_typmod != tupTypmod1 ||
854 my_extra->record2_type != tupType2 ||
855 my_extra->record2_typmod != tupTypmod2)
857 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
858 my_extra->record1_type = tupType1;
859 my_extra->record1_typmod = tupTypmod1;
860 my_extra->record2_type = tupType2;
861 my_extra->record2_typmod = tupTypmod2;
864 /* Break down the tuples into fields */
865 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
866 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
867 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
868 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
869 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
870 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
873 * Scan corresponding columns, allowing for dropped columns in different
874 * places in the two rows. i1 and i2 are physical column indexes, j is
875 * the logical column index.
878 while (i1 < ncolumns1 || i2 < ncolumns2)
880 Form_pg_attribute att1;
881 Form_pg_attribute att2;
882 TypeCacheEntry *typentry;
886 * Skip dropped columns
888 if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
893 if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
898 if (i1 >= ncolumns1 || i2 >= ncolumns2)
899 break; /* we'll deal with mismatch below loop */
901 att1 = TupleDescAttr(tupdesc1, i1);
902 att2 = TupleDescAttr(tupdesc2, i2);
905 * Have two matching columns, they must be same type
907 if (att1->atttypid != att2->atttypid)
909 (errcode(ERRCODE_DATATYPE_MISMATCH),
910 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
911 format_type_be(att1->atttypid),
912 format_type_be(att2->atttypid),
916 * If they're not same collation, we don't complain here, but the
917 * comparison function might.
919 collation = att1->attcollation;
920 if (collation != att2->attcollation)
921 collation = InvalidOid;
924 * Lookup the comparison function if not done already
926 typentry = my_extra->columns[j].typentry;
927 if (typentry == NULL ||
928 typentry->type_id != att1->atttypid)
930 typentry = lookup_type_cache(att1->atttypid,
931 TYPECACHE_CMP_PROC_FINFO);
932 if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
934 (errcode(ERRCODE_UNDEFINED_FUNCTION),
935 errmsg("could not identify a comparison function for type %s",
936 format_type_be(typentry->type_id))));
937 my_extra->columns[j].typentry = typentry;
941 * We consider two NULLs equal; NULL > not-NULL.
943 if (!nulls1[i1] || !nulls2[i2])
945 FunctionCallInfoData locfcinfo;
950 /* arg1 is greater than arg2 */
956 /* arg1 is less than arg2 */
961 /* Compare the pair of elements */
962 InitFunctionCallInfoData(locfcinfo, &typentry->cmp_proc_finfo, 2,
963 collation, NULL, NULL);
964 locfcinfo.arg[0] = values1[i1];
965 locfcinfo.arg[1] = values2[i2];
966 locfcinfo.argnull[0] = false;
967 locfcinfo.argnull[1] = false;
968 locfcinfo.isnull = false;
969 cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
973 /* arg1 is less than arg2 */
977 else if (cmpresult > 0)
979 /* arg1 is greater than arg2 */
985 /* equal, so continue to next column */
990 * If we didn't break out of the loop early, check for column count
991 * mismatch. (We do not report such mismatch if we found unequal column
992 * values; is that a feature or a bug?)
996 if (i1 != ncolumns1 || i2 != ncolumns2)
998 (errcode(ERRCODE_DATATYPE_MISMATCH),
999 errmsg("cannot compare record types with different numbers of columns")));
1006 ReleaseTupleDesc(tupdesc1);
1007 ReleaseTupleDesc(tupdesc2);
1009 /* Avoid leaking memory when handed toasted input. */
1010 PG_FREE_IF_COPY(record1, 0);
1011 PG_FREE_IF_COPY(record2, 1);
1018 * compares two records for equality
1020 * returns true if the records are equal, false otherwise.
1022 * Note: we do not use record_cmp here, since equality may be meaningful in
1023 * datatypes that don't have a total ordering (and hence no btree support).
1026 record_eq(PG_FUNCTION_ARGS)
1028 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1029 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1037 HeapTupleData tuple1;
1038 HeapTupleData tuple2;
1041 RecordCompareData *my_extra;
1051 check_stack_depth(); /* recurses for record-type columns */
1053 /* Extract type info from the tuples */
1054 tupType1 = HeapTupleHeaderGetTypeId(record1);
1055 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1056 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1057 ncolumns1 = tupdesc1->natts;
1058 tupType2 = HeapTupleHeaderGetTypeId(record2);
1059 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1060 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1061 ncolumns2 = tupdesc2->natts;
1063 /* Build temporary HeapTuple control structures */
1064 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1065 ItemPointerSetInvalid(&(tuple1.t_self));
1066 tuple1.t_tableOid = InvalidOid;
1067 tuple1.t_data = record1;
1068 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1069 ItemPointerSetInvalid(&(tuple2.t_self));
1070 tuple2.t_tableOid = InvalidOid;
1071 tuple2.t_data = record2;
1074 * We arrange to look up the needed comparison info just once per series
1075 * of calls, assuming the record types don't change underneath us.
1077 ncols = Max(ncolumns1, ncolumns2);
1078 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1079 if (my_extra == NULL ||
1080 my_extra->ncolumns < ncols)
1082 fcinfo->flinfo->fn_extra =
1083 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1084 offsetof(RecordCompareData, columns) +
1085 ncols * sizeof(ColumnCompareData));
1086 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1087 my_extra->ncolumns = ncols;
1088 my_extra->record1_type = InvalidOid;
1089 my_extra->record1_typmod = 0;
1090 my_extra->record2_type = InvalidOid;
1091 my_extra->record2_typmod = 0;
1094 if (my_extra->record1_type != tupType1 ||
1095 my_extra->record1_typmod != tupTypmod1 ||
1096 my_extra->record2_type != tupType2 ||
1097 my_extra->record2_typmod != tupTypmod2)
1099 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1100 my_extra->record1_type = tupType1;
1101 my_extra->record1_typmod = tupTypmod1;
1102 my_extra->record2_type = tupType2;
1103 my_extra->record2_typmod = tupTypmod2;
1106 /* Break down the tuples into fields */
1107 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1108 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1109 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1110 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1111 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1112 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1115 * Scan corresponding columns, allowing for dropped columns in different
1116 * places in the two rows. i1 and i2 are physical column indexes, j is
1117 * the logical column index.
1120 while (i1 < ncolumns1 || i2 < ncolumns2)
1122 Form_pg_attribute att1;
1123 Form_pg_attribute att2;
1124 TypeCacheEntry *typentry;
1126 FunctionCallInfoData locfcinfo;
1130 * Skip dropped columns
1132 if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1137 if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1142 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1143 break; /* we'll deal with mismatch below loop */
1145 att1 = TupleDescAttr(tupdesc1, i1);
1146 att2 = TupleDescAttr(tupdesc2, i2);
1149 * Have two matching columns, they must be same type
1151 if (att1->atttypid != att2->atttypid)
1153 (errcode(ERRCODE_DATATYPE_MISMATCH),
1154 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1155 format_type_be(att1->atttypid),
1156 format_type_be(att2->atttypid),
1160 * If they're not same collation, we don't complain here, but the
1161 * equality function might.
1163 collation = att1->attcollation;
1164 if (collation != att2->attcollation)
1165 collation = InvalidOid;
1168 * Lookup the equality function if not done already
1170 typentry = my_extra->columns[j].typentry;
1171 if (typentry == NULL ||
1172 typentry->type_id != att1->atttypid)
1174 typentry = lookup_type_cache(att1->atttypid,
1175 TYPECACHE_EQ_OPR_FINFO);
1176 if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1178 (errcode(ERRCODE_UNDEFINED_FUNCTION),
1179 errmsg("could not identify an equality operator for type %s",
1180 format_type_be(typentry->type_id))));
1181 my_extra->columns[j].typentry = typentry;
1185 * We consider two NULLs equal; NULL > not-NULL.
1187 if (!nulls1[i1] || !nulls2[i2])
1189 if (nulls1[i1] || nulls2[i2])
1195 /* Compare the pair of elements */
1196 InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
1197 collation, NULL, NULL);
1198 locfcinfo.arg[0] = values1[i1];
1199 locfcinfo.arg[1] = values2[i2];
1200 locfcinfo.argnull[0] = false;
1201 locfcinfo.argnull[1] = false;
1202 locfcinfo.isnull = false;
1203 oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
1211 /* equal, so continue to next column */
1216 * If we didn't break out of the loop early, check for column count
1217 * mismatch. (We do not report such mismatch if we found unequal column
1218 * values; is that a feature or a bug?)
1222 if (i1 != ncolumns1 || i2 != ncolumns2)
1224 (errcode(ERRCODE_DATATYPE_MISMATCH),
1225 errmsg("cannot compare record types with different numbers of columns")));
1232 ReleaseTupleDesc(tupdesc1);
1233 ReleaseTupleDesc(tupdesc2);
1235 /* Avoid leaking memory when handed toasted input. */
1236 PG_FREE_IF_COPY(record1, 0);
1237 PG_FREE_IF_COPY(record2, 1);
1239 PG_RETURN_BOOL(result);
1243 record_ne(PG_FUNCTION_ARGS)
1245 PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
1249 record_lt(PG_FUNCTION_ARGS)
1251 PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
1255 record_gt(PG_FUNCTION_ARGS)
1257 PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
1261 record_le(PG_FUNCTION_ARGS)
1263 PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
1267 record_ge(PG_FUNCTION_ARGS)
1269 PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
1273 btrecordcmp(PG_FUNCTION_ARGS)
1275 PG_RETURN_INT32(record_cmp(fcinfo));
1280 * record_image_cmp :
1281 * Internal byte-oriented comparison function for records.
1283 * Returns -1, 0 or 1
1285 * Note: The normal concepts of "equality" do not apply here; different
1286 * representation of values considered to be equal are not considered to be
1287 * identical. As an example, for the citext type 'A' and 'a' are equal, but
1288 * they are not identical.
1291 record_image_cmp(FunctionCallInfo fcinfo)
1293 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1294 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1302 HeapTupleData tuple1;
1303 HeapTupleData tuple2;
1306 RecordCompareData *my_extra;
1316 /* Extract type info from the tuples */
1317 tupType1 = HeapTupleHeaderGetTypeId(record1);
1318 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1319 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1320 ncolumns1 = tupdesc1->natts;
1321 tupType2 = HeapTupleHeaderGetTypeId(record2);
1322 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1323 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1324 ncolumns2 = tupdesc2->natts;
1326 /* Build temporary HeapTuple control structures */
1327 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1328 ItemPointerSetInvalid(&(tuple1.t_self));
1329 tuple1.t_tableOid = InvalidOid;
1330 tuple1.t_data = record1;
1331 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1332 ItemPointerSetInvalid(&(tuple2.t_self));
1333 tuple2.t_tableOid = InvalidOid;
1334 tuple2.t_data = record2;
1337 * We arrange to look up the needed comparison info just once per series
1338 * of calls, assuming the record types don't change underneath us.
1340 ncols = Max(ncolumns1, ncolumns2);
1341 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1342 if (my_extra == NULL ||
1343 my_extra->ncolumns < ncols)
1345 fcinfo->flinfo->fn_extra =
1346 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1347 offsetof(RecordCompareData, columns) +
1348 ncols * sizeof(ColumnCompareData));
1349 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1350 my_extra->ncolumns = ncols;
1351 my_extra->record1_type = InvalidOid;
1352 my_extra->record1_typmod = 0;
1353 my_extra->record2_type = InvalidOid;
1354 my_extra->record2_typmod = 0;
1357 if (my_extra->record1_type != tupType1 ||
1358 my_extra->record1_typmod != tupTypmod1 ||
1359 my_extra->record2_type != tupType2 ||
1360 my_extra->record2_typmod != tupTypmod2)
1362 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1363 my_extra->record1_type = tupType1;
1364 my_extra->record1_typmod = tupTypmod1;
1365 my_extra->record2_type = tupType2;
1366 my_extra->record2_typmod = tupTypmod2;
1369 /* Break down the tuples into fields */
1370 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1371 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1372 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1373 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1374 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1375 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1378 * Scan corresponding columns, allowing for dropped columns in different
1379 * places in the two rows. i1 and i2 are physical column indexes, j is
1380 * the logical column index.
1383 while (i1 < ncolumns1 || i2 < ncolumns2)
1385 Form_pg_attribute att1;
1386 Form_pg_attribute att2;
1389 * Skip dropped columns
1391 if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1396 if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1401 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1402 break; /* we'll deal with mismatch below loop */
1404 att1 = TupleDescAttr(tupdesc1, i1);
1405 att2 = TupleDescAttr(tupdesc2, i2);
1408 * Have two matching columns, they must be same type
1410 if (att1->atttypid != att2->atttypid)
1412 (errcode(ERRCODE_DATATYPE_MISMATCH),
1413 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1414 format_type_be(att1->atttypid),
1415 format_type_be(att2->atttypid),
1419 * The same type should have the same length (or both should be
1422 Assert(att1->attlen == att2->attlen);
1425 * We consider two NULLs equal; NULL > not-NULL.
1427 if (!nulls1[i1] || !nulls2[i2])
1433 /* arg1 is greater than arg2 */
1439 /* arg1 is less than arg2 */
1444 /* Compare the pair of elements */
1445 if (att1->attlen == -1)
1449 struct varlena *arg1val;
1450 struct varlena *arg2val;
1452 len1 = toast_raw_datum_size(values1[i1]);
1453 len2 = toast_raw_datum_size(values2[i2]);
1454 arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
1455 arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
1457 cmpresult = memcmp(VARDATA_ANY(arg1val),
1458 VARDATA_ANY(arg2val),
1459 Min(len1, len2) - VARHDRSZ);
1460 if ((cmpresult == 0) && (len1 != len2))
1461 cmpresult = (len1 < len2) ? -1 : 1;
1463 if ((Pointer) arg1val != (Pointer) values1[i1])
1465 if ((Pointer) arg2val != (Pointer) values2[i2])
1468 else if (att1->attbyval)
1470 switch (att1->attlen)
1473 if (GET_1_BYTE(values1[i1]) !=
1474 GET_1_BYTE(values2[i2]))
1476 cmpresult = (GET_1_BYTE(values1[i1]) <
1477 GET_1_BYTE(values2[i2])) ? -1 : 1;
1481 if (GET_2_BYTES(values1[i1]) !=
1482 GET_2_BYTES(values2[i2]))
1484 cmpresult = (GET_2_BYTES(values1[i1]) <
1485 GET_2_BYTES(values2[i2])) ? -1 : 1;
1489 if (GET_4_BYTES(values1[i1]) !=
1490 GET_4_BYTES(values2[i2]))
1492 cmpresult = (GET_4_BYTES(values1[i1]) <
1493 GET_4_BYTES(values2[i2])) ? -1 : 1;
1496 #if SIZEOF_DATUM == 8
1498 if (GET_8_BYTES(values1[i1]) !=
1499 GET_8_BYTES(values2[i2]))
1501 cmpresult = (GET_8_BYTES(values1[i1]) <
1502 GET_8_BYTES(values2[i2])) ? -1 : 1;
1507 Assert(false); /* cannot happen */
1512 cmpresult = memcmp(DatumGetPointer(values1[i1]),
1513 DatumGetPointer(values2[i2]),
1519 /* arg1 is less than arg2 */
1523 else if (cmpresult > 0)
1525 /* arg1 is greater than arg2 */
1531 /* equal, so continue to next column */
1536 * If we didn't break out of the loop early, check for column count
1537 * mismatch. (We do not report such mismatch if we found unequal column
1538 * values; is that a feature or a bug?)
1542 if (i1 != ncolumns1 || i2 != ncolumns2)
1544 (errcode(ERRCODE_DATATYPE_MISMATCH),
1545 errmsg("cannot compare record types with different numbers of columns")));
1552 ReleaseTupleDesc(tupdesc1);
1553 ReleaseTupleDesc(tupdesc2);
1555 /* Avoid leaking memory when handed toasted input. */
1556 PG_FREE_IF_COPY(record1, 0);
1557 PG_FREE_IF_COPY(record2, 1);
1564 * compares two records for identical contents, based on byte images
1566 * returns true if the records are identical, false otherwise.
1568 * Note: we do not use record_image_cmp here, since we can avoid
1569 * de-toasting for unequal lengths this way.
1572 record_image_eq(PG_FUNCTION_ARGS)
1574 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1575 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1583 HeapTupleData tuple1;
1584 HeapTupleData tuple2;
1587 RecordCompareData *my_extra;
1597 /* Extract type info from the tuples */
1598 tupType1 = HeapTupleHeaderGetTypeId(record1);
1599 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1600 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1601 ncolumns1 = tupdesc1->natts;
1602 tupType2 = HeapTupleHeaderGetTypeId(record2);
1603 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1604 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1605 ncolumns2 = tupdesc2->natts;
1607 /* Build temporary HeapTuple control structures */
1608 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1609 ItemPointerSetInvalid(&(tuple1.t_self));
1610 tuple1.t_tableOid = InvalidOid;
1611 tuple1.t_data = record1;
1612 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1613 ItemPointerSetInvalid(&(tuple2.t_self));
1614 tuple2.t_tableOid = InvalidOid;
1615 tuple2.t_data = record2;
1618 * We arrange to look up the needed comparison info just once per series
1619 * of calls, assuming the record types don't change underneath us.
1621 ncols = Max(ncolumns1, ncolumns2);
1622 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1623 if (my_extra == NULL ||
1624 my_extra->ncolumns < ncols)
1626 fcinfo->flinfo->fn_extra =
1627 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1628 offsetof(RecordCompareData, columns) +
1629 ncols * sizeof(ColumnCompareData));
1630 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1631 my_extra->ncolumns = ncols;
1632 my_extra->record1_type = InvalidOid;
1633 my_extra->record1_typmod = 0;
1634 my_extra->record2_type = InvalidOid;
1635 my_extra->record2_typmod = 0;
1638 if (my_extra->record1_type != tupType1 ||
1639 my_extra->record1_typmod != tupTypmod1 ||
1640 my_extra->record2_type != tupType2 ||
1641 my_extra->record2_typmod != tupTypmod2)
1643 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1644 my_extra->record1_type = tupType1;
1645 my_extra->record1_typmod = tupTypmod1;
1646 my_extra->record2_type = tupType2;
1647 my_extra->record2_typmod = tupTypmod2;
1650 /* Break down the tuples into fields */
1651 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1652 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1653 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1654 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1655 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1656 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1659 * Scan corresponding columns, allowing for dropped columns in different
1660 * places in the two rows. i1 and i2 are physical column indexes, j is
1661 * the logical column index.
1664 while (i1 < ncolumns1 || i2 < ncolumns2)
1666 Form_pg_attribute att1;
1667 Form_pg_attribute att2;
1670 * Skip dropped columns
1672 if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1677 if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1682 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1683 break; /* we'll deal with mismatch below loop */
1685 att1 = TupleDescAttr(tupdesc1, i1);
1686 att2 = TupleDescAttr(tupdesc2, i2);
1689 * Have two matching columns, they must be same type
1691 if (att1->atttypid != att2->atttypid)
1693 (errcode(ERRCODE_DATATYPE_MISMATCH),
1694 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1695 format_type_be(att1->atttypid),
1696 format_type_be(att2->atttypid),
1700 * We consider two NULLs equal; NULL > not-NULL.
1702 if (!nulls1[i1] || !nulls2[i2])
1704 if (nulls1[i1] || nulls2[i2])
1710 /* Compare the pair of elements */
1711 if (att1->attlen == -1)
1716 len1 = toast_raw_datum_size(values1[i1]);
1717 len2 = toast_raw_datum_size(values2[i2]);
1718 /* No need to de-toast if lengths don't match. */
1723 struct varlena *arg1val;
1724 struct varlena *arg2val;
1726 arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
1727 arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
1729 result = (memcmp(VARDATA_ANY(arg1val),
1730 VARDATA_ANY(arg2val),
1731 len1 - VARHDRSZ) == 0);
1733 /* Only free memory if it's a copy made here. */
1734 if ((Pointer) arg1val != (Pointer) values1[i1])
1736 if ((Pointer) arg2val != (Pointer) values2[i2])
1740 else if (att1->attbyval)
1742 switch (att1->attlen)
1745 result = (GET_1_BYTE(values1[i1]) ==
1746 GET_1_BYTE(values2[i2]));
1749 result = (GET_2_BYTES(values1[i1]) ==
1750 GET_2_BYTES(values2[i2]));
1753 result = (GET_4_BYTES(values1[i1]) ==
1754 GET_4_BYTES(values2[i2]));
1756 #if SIZEOF_DATUM == 8
1758 result = (GET_8_BYTES(values1[i1]) ==
1759 GET_8_BYTES(values2[i2]));
1763 Assert(false); /* cannot happen */
1768 result = (memcmp(DatumGetPointer(values1[i1]),
1769 DatumGetPointer(values2[i2]),
1770 att1->attlen) == 0);
1776 /* equal, so continue to next column */
1781 * If we didn't break out of the loop early, check for column count
1782 * mismatch. (We do not report such mismatch if we found unequal column
1783 * values; is that a feature or a bug?)
1787 if (i1 != ncolumns1 || i2 != ncolumns2)
1789 (errcode(ERRCODE_DATATYPE_MISMATCH),
1790 errmsg("cannot compare record types with different numbers of columns")));
1797 ReleaseTupleDesc(tupdesc1);
1798 ReleaseTupleDesc(tupdesc2);
1800 /* Avoid leaking memory when handed toasted input. */
1801 PG_FREE_IF_COPY(record1, 0);
1802 PG_FREE_IF_COPY(record2, 1);
1804 PG_RETURN_BOOL(result);
1808 record_image_ne(PG_FUNCTION_ARGS)
1810 PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo)));
1814 record_image_lt(PG_FUNCTION_ARGS)
1816 PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0);
1820 record_image_gt(PG_FUNCTION_ARGS)
1822 PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0);
1826 record_image_le(PG_FUNCTION_ARGS)
1828 PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0);
1832 record_image_ge(PG_FUNCTION_ARGS)
1834 PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0);
1838 btrecordimagecmp(PG_FUNCTION_ARGS)
1840 PG_RETURN_INT32(record_image_cmp(fcinfo));