1 /*-------------------------------------------------------------------------
4 * I/O and comparison functions for generic composite types.
6 * Portions Copyright (c) 1996-2015, 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 "utils/builtins.h"
25 #include "utils/lsyscache.h"
26 #include "utils/typcache.h"
30 * structure to cache metadata needed for record I/O
32 typedef struct ColumnIOData
41 typedef struct RecordIOData
46 ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
50 * structure to cache metadata needed for record comparison
52 typedef struct ColumnCompareData
54 TypeCacheEntry *typentry; /* has everything we need, actually */
57 typedef struct RecordCompareData
59 int ncolumns; /* allocated length of columns[] */
64 ColumnCompareData columns[FLEXIBLE_ARRAY_MEMBER];
69 * record_in - input routine for any composite type.
72 record_in(PG_FUNCTION_ARGS)
74 char *string = PG_GETARG_CSTRING(0);
75 Oid tupType = PG_GETARG_OID(1);
76 int32 tupTypmod = PG_GETARG_INT32(2);
77 HeapTupleHeader result;
80 RecordIOData *my_extra;
81 bool needComma = false;
90 * Give a friendly error message if we did not get enough info to identify
91 * the target record type. (lookup_rowtype_tupdesc would fail anyway, but
92 * with a non-user-friendly message.) In ordinary SQL usage, we'll get -1
93 * for typmod, since composite types and RECORD have no type modifiers at
94 * the SQL level, and thus must fail for RECORD. However some callers can
95 * supply a valid typmod, and then we can do something useful for RECORD.
97 if (tupType == RECORDOID && tupTypmod < 0)
99 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
100 errmsg("input of anonymous composite types is not implemented")));
103 * This comes from the composite type's pg_type.oid and stores system oids
104 * in user tables, specifically DatumTupleFields. This oid must be
105 * preserved by binary upgrades.
107 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
108 ncolumns = tupdesc->natts;
111 * We arrange to look up the needed I/O info just once per series of
112 * calls, assuming the record type doesn't change underneath us.
114 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
115 if (my_extra == NULL ||
116 my_extra->ncolumns != ncolumns)
118 fcinfo->flinfo->fn_extra =
119 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
120 offsetof(RecordIOData, columns) +
121 ncolumns * sizeof(ColumnIOData));
122 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
123 my_extra->record_type = InvalidOid;
124 my_extra->record_typmod = 0;
127 if (my_extra->record_type != tupType ||
128 my_extra->record_typmod != tupTypmod)
131 offsetof(RecordIOData, columns) +
132 ncolumns * sizeof(ColumnIOData));
133 my_extra->record_type = tupType;
134 my_extra->record_typmod = tupTypmod;
135 my_extra->ncolumns = ncolumns;
138 values = (Datum *) palloc(ncolumns * sizeof(Datum));
139 nulls = (bool *) palloc(ncolumns * sizeof(bool));
142 * Scan the string. We use "buf" to accumulate the de-quoted data for
143 * each column, which is then fed to the appropriate input converter.
146 /* Allow leading whitespace */
147 while (*ptr && isspace((unsigned char) *ptr))
151 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
152 errmsg("malformed record literal: \"%s\"", string),
153 errdetail("Missing left parenthesis.")));
155 initStringInfo(&buf);
157 for (i = 0; i < ncolumns; i++)
159 ColumnIOData *column_info = &my_extra->columns[i];
160 Oid column_type = tupdesc->attrs[i]->atttypid;
163 /* Ignore dropped columns in datatype, but fill with nulls */
164 if (tupdesc->attrs[i]->attisdropped)
166 values[i] = (Datum) 0;
173 /* Skip comma that separates prior field from this one */
177 /* *ptr must be ')' */
179 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
180 errmsg("malformed record literal: \"%s\"", string),
181 errdetail("Too few columns.")));
184 /* Check for null: completely empty input means null */
185 if (*ptr == ',' || *ptr == ')')
192 /* Extract string for this column */
193 bool inquote = false;
195 resetStringInfo(&buf);
196 while (inquote || !(*ptr == ',' || *ptr == ')'))
202 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
203 errmsg("malformed record literal: \"%s\"",
205 errdetail("Unexpected end of input.")));
210 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
211 errmsg("malformed record literal: \"%s\"",
213 errdetail("Unexpected end of input.")));
214 appendStringInfoChar(&buf, *ptr++);
220 else if (*ptr == '\"')
222 /* doubled quote within quote sequence */
223 appendStringInfoChar(&buf, *ptr++);
229 appendStringInfoChar(&buf, ch);
232 column_data = buf.data;
237 * Convert the column value
239 if (column_info->column_type != column_type)
241 getTypeInputInfo(column_type,
242 &column_info->typiofunc,
243 &column_info->typioparam);
244 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
245 fcinfo->flinfo->fn_mcxt);
246 column_info->column_type = column_type;
249 values[i] = InputFunctionCall(&column_info->proc,
251 column_info->typioparam,
252 tupdesc->attrs[i]->atttypmod);
255 * Prep for next column
262 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
263 errmsg("malformed record literal: \"%s\"", string),
264 errdetail("Too many columns.")));
265 /* Allow trailing whitespace */
266 while (*ptr && isspace((unsigned char) *ptr))
270 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
271 errmsg("malformed record literal: \"%s\"", string),
272 errdetail("Junk after right parenthesis.")));
274 tuple = heap_form_tuple(tupdesc, values, nulls);
277 * We cannot return tuple->t_data because heap_form_tuple allocates it as
278 * part of a larger chunk, and our caller may expect to be able to pfree
279 * our result. So must copy the info into a new palloc chunk.
281 result = (HeapTupleHeader) palloc(tuple->t_len);
282 memcpy(result, tuple->t_data, tuple->t_len);
284 heap_freetuple(tuple);
288 ReleaseTupleDesc(tupdesc);
290 PG_RETURN_HEAPTUPLEHEADER(result);
294 * record_out - output routine for any composite type.
297 record_out(PG_FUNCTION_ARGS)
299 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
304 RecordIOData *my_extra;
305 bool needComma = false;
312 /* Extract type info from the tuple itself */
313 tupType = HeapTupleHeaderGetTypeId(rec);
314 tupTypmod = HeapTupleHeaderGetTypMod(rec);
315 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
316 ncolumns = tupdesc->natts;
318 /* Build a temporary HeapTuple control structure */
319 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
320 ItemPointerSetInvalid(&(tuple.t_self));
321 tuple.t_tableOid = InvalidOid;
325 * We arrange to look up the needed I/O info just once per series of
326 * calls, assuming the record type doesn't change underneath us.
328 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
329 if (my_extra == NULL ||
330 my_extra->ncolumns != ncolumns)
332 fcinfo->flinfo->fn_extra =
333 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
334 offsetof(RecordIOData, columns) +
335 ncolumns * sizeof(ColumnIOData));
336 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
337 my_extra->record_type = InvalidOid;
338 my_extra->record_typmod = 0;
341 if (my_extra->record_type != tupType ||
342 my_extra->record_typmod != tupTypmod)
345 offsetof(RecordIOData, columns) +
346 ncolumns * sizeof(ColumnIOData));
347 my_extra->record_type = tupType;
348 my_extra->record_typmod = tupTypmod;
349 my_extra->ncolumns = ncolumns;
352 values = (Datum *) palloc(ncolumns * sizeof(Datum));
353 nulls = (bool *) palloc(ncolumns * sizeof(bool));
355 /* Break down the tuple into fields */
356 heap_deform_tuple(&tuple, tupdesc, values, nulls);
358 /* And build the result string */
359 initStringInfo(&buf);
361 appendStringInfoChar(&buf, '(');
363 for (i = 0; i < ncolumns; i++)
365 ColumnIOData *column_info = &my_extra->columns[i];
366 Oid column_type = tupdesc->attrs[i]->atttypid;
372 /* Ignore dropped columns in datatype */
373 if (tupdesc->attrs[i]->attisdropped)
377 appendStringInfoChar(&buf, ',');
382 /* emit nothing... */
387 * Convert the column value to text
389 if (column_info->column_type != column_type)
391 getTypeOutputInfo(column_type,
392 &column_info->typiofunc,
393 &column_info->typisvarlena);
394 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
395 fcinfo->flinfo->fn_mcxt);
396 column_info->column_type = column_type;
400 value = OutputFunctionCall(&column_info->proc, attr);
402 /* Detect whether we need double quotes for this value */
403 nq = (value[0] == '\0'); /* force quotes for empty string */
404 for (tmp = value; *tmp; tmp++)
408 if (ch == '"' || ch == '\\' ||
409 ch == '(' || ch == ')' || ch == ',' ||
410 isspace((unsigned char) ch))
417 /* And emit the string */
419 appendStringInfoCharMacro(&buf, '"');
420 for (tmp = value; *tmp; tmp++)
424 if (ch == '"' || ch == '\\')
425 appendStringInfoCharMacro(&buf, ch);
426 appendStringInfoCharMacro(&buf, ch);
429 appendStringInfoCharMacro(&buf, '"');
432 appendStringInfoChar(&buf, ')');
436 ReleaseTupleDesc(tupdesc);
438 PG_RETURN_CSTRING(buf.data);
442 * record_recv - binary input routine for any composite type.
445 record_recv(PG_FUNCTION_ARGS)
447 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
448 Oid tupType = PG_GETARG_OID(1);
449 int32 tupTypmod = PG_GETARG_INT32(2);
450 HeapTupleHeader result;
453 RecordIOData *my_extra;
462 * Give a friendly error message if we did not get enough info to identify
463 * the target record type. (lookup_rowtype_tupdesc would fail anyway, but
464 * with a non-user-friendly message.) In ordinary SQL usage, we'll get -1
465 * for typmod, since composite types and RECORD have no type modifiers at
466 * the SQL level, and thus must fail for RECORD. However some callers can
467 * supply a valid typmod, and then we can do something useful for RECORD.
469 if (tupType == RECORDOID && tupTypmod < 0)
471 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
472 errmsg("input of anonymous composite types is not implemented")));
474 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
475 ncolumns = tupdesc->natts;
478 * We arrange to look up the needed I/O info just once per series of
479 * calls, assuming the record type doesn't change underneath us.
481 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
482 if (my_extra == NULL ||
483 my_extra->ncolumns != ncolumns)
485 fcinfo->flinfo->fn_extra =
486 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
487 offsetof(RecordIOData, columns) +
488 ncolumns * sizeof(ColumnIOData));
489 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
490 my_extra->record_type = InvalidOid;
491 my_extra->record_typmod = 0;
494 if (my_extra->record_type != tupType ||
495 my_extra->record_typmod != tupTypmod)
498 offsetof(RecordIOData, columns) +
499 ncolumns * sizeof(ColumnIOData));
500 my_extra->record_type = tupType;
501 my_extra->record_typmod = tupTypmod;
502 my_extra->ncolumns = ncolumns;
505 values = (Datum *) palloc(ncolumns * sizeof(Datum));
506 nulls = (bool *) palloc(ncolumns * sizeof(bool));
508 /* Fetch number of columns user thinks it has */
509 usercols = pq_getmsgint(buf, 4);
511 /* Need to scan to count nondeleted columns */
513 for (i = 0; i < ncolumns; i++)
515 if (!tupdesc->attrs[i]->attisdropped)
518 if (usercols != validcols)
520 (errcode(ERRCODE_DATATYPE_MISMATCH),
521 errmsg("wrong number of columns: %d, expected %d",
522 usercols, validcols)));
524 /* Process each column */
525 for (i = 0; i < ncolumns; i++)
527 ColumnIOData *column_info = &my_extra->columns[i];
528 Oid column_type = tupdesc->attrs[i]->atttypid;
531 StringInfoData item_buf;
535 /* Ignore dropped columns in datatype, but fill with nulls */
536 if (tupdesc->attrs[i]->attisdropped)
538 values[i] = (Datum) 0;
543 /* Verify column datatype */
544 coltypoid = pq_getmsgint(buf, sizeof(Oid));
545 if (coltypoid != column_type)
547 (errcode(ERRCODE_DATATYPE_MISMATCH),
548 errmsg("wrong data type: %u, expected %u",
549 coltypoid, column_type)));
551 /* Get and check the item length */
552 itemlen = pq_getmsgint(buf, 4);
553 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
555 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
556 errmsg("insufficient data left in message")));
560 /* -1 length means NULL */
563 csave = 0; /* keep compiler quiet */
568 * Rather than copying data around, we just set up a phony
569 * StringInfo pointing to the correct portion of the input buffer.
570 * We assume we can scribble on the input buffer so as to maintain
571 * the convention that StringInfos have a trailing null.
573 item_buf.data = &buf->data[buf->cursor];
574 item_buf.maxlen = itemlen + 1;
575 item_buf.len = itemlen;
578 buf->cursor += itemlen;
580 csave = buf->data[buf->cursor];
581 buf->data[buf->cursor] = '\0';
587 /* Now call the column's receiveproc */
588 if (column_info->column_type != column_type)
590 getTypeBinaryInputInfo(column_type,
591 &column_info->typiofunc,
592 &column_info->typioparam);
593 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
594 fcinfo->flinfo->fn_mcxt);
595 column_info->column_type = column_type;
598 values[i] = ReceiveFunctionCall(&column_info->proc,
600 column_info->typioparam,
601 tupdesc->attrs[i]->atttypmod);
605 /* Trouble if it didn't eat the whole buffer */
606 if (item_buf.cursor != itemlen)
608 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
609 errmsg("improper binary format in record column %d",
612 buf->data[buf->cursor] = csave;
616 tuple = heap_form_tuple(tupdesc, values, nulls);
619 * We cannot return tuple->t_data because heap_form_tuple allocates it as
620 * part of a larger chunk, and our caller may expect to be able to pfree
621 * our result. So must copy the info into a new palloc chunk.
623 result = (HeapTupleHeader) palloc(tuple->t_len);
624 memcpy(result, tuple->t_data, tuple->t_len);
626 heap_freetuple(tuple);
629 ReleaseTupleDesc(tupdesc);
631 PG_RETURN_HEAPTUPLEHEADER(result);
635 * record_send - binary output routine for any composite type.
638 record_send(PG_FUNCTION_ARGS)
640 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
645 RecordIOData *my_extra;
653 /* Extract type info from the tuple itself */
654 tupType = HeapTupleHeaderGetTypeId(rec);
655 tupTypmod = HeapTupleHeaderGetTypMod(rec);
656 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
657 ncolumns = tupdesc->natts;
659 /* Build a temporary HeapTuple control structure */
660 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
661 ItemPointerSetInvalid(&(tuple.t_self));
662 tuple.t_tableOid = InvalidOid;
666 * We arrange to look up the needed I/O info just once per series of
667 * calls, assuming the record type doesn't change underneath us.
669 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
670 if (my_extra == NULL ||
671 my_extra->ncolumns != ncolumns)
673 fcinfo->flinfo->fn_extra =
674 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
675 offsetof(RecordIOData, columns) +
676 ncolumns * sizeof(ColumnIOData));
677 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
678 my_extra->record_type = InvalidOid;
679 my_extra->record_typmod = 0;
682 if (my_extra->record_type != tupType ||
683 my_extra->record_typmod != tupTypmod)
686 offsetof(RecordIOData, columns) +
687 ncolumns * sizeof(ColumnIOData));
688 my_extra->record_type = tupType;
689 my_extra->record_typmod = tupTypmod;
690 my_extra->ncolumns = ncolumns;
693 values = (Datum *) palloc(ncolumns * sizeof(Datum));
694 nulls = (bool *) palloc(ncolumns * sizeof(bool));
696 /* Break down the tuple into fields */
697 heap_deform_tuple(&tuple, tupdesc, values, nulls);
699 /* And build the result string */
700 pq_begintypsend(&buf);
702 /* Need to scan to count nondeleted columns */
704 for (i = 0; i < ncolumns; i++)
706 if (!tupdesc->attrs[i]->attisdropped)
709 pq_sendint(&buf, validcols, 4);
711 for (i = 0; i < ncolumns; i++)
713 ColumnIOData *column_info = &my_extra->columns[i];
714 Oid column_type = tupdesc->attrs[i]->atttypid;
718 /* Ignore dropped columns in datatype */
719 if (tupdesc->attrs[i]->attisdropped)
722 pq_sendint(&buf, column_type, sizeof(Oid));
726 /* emit -1 data length to signify a NULL */
727 pq_sendint(&buf, -1, 4);
732 * Convert the column value to binary
734 if (column_info->column_type != column_type)
736 getTypeBinaryOutputInfo(column_type,
737 &column_info->typiofunc,
738 &column_info->typisvarlena);
739 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
740 fcinfo->flinfo->fn_mcxt);
741 column_info->column_type = column_type;
745 outputbytes = SendFunctionCall(&column_info->proc, attr);
746 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
747 pq_sendbytes(&buf, VARDATA(outputbytes),
748 VARSIZE(outputbytes) - VARHDRSZ);
753 ReleaseTupleDesc(tupdesc);
755 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
761 * Internal comparison function for records.
765 * Do not assume that the two inputs are exactly the same record type;
766 * for instance we might be comparing an anonymous ROW() construct against a
767 * named composite type. We will compare as long as they have the same number
768 * of non-dropped columns of the same types.
771 record_cmp(FunctionCallInfo fcinfo)
773 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
774 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
782 HeapTupleData tuple1;
783 HeapTupleData tuple2;
786 RecordCompareData *my_extra;
796 /* Extract type info from the tuples */
797 tupType1 = HeapTupleHeaderGetTypeId(record1);
798 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
799 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
800 ncolumns1 = tupdesc1->natts;
801 tupType2 = HeapTupleHeaderGetTypeId(record2);
802 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
803 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
804 ncolumns2 = tupdesc2->natts;
806 /* Build temporary HeapTuple control structures */
807 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
808 ItemPointerSetInvalid(&(tuple1.t_self));
809 tuple1.t_tableOid = InvalidOid;
810 tuple1.t_data = record1;
811 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
812 ItemPointerSetInvalid(&(tuple2.t_self));
813 tuple2.t_tableOid = InvalidOid;
814 tuple2.t_data = record2;
817 * We arrange to look up the needed comparison info just once per series
818 * of calls, assuming the record types don't change underneath us.
820 ncols = Max(ncolumns1, ncolumns2);
821 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
822 if (my_extra == NULL ||
823 my_extra->ncolumns < ncols)
825 fcinfo->flinfo->fn_extra =
826 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
827 offsetof(RecordCompareData, columns) +
828 ncols * sizeof(ColumnCompareData));
829 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
830 my_extra->ncolumns = ncols;
831 my_extra->record1_type = InvalidOid;
832 my_extra->record1_typmod = 0;
833 my_extra->record2_type = InvalidOid;
834 my_extra->record2_typmod = 0;
837 if (my_extra->record1_type != tupType1 ||
838 my_extra->record1_typmod != tupTypmod1 ||
839 my_extra->record2_type != tupType2 ||
840 my_extra->record2_typmod != tupTypmod2)
842 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
843 my_extra->record1_type = tupType1;
844 my_extra->record1_typmod = tupTypmod1;
845 my_extra->record2_type = tupType2;
846 my_extra->record2_typmod = tupTypmod2;
849 /* Break down the tuples into fields */
850 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
851 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
852 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
853 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
854 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
855 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
858 * Scan corresponding columns, allowing for dropped columns in different
859 * places in the two rows. i1 and i2 are physical column indexes, j is
860 * the logical column index.
863 while (i1 < ncolumns1 || i2 < ncolumns2)
865 TypeCacheEntry *typentry;
869 * Skip dropped columns
871 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
876 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
881 if (i1 >= ncolumns1 || i2 >= ncolumns2)
882 break; /* we'll deal with mismatch below loop */
885 * Have two matching columns, they must be same type
887 if (tupdesc1->attrs[i1]->atttypid !=
888 tupdesc2->attrs[i2]->atttypid)
890 (errcode(ERRCODE_DATATYPE_MISMATCH),
891 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
892 format_type_be(tupdesc1->attrs[i1]->atttypid),
893 format_type_be(tupdesc2->attrs[i2]->atttypid),
897 * If they're not same collation, we don't complain here, but the
898 * comparison function might.
900 collation = tupdesc1->attrs[i1]->attcollation;
901 if (collation != tupdesc2->attrs[i2]->attcollation)
902 collation = InvalidOid;
905 * Lookup the comparison function if not done already
907 typentry = my_extra->columns[j].typentry;
908 if (typentry == NULL ||
909 typentry->type_id != tupdesc1->attrs[i1]->atttypid)
911 typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
912 TYPECACHE_CMP_PROC_FINFO);
913 if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
915 (errcode(ERRCODE_UNDEFINED_FUNCTION),
916 errmsg("could not identify a comparison function for type %s",
917 format_type_be(typentry->type_id))));
918 my_extra->columns[j].typentry = typentry;
922 * We consider two NULLs equal; NULL > not-NULL.
924 if (!nulls1[i1] || !nulls2[i2])
926 FunctionCallInfoData locfcinfo;
931 /* arg1 is greater than arg2 */
937 /* arg1 is less than arg2 */
942 /* Compare the pair of elements */
943 InitFunctionCallInfoData(locfcinfo, &typentry->cmp_proc_finfo, 2,
944 collation, NULL, NULL);
945 locfcinfo.arg[0] = values1[i1];
946 locfcinfo.arg[1] = values2[i2];
947 locfcinfo.argnull[0] = false;
948 locfcinfo.argnull[1] = false;
949 locfcinfo.isnull = false;
950 cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
954 /* arg1 is less than arg2 */
958 else if (cmpresult > 0)
960 /* arg1 is greater than arg2 */
966 /* equal, so continue to next column */
971 * If we didn't break out of the loop early, check for column count
972 * mismatch. (We do not report such mismatch if we found unequal column
973 * values; is that a feature or a bug?)
977 if (i1 != ncolumns1 || i2 != ncolumns2)
979 (errcode(ERRCODE_DATATYPE_MISMATCH),
980 errmsg("cannot compare record types with different numbers of columns")));
987 ReleaseTupleDesc(tupdesc1);
988 ReleaseTupleDesc(tupdesc2);
990 /* Avoid leaking memory when handed toasted input. */
991 PG_FREE_IF_COPY(record1, 0);
992 PG_FREE_IF_COPY(record2, 1);
999 * compares two records for equality
1001 * returns true if the records are equal, false otherwise.
1003 * Note: we do not use record_cmp here, since equality may be meaningful in
1004 * datatypes that don't have a total ordering (and hence no btree support).
1007 record_eq(PG_FUNCTION_ARGS)
1009 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1010 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1018 HeapTupleData tuple1;
1019 HeapTupleData tuple2;
1022 RecordCompareData *my_extra;
1032 /* Extract type info from the tuples */
1033 tupType1 = HeapTupleHeaderGetTypeId(record1);
1034 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1035 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1036 ncolumns1 = tupdesc1->natts;
1037 tupType2 = HeapTupleHeaderGetTypeId(record2);
1038 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1039 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1040 ncolumns2 = tupdesc2->natts;
1042 /* Build temporary HeapTuple control structures */
1043 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1044 ItemPointerSetInvalid(&(tuple1.t_self));
1045 tuple1.t_tableOid = InvalidOid;
1046 tuple1.t_data = record1;
1047 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1048 ItemPointerSetInvalid(&(tuple2.t_self));
1049 tuple2.t_tableOid = InvalidOid;
1050 tuple2.t_data = record2;
1053 * We arrange to look up the needed comparison info just once per series
1054 * of calls, assuming the record types don't change underneath us.
1056 ncols = Max(ncolumns1, ncolumns2);
1057 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1058 if (my_extra == NULL ||
1059 my_extra->ncolumns < ncols)
1061 fcinfo->flinfo->fn_extra =
1062 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1063 offsetof(RecordCompareData, columns) +
1064 ncols * sizeof(ColumnCompareData));
1065 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1066 my_extra->ncolumns = ncols;
1067 my_extra->record1_type = InvalidOid;
1068 my_extra->record1_typmod = 0;
1069 my_extra->record2_type = InvalidOid;
1070 my_extra->record2_typmod = 0;
1073 if (my_extra->record1_type != tupType1 ||
1074 my_extra->record1_typmod != tupTypmod1 ||
1075 my_extra->record2_type != tupType2 ||
1076 my_extra->record2_typmod != tupTypmod2)
1078 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1079 my_extra->record1_type = tupType1;
1080 my_extra->record1_typmod = tupTypmod1;
1081 my_extra->record2_type = tupType2;
1082 my_extra->record2_typmod = tupTypmod2;
1085 /* Break down the tuples into fields */
1086 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1087 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1088 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1089 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1090 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1091 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1094 * Scan corresponding columns, allowing for dropped columns in different
1095 * places in the two rows. i1 and i2 are physical column indexes, j is
1096 * the logical column index.
1099 while (i1 < ncolumns1 || i2 < ncolumns2)
1101 TypeCacheEntry *typentry;
1103 FunctionCallInfoData locfcinfo;
1107 * Skip dropped columns
1109 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1114 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1119 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1120 break; /* we'll deal with mismatch below loop */
1123 * Have two matching columns, they must be same type
1125 if (tupdesc1->attrs[i1]->atttypid !=
1126 tupdesc2->attrs[i2]->atttypid)
1128 (errcode(ERRCODE_DATATYPE_MISMATCH),
1129 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1130 format_type_be(tupdesc1->attrs[i1]->atttypid),
1131 format_type_be(tupdesc2->attrs[i2]->atttypid),
1135 * If they're not same collation, we don't complain here, but the
1136 * equality function might.
1138 collation = tupdesc1->attrs[i1]->attcollation;
1139 if (collation != tupdesc2->attrs[i2]->attcollation)
1140 collation = InvalidOid;
1143 * Lookup the equality function if not done already
1145 typentry = my_extra->columns[j].typentry;
1146 if (typentry == NULL ||
1147 typentry->type_id != tupdesc1->attrs[i1]->atttypid)
1149 typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
1150 TYPECACHE_EQ_OPR_FINFO);
1151 if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1153 (errcode(ERRCODE_UNDEFINED_FUNCTION),
1154 errmsg("could not identify an equality operator for type %s",
1155 format_type_be(typentry->type_id))));
1156 my_extra->columns[j].typentry = typentry;
1160 * We consider two NULLs equal; NULL > not-NULL.
1162 if (!nulls1[i1] || !nulls2[i2])
1164 if (nulls1[i1] || nulls2[i2])
1170 /* Compare the pair of elements */
1171 InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
1172 collation, NULL, NULL);
1173 locfcinfo.arg[0] = values1[i1];
1174 locfcinfo.arg[1] = values2[i2];
1175 locfcinfo.argnull[0] = false;
1176 locfcinfo.argnull[1] = false;
1177 locfcinfo.isnull = false;
1178 oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
1186 /* equal, so continue to next column */
1191 * If we didn't break out of the loop early, check for column count
1192 * mismatch. (We do not report such mismatch if we found unequal column
1193 * values; is that a feature or a bug?)
1197 if (i1 != ncolumns1 || i2 != ncolumns2)
1199 (errcode(ERRCODE_DATATYPE_MISMATCH),
1200 errmsg("cannot compare record types with different numbers of columns")));
1207 ReleaseTupleDesc(tupdesc1);
1208 ReleaseTupleDesc(tupdesc2);
1210 /* Avoid leaking memory when handed toasted input. */
1211 PG_FREE_IF_COPY(record1, 0);
1212 PG_FREE_IF_COPY(record2, 1);
1214 PG_RETURN_BOOL(result);
1218 record_ne(PG_FUNCTION_ARGS)
1220 PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
1224 record_lt(PG_FUNCTION_ARGS)
1226 PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
1230 record_gt(PG_FUNCTION_ARGS)
1232 PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
1236 record_le(PG_FUNCTION_ARGS)
1238 PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
1242 record_ge(PG_FUNCTION_ARGS)
1244 PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
1248 btrecordcmp(PG_FUNCTION_ARGS)
1250 PG_RETURN_INT32(record_cmp(fcinfo));
1255 * record_image_cmp :
1256 * Internal byte-oriented comparison function for records.
1258 * Returns -1, 0 or 1
1260 * Note: The normal concepts of "equality" do not apply here; different
1261 * representation of values considered to be equal are not considered to be
1262 * identical. As an example, for the citext type 'A' and 'a' are equal, but
1263 * they are not identical.
1266 record_image_cmp(FunctionCallInfo fcinfo)
1268 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1269 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1277 HeapTupleData tuple1;
1278 HeapTupleData tuple2;
1281 RecordCompareData *my_extra;
1291 /* Extract type info from the tuples */
1292 tupType1 = HeapTupleHeaderGetTypeId(record1);
1293 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1294 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1295 ncolumns1 = tupdesc1->natts;
1296 tupType2 = HeapTupleHeaderGetTypeId(record2);
1297 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1298 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1299 ncolumns2 = tupdesc2->natts;
1301 /* Build temporary HeapTuple control structures */
1302 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1303 ItemPointerSetInvalid(&(tuple1.t_self));
1304 tuple1.t_tableOid = InvalidOid;
1305 tuple1.t_data = record1;
1306 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1307 ItemPointerSetInvalid(&(tuple2.t_self));
1308 tuple2.t_tableOid = InvalidOid;
1309 tuple2.t_data = record2;
1312 * We arrange to look up the needed comparison info just once per series
1313 * of calls, assuming the record types don't change underneath us.
1315 ncols = Max(ncolumns1, ncolumns2);
1316 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1317 if (my_extra == NULL ||
1318 my_extra->ncolumns < ncols)
1320 fcinfo->flinfo->fn_extra =
1321 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1322 offsetof(RecordCompareData, columns) +
1323 ncols * sizeof(ColumnCompareData));
1324 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1325 my_extra->ncolumns = ncols;
1326 my_extra->record1_type = InvalidOid;
1327 my_extra->record1_typmod = 0;
1328 my_extra->record2_type = InvalidOid;
1329 my_extra->record2_typmod = 0;
1332 if (my_extra->record1_type != tupType1 ||
1333 my_extra->record1_typmod != tupTypmod1 ||
1334 my_extra->record2_type != tupType2 ||
1335 my_extra->record2_typmod != tupTypmod2)
1337 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1338 my_extra->record1_type = tupType1;
1339 my_extra->record1_typmod = tupTypmod1;
1340 my_extra->record2_type = tupType2;
1341 my_extra->record2_typmod = tupTypmod2;
1344 /* Break down the tuples into fields */
1345 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1346 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1347 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1348 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1349 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1350 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1353 * Scan corresponding columns, allowing for dropped columns in different
1354 * places in the two rows. i1 and i2 are physical column indexes, j is
1355 * the logical column index.
1358 while (i1 < ncolumns1 || i2 < ncolumns2)
1361 * Skip dropped columns
1363 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1368 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1373 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1374 break; /* we'll deal with mismatch below loop */
1377 * Have two matching columns, they must be same type
1379 if (tupdesc1->attrs[i1]->atttypid !=
1380 tupdesc2->attrs[i2]->atttypid)
1382 (errcode(ERRCODE_DATATYPE_MISMATCH),
1383 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1384 format_type_be(tupdesc1->attrs[i1]->atttypid),
1385 format_type_be(tupdesc2->attrs[i2]->atttypid),
1389 * The same type should have the same length (or both should be
1392 Assert(tupdesc1->attrs[i1]->attlen ==
1393 tupdesc2->attrs[i2]->attlen);
1396 * We consider two NULLs equal; NULL > not-NULL.
1398 if (!nulls1[i1] || !nulls2[i2])
1404 /* arg1 is greater than arg2 */
1410 /* arg1 is less than arg2 */
1415 /* Compare the pair of elements */
1416 if (tupdesc1->attrs[i1]->attlen == -1)
1420 struct varlena *arg1val;
1421 struct varlena *arg2val;
1423 len1 = toast_raw_datum_size(values1[i1]);
1424 len2 = toast_raw_datum_size(values2[i2]);
1425 arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
1426 arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
1428 cmpresult = memcmp(VARDATA_ANY(arg1val),
1429 VARDATA_ANY(arg2val),
1430 Min(len1, len2) - VARHDRSZ);
1431 if ((cmpresult == 0) && (len1 != len2))
1432 cmpresult = (len1 < len2) ? -1 : 1;
1434 if ((Pointer) arg1val != (Pointer) values1[i1])
1436 if ((Pointer) arg2val != (Pointer) values2[i2])
1439 else if (tupdesc1->attrs[i1]->attbyval)
1441 switch (tupdesc1->attrs[i1]->attlen)
1444 if (GET_1_BYTE(values1[i1]) !=
1445 GET_1_BYTE(values2[i2]))
1447 cmpresult = (GET_1_BYTE(values1[i1]) <
1448 GET_1_BYTE(values2[i2])) ? -1 : 1;
1452 if (GET_2_BYTES(values1[i1]) !=
1453 GET_2_BYTES(values2[i2]))
1455 cmpresult = (GET_2_BYTES(values1[i1]) <
1456 GET_2_BYTES(values2[i2])) ? -1 : 1;
1460 if (GET_4_BYTES(values1[i1]) !=
1461 GET_4_BYTES(values2[i2]))
1463 cmpresult = (GET_4_BYTES(values1[i1]) <
1464 GET_4_BYTES(values2[i2])) ? -1 : 1;
1467 #if SIZEOF_DATUM == 8
1469 if (GET_8_BYTES(values1[i1]) !=
1470 GET_8_BYTES(values2[i2]))
1472 cmpresult = (GET_8_BYTES(values1[i1]) <
1473 GET_8_BYTES(values2[i2])) ? -1 : 1;
1478 Assert(false); /* cannot happen */
1483 cmpresult = memcmp(DatumGetPointer(values1[i1]),
1484 DatumGetPointer(values2[i2]),
1485 tupdesc1->attrs[i1]->attlen);
1490 /* arg1 is less than arg2 */
1494 else if (cmpresult > 0)
1496 /* arg1 is greater than arg2 */
1502 /* equal, so continue to next column */
1507 * If we didn't break out of the loop early, check for column count
1508 * mismatch. (We do not report such mismatch if we found unequal column
1509 * values; is that a feature or a bug?)
1513 if (i1 != ncolumns1 || i2 != ncolumns2)
1515 (errcode(ERRCODE_DATATYPE_MISMATCH),
1516 errmsg("cannot compare record types with different numbers of columns")));
1523 ReleaseTupleDesc(tupdesc1);
1524 ReleaseTupleDesc(tupdesc2);
1526 /* Avoid leaking memory when handed toasted input. */
1527 PG_FREE_IF_COPY(record1, 0);
1528 PG_FREE_IF_COPY(record2, 1);
1535 * compares two records for identical contents, based on byte images
1537 * returns true if the records are identical, false otherwise.
1539 * Note: we do not use record_image_cmp here, since we can avoid
1540 * de-toasting for unequal lengths this way.
1543 record_image_eq(PG_FUNCTION_ARGS)
1545 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1546 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1554 HeapTupleData tuple1;
1555 HeapTupleData tuple2;
1558 RecordCompareData *my_extra;
1568 /* Extract type info from the tuples */
1569 tupType1 = HeapTupleHeaderGetTypeId(record1);
1570 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1571 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1572 ncolumns1 = tupdesc1->natts;
1573 tupType2 = HeapTupleHeaderGetTypeId(record2);
1574 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1575 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1576 ncolumns2 = tupdesc2->natts;
1578 /* Build temporary HeapTuple control structures */
1579 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1580 ItemPointerSetInvalid(&(tuple1.t_self));
1581 tuple1.t_tableOid = InvalidOid;
1582 tuple1.t_data = record1;
1583 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1584 ItemPointerSetInvalid(&(tuple2.t_self));
1585 tuple2.t_tableOid = InvalidOid;
1586 tuple2.t_data = record2;
1589 * We arrange to look up the needed comparison info just once per series
1590 * of calls, assuming the record types don't change underneath us.
1592 ncols = Max(ncolumns1, ncolumns2);
1593 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1594 if (my_extra == NULL ||
1595 my_extra->ncolumns < ncols)
1597 fcinfo->flinfo->fn_extra =
1598 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1599 offsetof(RecordCompareData, columns) +
1600 ncols * sizeof(ColumnCompareData));
1601 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1602 my_extra->ncolumns = ncols;
1603 my_extra->record1_type = InvalidOid;
1604 my_extra->record1_typmod = 0;
1605 my_extra->record2_type = InvalidOid;
1606 my_extra->record2_typmod = 0;
1609 if (my_extra->record1_type != tupType1 ||
1610 my_extra->record1_typmod != tupTypmod1 ||
1611 my_extra->record2_type != tupType2 ||
1612 my_extra->record2_typmod != tupTypmod2)
1614 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1615 my_extra->record1_type = tupType1;
1616 my_extra->record1_typmod = tupTypmod1;
1617 my_extra->record2_type = tupType2;
1618 my_extra->record2_typmod = tupTypmod2;
1621 /* Break down the tuples into fields */
1622 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1623 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1624 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1625 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1626 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1627 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1630 * Scan corresponding columns, allowing for dropped columns in different
1631 * places in the two rows. i1 and i2 are physical column indexes, j is
1632 * the logical column index.
1635 while (i1 < ncolumns1 || i2 < ncolumns2)
1638 * Skip dropped columns
1640 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1645 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1650 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1651 break; /* we'll deal with mismatch below loop */
1654 * Have two matching columns, they must be same type
1656 if (tupdesc1->attrs[i1]->atttypid !=
1657 tupdesc2->attrs[i2]->atttypid)
1659 (errcode(ERRCODE_DATATYPE_MISMATCH),
1660 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1661 format_type_be(tupdesc1->attrs[i1]->atttypid),
1662 format_type_be(tupdesc2->attrs[i2]->atttypid),
1666 * We consider two NULLs equal; NULL > not-NULL.
1668 if (!nulls1[i1] || !nulls2[i2])
1670 if (nulls1[i1] || nulls2[i2])
1676 /* Compare the pair of elements */
1677 if (tupdesc1->attrs[i1]->attlen == -1)
1682 len1 = toast_raw_datum_size(values1[i1]);
1683 len2 = toast_raw_datum_size(values2[i2]);
1684 /* No need to de-toast if lengths don't match. */
1689 struct varlena *arg1val;
1690 struct varlena *arg2val;
1692 arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
1693 arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
1695 result = (memcmp(VARDATA_ANY(arg1val),
1696 VARDATA_ANY(arg2val),
1697 len1 - VARHDRSZ) == 0);
1699 /* Only free memory if it's a copy made here. */
1700 if ((Pointer) arg1val != (Pointer) values1[i1])
1702 if ((Pointer) arg2val != (Pointer) values2[i2])
1706 else if (tupdesc1->attrs[i1]->attbyval)
1708 switch (tupdesc1->attrs[i1]->attlen)
1711 result = (GET_1_BYTE(values1[i1]) ==
1712 GET_1_BYTE(values2[i2]));
1715 result = (GET_2_BYTES(values1[i1]) ==
1716 GET_2_BYTES(values2[i2]));
1719 result = (GET_4_BYTES(values1[i1]) ==
1720 GET_4_BYTES(values2[i2]));
1722 #if SIZEOF_DATUM == 8
1724 result = (GET_8_BYTES(values1[i1]) ==
1725 GET_8_BYTES(values2[i2]));
1729 Assert(false); /* cannot happen */
1734 result = (memcmp(DatumGetPointer(values1[i1]),
1735 DatumGetPointer(values2[i2]),
1736 tupdesc1->attrs[i1]->attlen) == 0);
1742 /* equal, so continue to next column */
1747 * If we didn't break out of the loop early, check for column count
1748 * mismatch. (We do not report such mismatch if we found unequal column
1749 * values; is that a feature or a bug?)
1753 if (i1 != ncolumns1 || i2 != ncolumns2)
1755 (errcode(ERRCODE_DATATYPE_MISMATCH),
1756 errmsg("cannot compare record types with different numbers of columns")));
1763 ReleaseTupleDesc(tupdesc1);
1764 ReleaseTupleDesc(tupdesc2);
1766 /* Avoid leaking memory when handed toasted input. */
1767 PG_FREE_IF_COPY(record1, 0);
1768 PG_FREE_IF_COPY(record2, 1);
1770 PG_RETURN_BOOL(result);
1774 record_image_ne(PG_FUNCTION_ARGS)
1776 PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo)));
1780 record_image_lt(PG_FUNCTION_ARGS)
1782 PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0);
1786 record_image_gt(PG_FUNCTION_ARGS)
1788 PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0);
1792 record_image_le(PG_FUNCTION_ARGS)
1794 PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0);
1798 record_image_ge(PG_FUNCTION_ARGS)
1800 PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0);
1804 btrecordimagecmp(PG_FUNCTION_ARGS)
1806 PG_RETURN_INT32(record_image_cmp(fcinfo));