1 /*-------------------------------------------------------------------------
4 * I/O and comparison functions for generic composite types.
6 * Portions Copyright (c) 1996-2025, 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 Node
*escontext
= fcinfo
->context
;
80 HeapTupleHeader result
;
83 RecordIOData
*my_extra
;
84 bool needComma
= false;
92 check_stack_depth(); /* recurses for record-type columns */
95 * Give a friendly error message if we did not get enough info to identify
96 * the target record type. (lookup_rowtype_tupdesc would fail anyway, but
97 * with a non-user-friendly message.) In ordinary SQL usage, we'll get -1
98 * for typmod, since composite types and RECORD have no type modifiers at
99 * the SQL level, and thus must fail for RECORD. However some callers can
100 * supply a valid typmod, and then we can do something useful for RECORD.
102 if (tupType
== RECORDOID
&& tupTypmod
< 0)
103 ereturn(escontext
, (Datum
) 0,
104 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED
),
105 errmsg("input of anonymous composite types is not implemented")));
108 * This comes from the composite type's pg_type.oid and stores system oids
109 * in user tables, specifically DatumTupleFields. This oid must be
110 * preserved by binary upgrades.
112 tupdesc
= lookup_rowtype_tupdesc(tupType
, tupTypmod
);
113 ncolumns
= tupdesc
->natts
;
116 * We arrange to look up the needed I/O info just once per series of
117 * calls, assuming the record type doesn't change underneath us.
119 my_extra
= (RecordIOData
*) fcinfo
->flinfo
->fn_extra
;
120 if (my_extra
== NULL
||
121 my_extra
->ncolumns
!= ncolumns
)
123 fcinfo
->flinfo
->fn_extra
=
124 MemoryContextAlloc(fcinfo
->flinfo
->fn_mcxt
,
125 offsetof(RecordIOData
, columns
) +
126 ncolumns
* sizeof(ColumnIOData
));
127 my_extra
= (RecordIOData
*) fcinfo
->flinfo
->fn_extra
;
128 my_extra
->record_type
= InvalidOid
;
129 my_extra
->record_typmod
= 0;
132 if (my_extra
->record_type
!= tupType
||
133 my_extra
->record_typmod
!= tupTypmod
)
136 offsetof(RecordIOData
, columns
) +
137 ncolumns
* sizeof(ColumnIOData
));
138 my_extra
->record_type
= tupType
;
139 my_extra
->record_typmod
= tupTypmod
;
140 my_extra
->ncolumns
= ncolumns
;
143 values
= (Datum
*) palloc(ncolumns
* sizeof(Datum
));
144 nulls
= (bool *) palloc(ncolumns
* sizeof(bool));
147 * Scan the string. We use "buf" to accumulate the de-quoted data for
148 * each column, which is then fed to the appropriate input converter.
151 /* Allow leading whitespace */
152 while (*ptr
&& isspace((unsigned char) *ptr
))
157 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION
),
158 errmsg("malformed record literal: \"%s\"", string
),
159 errdetail("Missing left parenthesis.")));
163 initStringInfo(&buf
);
165 for (i
= 0; i
< ncolumns
; i
++)
167 Form_pg_attribute att
= TupleDescAttr(tupdesc
, i
);
168 ColumnIOData
*column_info
= &my_extra
->columns
[i
];
169 Oid column_type
= att
->atttypid
;
172 /* Ignore dropped columns in datatype, but fill with nulls */
173 if (att
->attisdropped
)
175 values
[i
] = (Datum
) 0;
182 /* Skip comma that separates prior field from this one */
186 /* *ptr must be ')' */
189 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION
),
190 errmsg("malformed record literal: \"%s\"", string
),
191 errdetail("Too few columns.")));
196 /* Check for null: completely empty input means null */
197 if (*ptr
== ',' || *ptr
== ')')
204 /* Extract string for this column */
205 bool inquote
= false;
207 resetStringInfo(&buf
);
208 while (inquote
|| !(*ptr
== ',' || *ptr
== ')'))
215 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION
),
216 errmsg("malformed record literal: \"%s\"",
218 errdetail("Unexpected end of input.")));
226 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION
),
227 errmsg("malformed record literal: \"%s\"",
229 errdetail("Unexpected end of input.")));
232 appendStringInfoChar(&buf
, *ptr
++);
238 else if (*ptr
== '"')
240 /* doubled quote within quote sequence */
241 appendStringInfoChar(&buf
, *ptr
++);
247 appendStringInfoChar(&buf
, ch
);
250 column_data
= buf
.data
;
255 * Convert the column value
257 if (column_info
->column_type
!= column_type
)
259 getTypeInputInfo(column_type
,
260 &column_info
->typiofunc
,
261 &column_info
->typioparam
);
262 fmgr_info_cxt(column_info
->typiofunc
, &column_info
->proc
,
263 fcinfo
->flinfo
->fn_mcxt
);
264 column_info
->column_type
= column_type
;
267 if (!InputFunctionCallSafe(&column_info
->proc
,
269 column_info
->typioparam
,
276 * Prep for next column
284 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION
),
285 errmsg("malformed record literal: \"%s\"", string
),
286 errdetail("Too many columns.")));
289 /* Allow trailing whitespace */
290 while (*ptr
&& isspace((unsigned char) *ptr
))
295 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION
),
296 errmsg("malformed record literal: \"%s\"", string
),
297 errdetail("Junk after right parenthesis.")));
301 tuple
= heap_form_tuple(tupdesc
, values
, nulls
);
304 * We cannot return tuple->t_data because heap_form_tuple allocates it as
305 * part of a larger chunk, and our caller may expect to be able to pfree
306 * our result. So must copy the info into a new palloc chunk.
308 result
= (HeapTupleHeader
) palloc(tuple
->t_len
);
309 memcpy(result
, tuple
->t_data
, tuple
->t_len
);
311 heap_freetuple(tuple
);
315 ReleaseTupleDesc(tupdesc
);
317 PG_RETURN_HEAPTUPLEHEADER(result
);
319 /* exit here once we've done lookup_rowtype_tupdesc */
321 ReleaseTupleDesc(tupdesc
);
326 * record_out - output routine for any composite type.
329 record_out(PG_FUNCTION_ARGS
)
331 HeapTupleHeader rec
= PG_GETARG_HEAPTUPLEHEADER(0);
336 RecordIOData
*my_extra
;
337 bool needComma
= false;
344 check_stack_depth(); /* recurses for record-type columns */
346 /* Extract type info from the tuple itself */
347 tupType
= HeapTupleHeaderGetTypeId(rec
);
348 tupTypmod
= HeapTupleHeaderGetTypMod(rec
);
349 tupdesc
= lookup_rowtype_tupdesc(tupType
, tupTypmod
);
350 ncolumns
= tupdesc
->natts
;
352 /* Build a temporary HeapTuple control structure */
353 tuple
.t_len
= HeapTupleHeaderGetDatumLength(rec
);
354 ItemPointerSetInvalid(&(tuple
.t_self
));
355 tuple
.t_tableOid
= InvalidOid
;
359 * We arrange to look up the needed I/O info just once per series of
360 * calls, assuming the record type doesn't change underneath us.
362 my_extra
= (RecordIOData
*) fcinfo
->flinfo
->fn_extra
;
363 if (my_extra
== NULL
||
364 my_extra
->ncolumns
!= ncolumns
)
366 fcinfo
->flinfo
->fn_extra
=
367 MemoryContextAlloc(fcinfo
->flinfo
->fn_mcxt
,
368 offsetof(RecordIOData
, columns
) +
369 ncolumns
* sizeof(ColumnIOData
));
370 my_extra
= (RecordIOData
*) fcinfo
->flinfo
->fn_extra
;
371 my_extra
->record_type
= InvalidOid
;
372 my_extra
->record_typmod
= 0;
375 if (my_extra
->record_type
!= tupType
||
376 my_extra
->record_typmod
!= tupTypmod
)
379 offsetof(RecordIOData
, columns
) +
380 ncolumns
* sizeof(ColumnIOData
));
381 my_extra
->record_type
= tupType
;
382 my_extra
->record_typmod
= tupTypmod
;
383 my_extra
->ncolumns
= ncolumns
;
386 values
= (Datum
*) palloc(ncolumns
* sizeof(Datum
));
387 nulls
= (bool *) palloc(ncolumns
* sizeof(bool));
389 /* Break down the tuple into fields */
390 heap_deform_tuple(&tuple
, tupdesc
, values
, nulls
);
392 /* And build the result string */
393 initStringInfo(&buf
);
395 appendStringInfoChar(&buf
, '(');
397 for (i
= 0; i
< ncolumns
; i
++)
399 Form_pg_attribute att
= TupleDescAttr(tupdesc
, i
);
400 ColumnIOData
*column_info
= &my_extra
->columns
[i
];
401 Oid column_type
= att
->atttypid
;
407 /* Ignore dropped columns in datatype */
408 if (att
->attisdropped
)
412 appendStringInfoChar(&buf
, ',');
417 /* emit nothing... */
422 * Convert the column value to text
424 if (column_info
->column_type
!= column_type
)
426 getTypeOutputInfo(column_type
,
427 &column_info
->typiofunc
,
428 &column_info
->typisvarlena
);
429 fmgr_info_cxt(column_info
->typiofunc
, &column_info
->proc
,
430 fcinfo
->flinfo
->fn_mcxt
);
431 column_info
->column_type
= column_type
;
435 value
= OutputFunctionCall(&column_info
->proc
, attr
);
437 /* Detect whether we need double quotes for this value */
438 nq
= (value
[0] == '\0'); /* force quotes for empty string */
439 for (tmp
= value
; *tmp
; tmp
++)
443 if (ch
== '"' || ch
== '\\' ||
444 ch
== '(' || ch
== ')' || ch
== ',' ||
445 isspace((unsigned char) ch
))
452 /* And emit the string */
454 appendStringInfoCharMacro(&buf
, '"');
455 for (tmp
= value
; *tmp
; tmp
++)
459 if (ch
== '"' || ch
== '\\')
460 appendStringInfoCharMacro(&buf
, ch
);
461 appendStringInfoCharMacro(&buf
, ch
);
464 appendStringInfoCharMacro(&buf
, '"');
467 appendStringInfoChar(&buf
, ')');
471 ReleaseTupleDesc(tupdesc
);
473 PG_RETURN_CSTRING(buf
.data
);
477 * record_recv - binary input routine for any composite type.
480 record_recv(PG_FUNCTION_ARGS
)
482 StringInfo buf
= (StringInfo
) PG_GETARG_POINTER(0);
483 Oid tupType
= PG_GETARG_OID(1);
484 int32 tupTypmod
= PG_GETARG_INT32(2);
485 HeapTupleHeader result
;
488 RecordIOData
*my_extra
;
496 check_stack_depth(); /* recurses for record-type columns */
499 * Give a friendly error message if we did not get enough info to identify
500 * the target record type. (lookup_rowtype_tupdesc would fail anyway, but
501 * with a non-user-friendly message.) In ordinary SQL usage, we'll get -1
502 * for typmod, since composite types and RECORD have no type modifiers at
503 * the SQL level, and thus must fail for RECORD. However some callers can
504 * supply a valid typmod, and then we can do something useful for RECORD.
506 if (tupType
== RECORDOID
&& tupTypmod
< 0)
508 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED
),
509 errmsg("input of anonymous composite types is not implemented")));
511 tupdesc
= lookup_rowtype_tupdesc(tupType
, tupTypmod
);
512 ncolumns
= tupdesc
->natts
;
515 * We arrange to look up the needed I/O info just once per series of
516 * calls, assuming the record type doesn't change underneath us.
518 my_extra
= (RecordIOData
*) fcinfo
->flinfo
->fn_extra
;
519 if (my_extra
== NULL
||
520 my_extra
->ncolumns
!= ncolumns
)
522 fcinfo
->flinfo
->fn_extra
=
523 MemoryContextAlloc(fcinfo
->flinfo
->fn_mcxt
,
524 offsetof(RecordIOData
, columns
) +
525 ncolumns
* sizeof(ColumnIOData
));
526 my_extra
= (RecordIOData
*) fcinfo
->flinfo
->fn_extra
;
527 my_extra
->record_type
= InvalidOid
;
528 my_extra
->record_typmod
= 0;
531 if (my_extra
->record_type
!= tupType
||
532 my_extra
->record_typmod
!= tupTypmod
)
535 offsetof(RecordIOData
, columns
) +
536 ncolumns
* sizeof(ColumnIOData
));
537 my_extra
->record_type
= tupType
;
538 my_extra
->record_typmod
= tupTypmod
;
539 my_extra
->ncolumns
= ncolumns
;
542 values
= (Datum
*) palloc(ncolumns
* sizeof(Datum
));
543 nulls
= (bool *) palloc(ncolumns
* sizeof(bool));
545 /* Fetch number of columns user thinks it has */
546 usercols
= pq_getmsgint(buf
, 4);
548 /* Need to scan to count nondeleted columns */
550 for (i
= 0; i
< ncolumns
; i
++)
552 if (!TupleDescAttr(tupdesc
, i
)->attisdropped
)
555 if (usercols
!= validcols
)
557 (errcode(ERRCODE_DATATYPE_MISMATCH
),
558 errmsg("wrong number of columns: %d, expected %d",
559 usercols
, validcols
)));
561 /* Process each column */
562 for (i
= 0; i
< ncolumns
; i
++)
564 Form_pg_attribute att
= TupleDescAttr(tupdesc
, i
);
565 ColumnIOData
*column_info
= &my_extra
->columns
[i
];
566 Oid column_type
= att
->atttypid
;
569 StringInfoData item_buf
;
572 /* Ignore dropped columns in datatype, but fill with nulls */
573 if (att
->attisdropped
)
575 values
[i
] = (Datum
) 0;
580 /* Check column type recorded in the data */
581 coltypoid
= pq_getmsgint(buf
, sizeof(Oid
));
584 * From a security standpoint, it doesn't matter whether the input's
585 * column type matches what we expect: the column type's receive
586 * function has to be robust enough to cope with invalid data.
587 * However, from a user-friendliness standpoint, it's nicer to
588 * complain about type mismatches than to throw "improper binary
589 * format" errors. But there's a problem: only built-in types have
590 * OIDs that are stable enough to believe that a mismatch is a real
591 * issue. So complain only if both OIDs are in the built-in range.
592 * Otherwise, carry on with the column type we "should" be getting.
594 if (coltypoid
!= column_type
&&
595 coltypoid
< FirstGenbkiObjectId
&&
596 column_type
< FirstGenbkiObjectId
)
598 (errcode(ERRCODE_DATATYPE_MISMATCH
),
599 errmsg("binary data has type %u (%s) instead of expected %u (%s) in record column %d",
601 format_type_extended(coltypoid
, -1,
602 FORMAT_TYPE_ALLOW_INVALID
),
604 format_type_extended(column_type
, -1,
605 FORMAT_TYPE_ALLOW_INVALID
),
608 /* Get and check the item length */
609 itemlen
= pq_getmsgint(buf
, 4);
610 if (itemlen
< -1 || itemlen
> (buf
->len
- buf
->cursor
))
612 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION
),
613 errmsg("insufficient data left in message")));
617 /* -1 length means NULL */
626 * Rather than copying data around, we just initialize a
627 * StringInfo pointing to the correct portion of the message
630 strbuff
= &buf
->data
[buf
->cursor
];
631 buf
->cursor
+= itemlen
;
632 initReadOnlyStringInfo(&item_buf
, strbuff
, itemlen
);
638 /* Now call the column's receiveproc */
639 if (column_info
->column_type
!= column_type
)
641 getTypeBinaryInputInfo(column_type
,
642 &column_info
->typiofunc
,
643 &column_info
->typioparam
);
644 fmgr_info_cxt(column_info
->typiofunc
, &column_info
->proc
,
645 fcinfo
->flinfo
->fn_mcxt
);
646 column_info
->column_type
= column_type
;
649 values
[i
] = ReceiveFunctionCall(&column_info
->proc
,
651 column_info
->typioparam
,
656 /* Trouble if it didn't eat the whole buffer */
657 if (item_buf
.cursor
!= itemlen
)
659 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION
),
660 errmsg("improper binary format in record column %d",
665 tuple
= heap_form_tuple(tupdesc
, values
, nulls
);
668 * We cannot return tuple->t_data because heap_form_tuple allocates it as
669 * part of a larger chunk, and our caller may expect to be able to pfree
670 * our result. So must copy the info into a new palloc chunk.
672 result
= (HeapTupleHeader
) palloc(tuple
->t_len
);
673 memcpy(result
, tuple
->t_data
, tuple
->t_len
);
675 heap_freetuple(tuple
);
678 ReleaseTupleDesc(tupdesc
);
680 PG_RETURN_HEAPTUPLEHEADER(result
);
684 * record_send - binary output routine for any composite type.
687 record_send(PG_FUNCTION_ARGS
)
689 HeapTupleHeader rec
= PG_GETARG_HEAPTUPLEHEADER(0);
694 RecordIOData
*my_extra
;
702 check_stack_depth(); /* recurses for record-type columns */
704 /* Extract type info from the tuple itself */
705 tupType
= HeapTupleHeaderGetTypeId(rec
);
706 tupTypmod
= HeapTupleHeaderGetTypMod(rec
);
707 tupdesc
= lookup_rowtype_tupdesc(tupType
, tupTypmod
);
708 ncolumns
= tupdesc
->natts
;
710 /* Build a temporary HeapTuple control structure */
711 tuple
.t_len
= HeapTupleHeaderGetDatumLength(rec
);
712 ItemPointerSetInvalid(&(tuple
.t_self
));
713 tuple
.t_tableOid
= InvalidOid
;
717 * We arrange to look up the needed I/O info just once per series of
718 * calls, assuming the record type doesn't change underneath us.
720 my_extra
= (RecordIOData
*) fcinfo
->flinfo
->fn_extra
;
721 if (my_extra
== NULL
||
722 my_extra
->ncolumns
!= ncolumns
)
724 fcinfo
->flinfo
->fn_extra
=
725 MemoryContextAlloc(fcinfo
->flinfo
->fn_mcxt
,
726 offsetof(RecordIOData
, columns
) +
727 ncolumns
* sizeof(ColumnIOData
));
728 my_extra
= (RecordIOData
*) fcinfo
->flinfo
->fn_extra
;
729 my_extra
->record_type
= InvalidOid
;
730 my_extra
->record_typmod
= 0;
733 if (my_extra
->record_type
!= tupType
||
734 my_extra
->record_typmod
!= tupTypmod
)
737 offsetof(RecordIOData
, columns
) +
738 ncolumns
* sizeof(ColumnIOData
));
739 my_extra
->record_type
= tupType
;
740 my_extra
->record_typmod
= tupTypmod
;
741 my_extra
->ncolumns
= ncolumns
;
744 values
= (Datum
*) palloc(ncolumns
* sizeof(Datum
));
745 nulls
= (bool *) palloc(ncolumns
* sizeof(bool));
747 /* Break down the tuple into fields */
748 heap_deform_tuple(&tuple
, tupdesc
, values
, nulls
);
750 /* And build the result string */
751 pq_begintypsend(&buf
);
753 /* Need to scan to count nondeleted columns */
755 for (i
= 0; i
< ncolumns
; i
++)
757 if (!TupleDescAttr(tupdesc
, i
)->attisdropped
)
760 pq_sendint32(&buf
, validcols
);
762 for (i
= 0; i
< ncolumns
; i
++)
764 Form_pg_attribute att
= TupleDescAttr(tupdesc
, i
);
765 ColumnIOData
*column_info
= &my_extra
->columns
[i
];
766 Oid column_type
= att
->atttypid
;
770 /* Ignore dropped columns in datatype */
771 if (att
->attisdropped
)
774 pq_sendint32(&buf
, column_type
);
778 /* emit -1 data length to signify a NULL */
779 pq_sendint32(&buf
, -1);
784 * Convert the column value to binary
786 if (column_info
->column_type
!= column_type
)
788 getTypeBinaryOutputInfo(column_type
,
789 &column_info
->typiofunc
,
790 &column_info
->typisvarlena
);
791 fmgr_info_cxt(column_info
->typiofunc
, &column_info
->proc
,
792 fcinfo
->flinfo
->fn_mcxt
);
793 column_info
->column_type
= column_type
;
797 outputbytes
= SendFunctionCall(&column_info
->proc
, attr
);
798 pq_sendint32(&buf
, VARSIZE(outputbytes
) - VARHDRSZ
);
799 pq_sendbytes(&buf
, VARDATA(outputbytes
),
800 VARSIZE(outputbytes
) - VARHDRSZ
);
805 ReleaseTupleDesc(tupdesc
);
807 PG_RETURN_BYTEA_P(pq_endtypsend(&buf
));
813 * Internal comparison function for records.
817 * Do not assume that the two inputs are exactly the same record type;
818 * for instance we might be comparing an anonymous ROW() construct against a
819 * named composite type. We will compare as long as they have the same number
820 * of non-dropped columns of the same types.
823 record_cmp(FunctionCallInfo fcinfo
)
825 HeapTupleHeader record1
= PG_GETARG_HEAPTUPLEHEADER(0);
826 HeapTupleHeader record2
= PG_GETARG_HEAPTUPLEHEADER(1);
834 HeapTupleData tuple1
;
835 HeapTupleData tuple2
;
838 RecordCompareData
*my_extra
;
848 check_stack_depth(); /* recurses for record-type columns */
850 /* Extract type info from the tuples */
851 tupType1
= HeapTupleHeaderGetTypeId(record1
);
852 tupTypmod1
= HeapTupleHeaderGetTypMod(record1
);
853 tupdesc1
= lookup_rowtype_tupdesc(tupType1
, tupTypmod1
);
854 ncolumns1
= tupdesc1
->natts
;
855 tupType2
= HeapTupleHeaderGetTypeId(record2
);
856 tupTypmod2
= HeapTupleHeaderGetTypMod(record2
);
857 tupdesc2
= lookup_rowtype_tupdesc(tupType2
, tupTypmod2
);
858 ncolumns2
= tupdesc2
->natts
;
860 /* Build temporary HeapTuple control structures */
861 tuple1
.t_len
= HeapTupleHeaderGetDatumLength(record1
);
862 ItemPointerSetInvalid(&(tuple1
.t_self
));
863 tuple1
.t_tableOid
= InvalidOid
;
864 tuple1
.t_data
= record1
;
865 tuple2
.t_len
= HeapTupleHeaderGetDatumLength(record2
);
866 ItemPointerSetInvalid(&(tuple2
.t_self
));
867 tuple2
.t_tableOid
= InvalidOid
;
868 tuple2
.t_data
= record2
;
871 * We arrange to look up the needed comparison info just once per series
872 * of calls, assuming the record types don't change underneath us.
874 ncols
= Max(ncolumns1
, ncolumns2
);
875 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
876 if (my_extra
== NULL
||
877 my_extra
->ncolumns
< ncols
)
879 fcinfo
->flinfo
->fn_extra
=
880 MemoryContextAlloc(fcinfo
->flinfo
->fn_mcxt
,
881 offsetof(RecordCompareData
, columns
) +
882 ncols
* sizeof(ColumnCompareData
));
883 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
884 my_extra
->ncolumns
= ncols
;
885 my_extra
->record1_type
= InvalidOid
;
886 my_extra
->record1_typmod
= 0;
887 my_extra
->record2_type
= InvalidOid
;
888 my_extra
->record2_typmod
= 0;
891 if (my_extra
->record1_type
!= tupType1
||
892 my_extra
->record1_typmod
!= tupTypmod1
||
893 my_extra
->record2_type
!= tupType2
||
894 my_extra
->record2_typmod
!= tupTypmod2
)
896 MemSet(my_extra
->columns
, 0, ncols
* sizeof(ColumnCompareData
));
897 my_extra
->record1_type
= tupType1
;
898 my_extra
->record1_typmod
= tupTypmod1
;
899 my_extra
->record2_type
= tupType2
;
900 my_extra
->record2_typmod
= tupTypmod2
;
903 /* Break down the tuples into fields */
904 values1
= (Datum
*) palloc(ncolumns1
* sizeof(Datum
));
905 nulls1
= (bool *) palloc(ncolumns1
* sizeof(bool));
906 heap_deform_tuple(&tuple1
, tupdesc1
, values1
, nulls1
);
907 values2
= (Datum
*) palloc(ncolumns2
* sizeof(Datum
));
908 nulls2
= (bool *) palloc(ncolumns2
* sizeof(bool));
909 heap_deform_tuple(&tuple2
, tupdesc2
, values2
, nulls2
);
912 * Scan corresponding columns, allowing for dropped columns in different
913 * places in the two rows. i1 and i2 are physical column indexes, j is
914 * the logical column index.
917 while (i1
< ncolumns1
|| i2
< ncolumns2
)
919 Form_pg_attribute att1
;
920 Form_pg_attribute att2
;
921 TypeCacheEntry
*typentry
;
925 * Skip dropped columns
927 if (i1
< ncolumns1
&& TupleDescAttr(tupdesc1
, i1
)->attisdropped
)
932 if (i2
< ncolumns2
&& TupleDescAttr(tupdesc2
, i2
)->attisdropped
)
937 if (i1
>= ncolumns1
|| i2
>= ncolumns2
)
938 break; /* we'll deal with mismatch below loop */
940 att1
= TupleDescAttr(tupdesc1
, i1
);
941 att2
= TupleDescAttr(tupdesc2
, i2
);
944 * Have two matching columns, they must be same type
946 if (att1
->atttypid
!= att2
->atttypid
)
948 (errcode(ERRCODE_DATATYPE_MISMATCH
),
949 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
950 format_type_be(att1
->atttypid
),
951 format_type_be(att2
->atttypid
),
955 * If they're not same collation, we don't complain here, but the
956 * comparison function might.
958 collation
= att1
->attcollation
;
959 if (collation
!= att2
->attcollation
)
960 collation
= InvalidOid
;
963 * Lookup the comparison function if not done already
965 typentry
= my_extra
->columns
[j
].typentry
;
966 if (typentry
== NULL
||
967 typentry
->type_id
!= att1
->atttypid
)
969 typentry
= lookup_type_cache(att1
->atttypid
,
970 TYPECACHE_CMP_PROC_FINFO
);
971 if (!OidIsValid(typentry
->cmp_proc_finfo
.fn_oid
))
973 (errcode(ERRCODE_UNDEFINED_FUNCTION
),
974 errmsg("could not identify a comparison function for type %s",
975 format_type_be(typentry
->type_id
))));
976 my_extra
->columns
[j
].typentry
= typentry
;
980 * We consider two NULLs equal; NULL > not-NULL.
982 if (!nulls1
[i1
] || !nulls2
[i2
])
984 LOCAL_FCINFO(locfcinfo
, 2);
989 /* arg1 is greater than arg2 */
995 /* arg1 is less than arg2 */
1000 /* Compare the pair of elements */
1001 InitFunctionCallInfoData(*locfcinfo
, &typentry
->cmp_proc_finfo
, 2,
1002 collation
, NULL
, NULL
);
1003 locfcinfo
->args
[0].value
= values1
[i1
];
1004 locfcinfo
->args
[0].isnull
= false;
1005 locfcinfo
->args
[1].value
= values2
[i2
];
1006 locfcinfo
->args
[1].isnull
= false;
1007 cmpresult
= DatumGetInt32(FunctionCallInvoke(locfcinfo
));
1009 /* We don't expect comparison support functions to return null */
1010 Assert(!locfcinfo
->isnull
);
1014 /* arg1 is less than arg2 */
1018 else if (cmpresult
> 0)
1020 /* arg1 is greater than arg2 */
1026 /* equal, so continue to next column */
1031 * If we didn't break out of the loop early, check for column count
1032 * mismatch. (We do not report such mismatch if we found unequal column
1033 * values; is that a feature or a bug?)
1037 if (i1
!= ncolumns1
|| i2
!= ncolumns2
)
1039 (errcode(ERRCODE_DATATYPE_MISMATCH
),
1040 errmsg("cannot compare record types with different numbers of columns")));
1047 ReleaseTupleDesc(tupdesc1
);
1048 ReleaseTupleDesc(tupdesc2
);
1050 /* Avoid leaking memory when handed toasted input. */
1051 PG_FREE_IF_COPY(record1
, 0);
1052 PG_FREE_IF_COPY(record2
, 1);
1059 * compares two records for equality
1061 * returns true if the records are equal, false otherwise.
1063 * Note: we do not use record_cmp here, since equality may be meaningful in
1064 * datatypes that don't have a total ordering (and hence no btree support).
1067 record_eq(PG_FUNCTION_ARGS
)
1069 HeapTupleHeader record1
= PG_GETARG_HEAPTUPLEHEADER(0);
1070 HeapTupleHeader record2
= PG_GETARG_HEAPTUPLEHEADER(1);
1078 HeapTupleData tuple1
;
1079 HeapTupleData tuple2
;
1082 RecordCompareData
*my_extra
;
1092 check_stack_depth(); /* recurses for record-type columns */
1094 /* Extract type info from the tuples */
1095 tupType1
= HeapTupleHeaderGetTypeId(record1
);
1096 tupTypmod1
= HeapTupleHeaderGetTypMod(record1
);
1097 tupdesc1
= lookup_rowtype_tupdesc(tupType1
, tupTypmod1
);
1098 ncolumns1
= tupdesc1
->natts
;
1099 tupType2
= HeapTupleHeaderGetTypeId(record2
);
1100 tupTypmod2
= HeapTupleHeaderGetTypMod(record2
);
1101 tupdesc2
= lookup_rowtype_tupdesc(tupType2
, tupTypmod2
);
1102 ncolumns2
= tupdesc2
->natts
;
1104 /* Build temporary HeapTuple control structures */
1105 tuple1
.t_len
= HeapTupleHeaderGetDatumLength(record1
);
1106 ItemPointerSetInvalid(&(tuple1
.t_self
));
1107 tuple1
.t_tableOid
= InvalidOid
;
1108 tuple1
.t_data
= record1
;
1109 tuple2
.t_len
= HeapTupleHeaderGetDatumLength(record2
);
1110 ItemPointerSetInvalid(&(tuple2
.t_self
));
1111 tuple2
.t_tableOid
= InvalidOid
;
1112 tuple2
.t_data
= record2
;
1115 * We arrange to look up the needed comparison info just once per series
1116 * of calls, assuming the record types don't change underneath us.
1118 ncols
= Max(ncolumns1
, ncolumns2
);
1119 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
1120 if (my_extra
== NULL
||
1121 my_extra
->ncolumns
< ncols
)
1123 fcinfo
->flinfo
->fn_extra
=
1124 MemoryContextAlloc(fcinfo
->flinfo
->fn_mcxt
,
1125 offsetof(RecordCompareData
, columns
) +
1126 ncols
* sizeof(ColumnCompareData
));
1127 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
1128 my_extra
->ncolumns
= ncols
;
1129 my_extra
->record1_type
= InvalidOid
;
1130 my_extra
->record1_typmod
= 0;
1131 my_extra
->record2_type
= InvalidOid
;
1132 my_extra
->record2_typmod
= 0;
1135 if (my_extra
->record1_type
!= tupType1
||
1136 my_extra
->record1_typmod
!= tupTypmod1
||
1137 my_extra
->record2_type
!= tupType2
||
1138 my_extra
->record2_typmod
!= tupTypmod2
)
1140 MemSet(my_extra
->columns
, 0, ncols
* sizeof(ColumnCompareData
));
1141 my_extra
->record1_type
= tupType1
;
1142 my_extra
->record1_typmod
= tupTypmod1
;
1143 my_extra
->record2_type
= tupType2
;
1144 my_extra
->record2_typmod
= tupTypmod2
;
1147 /* Break down the tuples into fields */
1148 values1
= (Datum
*) palloc(ncolumns1
* sizeof(Datum
));
1149 nulls1
= (bool *) palloc(ncolumns1
* sizeof(bool));
1150 heap_deform_tuple(&tuple1
, tupdesc1
, values1
, nulls1
);
1151 values2
= (Datum
*) palloc(ncolumns2
* sizeof(Datum
));
1152 nulls2
= (bool *) palloc(ncolumns2
* sizeof(bool));
1153 heap_deform_tuple(&tuple2
, tupdesc2
, values2
, nulls2
);
1156 * Scan corresponding columns, allowing for dropped columns in different
1157 * places in the two rows. i1 and i2 are physical column indexes, j is
1158 * the logical column index.
1161 while (i1
< ncolumns1
|| i2
< ncolumns2
)
1163 LOCAL_FCINFO(locfcinfo
, 2);
1164 Form_pg_attribute att1
;
1165 Form_pg_attribute att2
;
1166 TypeCacheEntry
*typentry
;
1171 * Skip dropped columns
1173 if (i1
< ncolumns1
&& TupleDescAttr(tupdesc1
, i1
)->attisdropped
)
1178 if (i2
< ncolumns2
&& TupleDescAttr(tupdesc2
, i2
)->attisdropped
)
1183 if (i1
>= ncolumns1
|| i2
>= ncolumns2
)
1184 break; /* we'll deal with mismatch below loop */
1186 att1
= TupleDescAttr(tupdesc1
, i1
);
1187 att2
= TupleDescAttr(tupdesc2
, i2
);
1190 * Have two matching columns, they must be same type
1192 if (att1
->atttypid
!= att2
->atttypid
)
1194 (errcode(ERRCODE_DATATYPE_MISMATCH
),
1195 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1196 format_type_be(att1
->atttypid
),
1197 format_type_be(att2
->atttypid
),
1201 * If they're not same collation, we don't complain here, but the
1202 * equality function might.
1204 collation
= att1
->attcollation
;
1205 if (collation
!= att2
->attcollation
)
1206 collation
= InvalidOid
;
1209 * Lookup the equality function if not done already
1211 typentry
= my_extra
->columns
[j
].typentry
;
1212 if (typentry
== NULL
||
1213 typentry
->type_id
!= att1
->atttypid
)
1215 typentry
= lookup_type_cache(att1
->atttypid
,
1216 TYPECACHE_EQ_OPR_FINFO
);
1217 if (!OidIsValid(typentry
->eq_opr_finfo
.fn_oid
))
1219 (errcode(ERRCODE_UNDEFINED_FUNCTION
),
1220 errmsg("could not identify an equality operator for type %s",
1221 format_type_be(typentry
->type_id
))));
1222 my_extra
->columns
[j
].typentry
= typentry
;
1226 * We consider two NULLs equal; NULL > not-NULL.
1228 if (!nulls1
[i1
] || !nulls2
[i2
])
1230 if (nulls1
[i1
] || nulls2
[i2
])
1236 /* Compare the pair of elements */
1237 InitFunctionCallInfoData(*locfcinfo
, &typentry
->eq_opr_finfo
, 2,
1238 collation
, NULL
, NULL
);
1239 locfcinfo
->args
[0].value
= values1
[i1
];
1240 locfcinfo
->args
[0].isnull
= false;
1241 locfcinfo
->args
[1].value
= values2
[i2
];
1242 locfcinfo
->args
[1].isnull
= false;
1243 oprresult
= DatumGetBool(FunctionCallInvoke(locfcinfo
));
1244 if (locfcinfo
->isnull
|| !oprresult
)
1251 /* equal, so continue to next column */
1256 * If we didn't break out of the loop early, check for column count
1257 * mismatch. (We do not report such mismatch if we found unequal column
1258 * values; is that a feature or a bug?)
1262 if (i1
!= ncolumns1
|| i2
!= ncolumns2
)
1264 (errcode(ERRCODE_DATATYPE_MISMATCH
),
1265 errmsg("cannot compare record types with different numbers of columns")));
1272 ReleaseTupleDesc(tupdesc1
);
1273 ReleaseTupleDesc(tupdesc2
);
1275 /* Avoid leaking memory when handed toasted input. */
1276 PG_FREE_IF_COPY(record1
, 0);
1277 PG_FREE_IF_COPY(record2
, 1);
1279 PG_RETURN_BOOL(result
);
1283 record_ne(PG_FUNCTION_ARGS
)
1285 PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo
)));
1289 record_lt(PG_FUNCTION_ARGS
)
1291 PG_RETURN_BOOL(record_cmp(fcinfo
) < 0);
1295 record_gt(PG_FUNCTION_ARGS
)
1297 PG_RETURN_BOOL(record_cmp(fcinfo
) > 0);
1301 record_le(PG_FUNCTION_ARGS
)
1303 PG_RETURN_BOOL(record_cmp(fcinfo
) <= 0);
1307 record_ge(PG_FUNCTION_ARGS
)
1309 PG_RETURN_BOOL(record_cmp(fcinfo
) >= 0);
1313 btrecordcmp(PG_FUNCTION_ARGS
)
1315 PG_RETURN_INT32(record_cmp(fcinfo
));
1319 record_larger(PG_FUNCTION_ARGS
)
1321 if (record_cmp(fcinfo
) > 0)
1322 PG_RETURN_DATUM(PG_GETARG_DATUM(0));
1324 PG_RETURN_DATUM(PG_GETARG_DATUM(1));
1328 record_smaller(PG_FUNCTION_ARGS
)
1330 if (record_cmp(fcinfo
) < 0)
1331 PG_RETURN_DATUM(PG_GETARG_DATUM(0));
1333 PG_RETURN_DATUM(PG_GETARG_DATUM(1));
1338 * record_image_cmp :
1339 * Internal byte-oriented comparison function for records.
1341 * Returns -1, 0 or 1
1343 * Note: The normal concepts of "equality" do not apply here; different
1344 * representation of values considered to be equal are not considered to be
1345 * identical. As an example, for the citext type 'A' and 'a' are equal, but
1346 * they are not identical.
1349 record_image_cmp(FunctionCallInfo fcinfo
)
1351 HeapTupleHeader record1
= PG_GETARG_HEAPTUPLEHEADER(0);
1352 HeapTupleHeader record2
= PG_GETARG_HEAPTUPLEHEADER(1);
1360 HeapTupleData tuple1
;
1361 HeapTupleData tuple2
;
1364 RecordCompareData
*my_extra
;
1374 /* Extract type info from the tuples */
1375 tupType1
= HeapTupleHeaderGetTypeId(record1
);
1376 tupTypmod1
= HeapTupleHeaderGetTypMod(record1
);
1377 tupdesc1
= lookup_rowtype_tupdesc(tupType1
, tupTypmod1
);
1378 ncolumns1
= tupdesc1
->natts
;
1379 tupType2
= HeapTupleHeaderGetTypeId(record2
);
1380 tupTypmod2
= HeapTupleHeaderGetTypMod(record2
);
1381 tupdesc2
= lookup_rowtype_tupdesc(tupType2
, tupTypmod2
);
1382 ncolumns2
= tupdesc2
->natts
;
1384 /* Build temporary HeapTuple control structures */
1385 tuple1
.t_len
= HeapTupleHeaderGetDatumLength(record1
);
1386 ItemPointerSetInvalid(&(tuple1
.t_self
));
1387 tuple1
.t_tableOid
= InvalidOid
;
1388 tuple1
.t_data
= record1
;
1389 tuple2
.t_len
= HeapTupleHeaderGetDatumLength(record2
);
1390 ItemPointerSetInvalid(&(tuple2
.t_self
));
1391 tuple2
.t_tableOid
= InvalidOid
;
1392 tuple2
.t_data
= record2
;
1395 * We arrange to look up the needed comparison info just once per series
1396 * of calls, assuming the record types don't change underneath us.
1398 ncols
= Max(ncolumns1
, ncolumns2
);
1399 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
1400 if (my_extra
== NULL
||
1401 my_extra
->ncolumns
< ncols
)
1403 fcinfo
->flinfo
->fn_extra
=
1404 MemoryContextAlloc(fcinfo
->flinfo
->fn_mcxt
,
1405 offsetof(RecordCompareData
, columns
) +
1406 ncols
* sizeof(ColumnCompareData
));
1407 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
1408 my_extra
->ncolumns
= ncols
;
1409 my_extra
->record1_type
= InvalidOid
;
1410 my_extra
->record1_typmod
= 0;
1411 my_extra
->record2_type
= InvalidOid
;
1412 my_extra
->record2_typmod
= 0;
1415 if (my_extra
->record1_type
!= tupType1
||
1416 my_extra
->record1_typmod
!= tupTypmod1
||
1417 my_extra
->record2_type
!= tupType2
||
1418 my_extra
->record2_typmod
!= tupTypmod2
)
1420 MemSet(my_extra
->columns
, 0, ncols
* sizeof(ColumnCompareData
));
1421 my_extra
->record1_type
= tupType1
;
1422 my_extra
->record1_typmod
= tupTypmod1
;
1423 my_extra
->record2_type
= tupType2
;
1424 my_extra
->record2_typmod
= tupTypmod2
;
1427 /* Break down the tuples into fields */
1428 values1
= (Datum
*) palloc(ncolumns1
* sizeof(Datum
));
1429 nulls1
= (bool *) palloc(ncolumns1
* sizeof(bool));
1430 heap_deform_tuple(&tuple1
, tupdesc1
, values1
, nulls1
);
1431 values2
= (Datum
*) palloc(ncolumns2
* sizeof(Datum
));
1432 nulls2
= (bool *) palloc(ncolumns2
* sizeof(bool));
1433 heap_deform_tuple(&tuple2
, tupdesc2
, values2
, nulls2
);
1436 * Scan corresponding columns, allowing for dropped columns in different
1437 * places in the two rows. i1 and i2 are physical column indexes, j is
1438 * the logical column index.
1441 while (i1
< ncolumns1
|| i2
< ncolumns2
)
1443 Form_pg_attribute att1
;
1444 Form_pg_attribute att2
;
1447 * Skip dropped columns
1449 if (i1
< ncolumns1
&& TupleDescAttr(tupdesc1
, i1
)->attisdropped
)
1454 if (i2
< ncolumns2
&& TupleDescAttr(tupdesc2
, i2
)->attisdropped
)
1459 if (i1
>= ncolumns1
|| i2
>= ncolumns2
)
1460 break; /* we'll deal with mismatch below loop */
1462 att1
= TupleDescAttr(tupdesc1
, i1
);
1463 att2
= TupleDescAttr(tupdesc2
, i2
);
1466 * Have two matching columns, they must be same type
1468 if (att1
->atttypid
!= att2
->atttypid
)
1470 (errcode(ERRCODE_DATATYPE_MISMATCH
),
1471 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1472 format_type_be(att1
->atttypid
),
1473 format_type_be(att2
->atttypid
),
1477 * The same type should have the same length (or both should be
1480 Assert(att1
->attlen
== att2
->attlen
);
1483 * We consider two NULLs equal; NULL > not-NULL.
1485 if (!nulls1
[i1
] || !nulls2
[i2
])
1491 /* arg1 is greater than arg2 */
1497 /* arg1 is less than arg2 */
1502 /* Compare the pair of elements */
1505 if (values1
[i1
] != values2
[i2
])
1506 cmpresult
= (values1
[i1
] < values2
[i2
]) ? -1 : 1;
1508 else if (att1
->attlen
> 0)
1510 cmpresult
= memcmp(DatumGetPointer(values1
[i1
]),
1511 DatumGetPointer(values2
[i2
]),
1514 else if (att1
->attlen
== -1)
1518 struct varlena
*arg1val
;
1519 struct varlena
*arg2val
;
1521 len1
= toast_raw_datum_size(values1
[i1
]);
1522 len2
= toast_raw_datum_size(values2
[i2
]);
1523 arg1val
= PG_DETOAST_DATUM_PACKED(values1
[i1
]);
1524 arg2val
= PG_DETOAST_DATUM_PACKED(values2
[i2
]);
1526 cmpresult
= memcmp(VARDATA_ANY(arg1val
),
1527 VARDATA_ANY(arg2val
),
1528 Min(len1
, len2
) - VARHDRSZ
);
1529 if ((cmpresult
== 0) && (len1
!= len2
))
1530 cmpresult
= (len1
< len2
) ? -1 : 1;
1532 if ((Pointer
) arg1val
!= (Pointer
) values1
[i1
])
1534 if ((Pointer
) arg2val
!= (Pointer
) values2
[i2
])
1538 elog(ERROR
, "unexpected attlen: %d", att1
->attlen
);
1542 /* arg1 is less than arg2 */
1546 else if (cmpresult
> 0)
1548 /* arg1 is greater than arg2 */
1554 /* equal, so continue to next column */
1559 * If we didn't break out of the loop early, check for column count
1560 * mismatch. (We do not report such mismatch if we found unequal column
1561 * values; is that a feature or a bug?)
1565 if (i1
!= ncolumns1
|| i2
!= ncolumns2
)
1567 (errcode(ERRCODE_DATATYPE_MISMATCH
),
1568 errmsg("cannot compare record types with different numbers of columns")));
1575 ReleaseTupleDesc(tupdesc1
);
1576 ReleaseTupleDesc(tupdesc2
);
1578 /* Avoid leaking memory when handed toasted input. */
1579 PG_FREE_IF_COPY(record1
, 0);
1580 PG_FREE_IF_COPY(record2
, 1);
1587 * compares two records for identical contents, based on byte images
1589 * returns true if the records are identical, false otherwise.
1591 * Note: we do not use record_image_cmp here, since we can avoid
1592 * de-toasting for unequal lengths this way.
1595 record_image_eq(PG_FUNCTION_ARGS
)
1597 HeapTupleHeader record1
= PG_GETARG_HEAPTUPLEHEADER(0);
1598 HeapTupleHeader record2
= PG_GETARG_HEAPTUPLEHEADER(1);
1606 HeapTupleData tuple1
;
1607 HeapTupleData tuple2
;
1610 RecordCompareData
*my_extra
;
1620 /* Extract type info from the tuples */
1621 tupType1
= HeapTupleHeaderGetTypeId(record1
);
1622 tupTypmod1
= HeapTupleHeaderGetTypMod(record1
);
1623 tupdesc1
= lookup_rowtype_tupdesc(tupType1
, tupTypmod1
);
1624 ncolumns1
= tupdesc1
->natts
;
1625 tupType2
= HeapTupleHeaderGetTypeId(record2
);
1626 tupTypmod2
= HeapTupleHeaderGetTypMod(record2
);
1627 tupdesc2
= lookup_rowtype_tupdesc(tupType2
, tupTypmod2
);
1628 ncolumns2
= tupdesc2
->natts
;
1630 /* Build temporary HeapTuple control structures */
1631 tuple1
.t_len
= HeapTupleHeaderGetDatumLength(record1
);
1632 ItemPointerSetInvalid(&(tuple1
.t_self
));
1633 tuple1
.t_tableOid
= InvalidOid
;
1634 tuple1
.t_data
= record1
;
1635 tuple2
.t_len
= HeapTupleHeaderGetDatumLength(record2
);
1636 ItemPointerSetInvalid(&(tuple2
.t_self
));
1637 tuple2
.t_tableOid
= InvalidOid
;
1638 tuple2
.t_data
= record2
;
1641 * We arrange to look up the needed comparison info just once per series
1642 * of calls, assuming the record types don't change underneath us.
1644 ncols
= Max(ncolumns1
, ncolumns2
);
1645 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
1646 if (my_extra
== NULL
||
1647 my_extra
->ncolumns
< ncols
)
1649 fcinfo
->flinfo
->fn_extra
=
1650 MemoryContextAlloc(fcinfo
->flinfo
->fn_mcxt
,
1651 offsetof(RecordCompareData
, columns
) +
1652 ncols
* sizeof(ColumnCompareData
));
1653 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
1654 my_extra
->ncolumns
= ncols
;
1655 my_extra
->record1_type
= InvalidOid
;
1656 my_extra
->record1_typmod
= 0;
1657 my_extra
->record2_type
= InvalidOid
;
1658 my_extra
->record2_typmod
= 0;
1661 if (my_extra
->record1_type
!= tupType1
||
1662 my_extra
->record1_typmod
!= tupTypmod1
||
1663 my_extra
->record2_type
!= tupType2
||
1664 my_extra
->record2_typmod
!= tupTypmod2
)
1666 MemSet(my_extra
->columns
, 0, ncols
* sizeof(ColumnCompareData
));
1667 my_extra
->record1_type
= tupType1
;
1668 my_extra
->record1_typmod
= tupTypmod1
;
1669 my_extra
->record2_type
= tupType2
;
1670 my_extra
->record2_typmod
= tupTypmod2
;
1673 /* Break down the tuples into fields */
1674 values1
= (Datum
*) palloc(ncolumns1
* sizeof(Datum
));
1675 nulls1
= (bool *) palloc(ncolumns1
* sizeof(bool));
1676 heap_deform_tuple(&tuple1
, tupdesc1
, values1
, nulls1
);
1677 values2
= (Datum
*) palloc(ncolumns2
* sizeof(Datum
));
1678 nulls2
= (bool *) palloc(ncolumns2
* sizeof(bool));
1679 heap_deform_tuple(&tuple2
, tupdesc2
, values2
, nulls2
);
1682 * Scan corresponding columns, allowing for dropped columns in different
1683 * places in the two rows. i1 and i2 are physical column indexes, j is
1684 * the logical column index.
1687 while (i1
< ncolumns1
|| i2
< ncolumns2
)
1689 Form_pg_attribute att1
;
1690 Form_pg_attribute att2
;
1693 * Skip dropped columns
1695 if (i1
< ncolumns1
&& TupleDescAttr(tupdesc1
, i1
)->attisdropped
)
1700 if (i2
< ncolumns2
&& TupleDescAttr(tupdesc2
, i2
)->attisdropped
)
1705 if (i1
>= ncolumns1
|| i2
>= ncolumns2
)
1706 break; /* we'll deal with mismatch below loop */
1708 att1
= TupleDescAttr(tupdesc1
, i1
);
1709 att2
= TupleDescAttr(tupdesc2
, i2
);
1712 * Have two matching columns, they must be same type
1714 if (att1
->atttypid
!= att2
->atttypid
)
1716 (errcode(ERRCODE_DATATYPE_MISMATCH
),
1717 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1718 format_type_be(att1
->atttypid
),
1719 format_type_be(att2
->atttypid
),
1723 * We consider two NULLs equal; NULL > not-NULL.
1725 if (!nulls1
[i1
] || !nulls2
[i2
])
1727 if (nulls1
[i1
] || nulls2
[i2
])
1733 /* Compare the pair of elements */
1734 result
= datum_image_eq(values1
[i1
], values2
[i2
], att1
->attbyval
, att2
->attlen
);
1739 /* equal, so continue to next column */
1744 * If we didn't break out of the loop early, check for column count
1745 * mismatch. (We do not report such mismatch if we found unequal column
1746 * values; is that a feature or a bug?)
1750 if (i1
!= ncolumns1
|| i2
!= ncolumns2
)
1752 (errcode(ERRCODE_DATATYPE_MISMATCH
),
1753 errmsg("cannot compare record types with different numbers of columns")));
1760 ReleaseTupleDesc(tupdesc1
);
1761 ReleaseTupleDesc(tupdesc2
);
1763 /* Avoid leaking memory when handed toasted input. */
1764 PG_FREE_IF_COPY(record1
, 0);
1765 PG_FREE_IF_COPY(record2
, 1);
1767 PG_RETURN_BOOL(result
);
1771 record_image_ne(PG_FUNCTION_ARGS
)
1773 PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo
)));
1777 record_image_lt(PG_FUNCTION_ARGS
)
1779 PG_RETURN_BOOL(record_image_cmp(fcinfo
) < 0);
1783 record_image_gt(PG_FUNCTION_ARGS
)
1785 PG_RETURN_BOOL(record_image_cmp(fcinfo
) > 0);
1789 record_image_le(PG_FUNCTION_ARGS
)
1791 PG_RETURN_BOOL(record_image_cmp(fcinfo
) <= 0);
1795 record_image_ge(PG_FUNCTION_ARGS
)
1797 PG_RETURN_BOOL(record_image_cmp(fcinfo
) >= 0);
1801 btrecordimagecmp(PG_FUNCTION_ARGS
)
1803 PG_RETURN_INT32(record_image_cmp(fcinfo
));
1808 * Row type hash functions
1812 hash_record(PG_FUNCTION_ARGS
)
1814 HeapTupleHeader record
= PG_GETARG_HEAPTUPLEHEADER(0);
1819 HeapTupleData tuple
;
1821 RecordCompareData
*my_extra
;
1825 check_stack_depth(); /* recurses for record-type columns */
1827 /* Extract type info from tuple */
1828 tupType
= HeapTupleHeaderGetTypeId(record
);
1829 tupTypmod
= HeapTupleHeaderGetTypMod(record
);
1830 tupdesc
= lookup_rowtype_tupdesc(tupType
, tupTypmod
);
1831 ncolumns
= tupdesc
->natts
;
1833 /* Build temporary HeapTuple control structure */
1834 tuple
.t_len
= HeapTupleHeaderGetDatumLength(record
);
1835 ItemPointerSetInvalid(&(tuple
.t_self
));
1836 tuple
.t_tableOid
= InvalidOid
;
1837 tuple
.t_data
= record
;
1840 * We arrange to look up the needed hashing info just once per series of
1841 * calls, assuming the record type doesn't change underneath us.
1843 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
1844 if (my_extra
== NULL
||
1845 my_extra
->ncolumns
< ncolumns
)
1847 fcinfo
->flinfo
->fn_extra
=
1848 MemoryContextAlloc(fcinfo
->flinfo
->fn_mcxt
,
1849 offsetof(RecordCompareData
, columns
) +
1850 ncolumns
* sizeof(ColumnCompareData
));
1851 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
1852 my_extra
->ncolumns
= ncolumns
;
1853 my_extra
->record1_type
= InvalidOid
;
1854 my_extra
->record1_typmod
= 0;
1857 if (my_extra
->record1_type
!= tupType
||
1858 my_extra
->record1_typmod
!= tupTypmod
)
1860 MemSet(my_extra
->columns
, 0, ncolumns
* sizeof(ColumnCompareData
));
1861 my_extra
->record1_type
= tupType
;
1862 my_extra
->record1_typmod
= tupTypmod
;
1865 /* Break down the tuple into fields */
1866 values
= (Datum
*) palloc(ncolumns
* sizeof(Datum
));
1867 nulls
= (bool *) palloc(ncolumns
* sizeof(bool));
1868 heap_deform_tuple(&tuple
, tupdesc
, values
, nulls
);
1870 for (int i
= 0; i
< ncolumns
; i
++)
1872 Form_pg_attribute att
;
1873 TypeCacheEntry
*typentry
;
1874 uint32 element_hash
;
1876 att
= TupleDescAttr(tupdesc
, i
);
1878 if (att
->attisdropped
)
1882 * Lookup the hash function if not done already
1884 typentry
= my_extra
->columns
[i
].typentry
;
1885 if (typentry
== NULL
||
1886 typentry
->type_id
!= att
->atttypid
)
1888 typentry
= lookup_type_cache(att
->atttypid
,
1889 TYPECACHE_HASH_PROC_FINFO
);
1890 if (!OidIsValid(typentry
->hash_proc_finfo
.fn_oid
))
1892 (errcode(ERRCODE_UNDEFINED_FUNCTION
),
1893 errmsg("could not identify a hash function for type %s",
1894 format_type_be(typentry
->type_id
))));
1895 my_extra
->columns
[i
].typentry
= typentry
;
1898 /* Compute hash of element */
1905 LOCAL_FCINFO(locfcinfo
, 1);
1907 InitFunctionCallInfoData(*locfcinfo
, &typentry
->hash_proc_finfo
, 1,
1908 att
->attcollation
, NULL
, NULL
);
1909 locfcinfo
->args
[0].value
= values
[i
];
1910 locfcinfo
->args
[0].isnull
= false;
1911 element_hash
= DatumGetUInt32(FunctionCallInvoke(locfcinfo
));
1913 /* We don't expect hash support functions to return null */
1914 Assert(!locfcinfo
->isnull
);
1917 /* see hash_array() */
1918 result
= (result
<< 5) - result
+ element_hash
;
1923 ReleaseTupleDesc(tupdesc
);
1925 /* Avoid leaking memory when handed toasted input. */
1926 PG_FREE_IF_COPY(record
, 0);
1928 PG_RETURN_UINT32(result
);
1932 hash_record_extended(PG_FUNCTION_ARGS
)
1934 HeapTupleHeader record
= PG_GETARG_HEAPTUPLEHEADER(0);
1935 uint64 seed
= PG_GETARG_INT64(1);
1940 HeapTupleData tuple
;
1942 RecordCompareData
*my_extra
;
1946 check_stack_depth(); /* recurses for record-type columns */
1948 /* Extract type info from tuple */
1949 tupType
= HeapTupleHeaderGetTypeId(record
);
1950 tupTypmod
= HeapTupleHeaderGetTypMod(record
);
1951 tupdesc
= lookup_rowtype_tupdesc(tupType
, tupTypmod
);
1952 ncolumns
= tupdesc
->natts
;
1954 /* Build temporary HeapTuple control structure */
1955 tuple
.t_len
= HeapTupleHeaderGetDatumLength(record
);
1956 ItemPointerSetInvalid(&(tuple
.t_self
));
1957 tuple
.t_tableOid
= InvalidOid
;
1958 tuple
.t_data
= record
;
1961 * We arrange to look up the needed hashing info just once per series of
1962 * calls, assuming the record type doesn't change underneath us.
1964 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
1965 if (my_extra
== NULL
||
1966 my_extra
->ncolumns
< ncolumns
)
1968 fcinfo
->flinfo
->fn_extra
=
1969 MemoryContextAlloc(fcinfo
->flinfo
->fn_mcxt
,
1970 offsetof(RecordCompareData
, columns
) +
1971 ncolumns
* sizeof(ColumnCompareData
));
1972 my_extra
= (RecordCompareData
*) fcinfo
->flinfo
->fn_extra
;
1973 my_extra
->ncolumns
= ncolumns
;
1974 my_extra
->record1_type
= InvalidOid
;
1975 my_extra
->record1_typmod
= 0;
1978 if (my_extra
->record1_type
!= tupType
||
1979 my_extra
->record1_typmod
!= tupTypmod
)
1981 MemSet(my_extra
->columns
, 0, ncolumns
* sizeof(ColumnCompareData
));
1982 my_extra
->record1_type
= tupType
;
1983 my_extra
->record1_typmod
= tupTypmod
;
1986 /* Break down the tuple into fields */
1987 values
= (Datum
*) palloc(ncolumns
* sizeof(Datum
));
1988 nulls
= (bool *) palloc(ncolumns
* sizeof(bool));
1989 heap_deform_tuple(&tuple
, tupdesc
, values
, nulls
);
1991 for (int i
= 0; i
< ncolumns
; i
++)
1993 Form_pg_attribute att
;
1994 TypeCacheEntry
*typentry
;
1995 uint64 element_hash
;
1997 att
= TupleDescAttr(tupdesc
, i
);
1999 if (att
->attisdropped
)
2003 * Lookup the hash function if not done already
2005 typentry
= my_extra
->columns
[i
].typentry
;
2006 if (typentry
== NULL
||
2007 typentry
->type_id
!= att
->atttypid
)
2009 typentry
= lookup_type_cache(att
->atttypid
,
2010 TYPECACHE_HASH_EXTENDED_PROC_FINFO
);
2011 if (!OidIsValid(typentry
->hash_extended_proc_finfo
.fn_oid
))
2013 (errcode(ERRCODE_UNDEFINED_FUNCTION
),
2014 errmsg("could not identify an extended hash function for type %s",
2015 format_type_be(typentry
->type_id
))));
2016 my_extra
->columns
[i
].typentry
= typentry
;
2019 /* Compute hash of element */
2026 LOCAL_FCINFO(locfcinfo
, 2);
2028 InitFunctionCallInfoData(*locfcinfo
, &typentry
->hash_extended_proc_finfo
, 2,
2029 att
->attcollation
, NULL
, NULL
);
2030 locfcinfo
->args
[0].value
= values
[i
];
2031 locfcinfo
->args
[0].isnull
= false;
2032 locfcinfo
->args
[1].value
= Int64GetDatum(seed
);
2033 locfcinfo
->args
[0].isnull
= false;
2034 element_hash
= DatumGetUInt64(FunctionCallInvoke(locfcinfo
));
2036 /* We don't expect hash support functions to return null */
2037 Assert(!locfcinfo
->isnull
);
2040 /* see hash_array_extended() */
2041 result
= (result
<< 5) - result
+ element_hash
;
2046 ReleaseTupleDesc(tupdesc
);
2048 /* Avoid leaking memory when handed toasted input. */
2049 PG_FREE_IF_COPY(record
, 0);
2051 PG_RETURN_UINT64(result
);