1 /*-------------------------------------------------------------------------
4 * I/O and comparison functions for generic composite types.
6 * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.26 2009/12/19 00:47:57 momjian Exp $
13 *-------------------------------------------------------------------------
19 #include "catalog/pg_type.h"
20 #include "libpq/pqformat.h"
21 #include "utils/builtins.h"
22 #include "utils/lsyscache.h"
23 #include "utils/typcache.h"
27 * structure to cache metadata needed for record I/O
29 typedef struct ColumnIOData
37 typedef struct RecordIOData
42 ColumnIOData columns[1]; /* VARIABLE LENGTH ARRAY */
46 * structure to cache metadata needed for record comparison
48 typedef struct ColumnCompareData
50 TypeCacheEntry *typentry; /* has everything we need, actually */
53 typedef struct RecordCompareData
55 int ncolumns; /* allocated length of columns[] */
60 ColumnCompareData columns[1]; /* VARIABLE LENGTH ARRAY */
65 * record_in - input routine for any composite type.
68 record_in(PG_FUNCTION_ARGS)
70 char *string = PG_GETARG_CSTRING(0);
71 Oid tupType = PG_GETARG_OID(1);
74 int32 typmod = PG_GETARG_INT32(2);
76 HeapTupleHeader result;
80 RecordIOData *my_extra;
81 bool needComma = false;
90 * Use the passed type unless it's RECORD; we can't support input of
91 * anonymous types, mainly because there's no good way to figure out which
92 * anonymous type is wanted. Note that for RECORD, what we'll probably
93 * actually get is RECORD's typelem, ie, zero.
95 if (tupType == InvalidOid || tupType == RECORDOID)
97 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
98 errmsg("input of anonymous composite types is not implemented")));
99 tupTypmod = -1; /* for all non-anonymous types */
101 * This comes from the composite type's pg_type.oid and
102 * stores system oids in user tables, specifically DatumTupleFields.
103 * This oid must be preserved by binary upgrades.
105 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
106 ncolumns = tupdesc->natts;
109 * We arrange to look up the needed I/O info just once per series of
110 * calls, assuming the record type doesn't change underneath us.
112 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
113 if (my_extra == NULL ||
114 my_extra->ncolumns != ncolumns)
116 fcinfo->flinfo->fn_extra =
117 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
118 sizeof(RecordIOData) - sizeof(ColumnIOData)
119 + ncolumns * sizeof(ColumnIOData));
120 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
121 my_extra->record_type = InvalidOid;
122 my_extra->record_typmod = 0;
125 if (my_extra->record_type != tupType ||
126 my_extra->record_typmod != tupTypmod)
129 sizeof(RecordIOData) - sizeof(ColumnIOData)
130 + ncolumns * sizeof(ColumnIOData));
131 my_extra->record_type = tupType;
132 my_extra->record_typmod = tupTypmod;
133 my_extra->ncolumns = ncolumns;
136 values = (Datum *) palloc(ncolumns * sizeof(Datum));
137 nulls = (bool *) palloc(ncolumns * sizeof(bool));
140 * Scan the string. We use "buf" to accumulate the de-quoted data for
141 * each column, which is then fed to the appropriate input converter.
144 /* Allow leading whitespace */
145 while (*ptr && isspace((unsigned char) *ptr))
149 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
150 errmsg("malformed record literal: \"%s\"", string),
151 errdetail("Missing left parenthesis.")));
153 initStringInfo(&buf);
155 for (i = 0; i < ncolumns; i++)
157 ColumnIOData *column_info = &my_extra->columns[i];
158 Oid column_type = tupdesc->attrs[i]->atttypid;
161 /* Ignore dropped columns in datatype, but fill with nulls */
162 if (tupdesc->attrs[i]->attisdropped)
164 values[i] = (Datum) 0;
171 /* Skip comma that separates prior field from this one */
175 /* *ptr must be ')' */
177 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
178 errmsg("malformed record literal: \"%s\"", string),
179 errdetail("Too few columns.")));
182 /* Check for null: completely empty input means null */
183 if (*ptr == ',' || *ptr == ')')
190 /* Extract string for this column */
191 bool inquote = false;
193 resetStringInfo(&buf);
194 while (inquote || !(*ptr == ',' || *ptr == ')'))
200 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
201 errmsg("malformed record literal: \"%s\"",
203 errdetail("Unexpected end of input.")));
208 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
209 errmsg("malformed record literal: \"%s\"",
211 errdetail("Unexpected end of input.")));
212 appendStringInfoChar(&buf, *ptr++);
218 else if (*ptr == '\"')
220 /* doubled quote within quote sequence */
221 appendStringInfoChar(&buf, *ptr++);
227 appendStringInfoChar(&buf, ch);
230 column_data = buf.data;
235 * Convert the column value
237 if (column_info->column_type != column_type)
239 getTypeInputInfo(column_type,
240 &column_info->typiofunc,
241 &column_info->typioparam);
242 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
243 fcinfo->flinfo->fn_mcxt);
244 column_info->column_type = column_type;
247 values[i] = InputFunctionCall(&column_info->proc,
249 column_info->typioparam,
250 tupdesc->attrs[i]->atttypmod);
253 * Prep for next column
260 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
261 errmsg("malformed record literal: \"%s\"", string),
262 errdetail("Too many columns.")));
263 /* Allow trailing whitespace */
264 while (*ptr && isspace((unsigned char) *ptr))
268 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
269 errmsg("malformed record literal: \"%s\"", string),
270 errdetail("Junk after right parenthesis.")));
272 tuple = heap_form_tuple(tupdesc, values, nulls);
275 * We cannot return tuple->t_data because heap_form_tuple allocates it as
276 * part of a larger chunk, and our caller may expect to be able to pfree
277 * our result. So must copy the info into a new palloc chunk.
279 result = (HeapTupleHeader) palloc(tuple->t_len);
280 memcpy(result, tuple->t_data, tuple->t_len);
282 heap_freetuple(tuple);
286 ReleaseTupleDesc(tupdesc);
288 PG_RETURN_HEAPTUPLEHEADER(result);
292 * record_out - output routine for any composite type.
295 record_out(PG_FUNCTION_ARGS)
297 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
302 RecordIOData *my_extra;
303 bool needComma = false;
310 /* Extract type info from the tuple itself */
311 tupType = HeapTupleHeaderGetTypeId(rec);
312 tupTypmod = HeapTupleHeaderGetTypMod(rec);
313 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
314 ncolumns = tupdesc->natts;
316 /* Build a temporary HeapTuple control structure */
317 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
318 ItemPointerSetInvalid(&(tuple.t_self));
319 tuple.t_tableOid = InvalidOid;
323 * We arrange to look up the needed I/O info just once per series of
324 * calls, assuming the record type doesn't change underneath us.
326 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
327 if (my_extra == NULL ||
328 my_extra->ncolumns != ncolumns)
330 fcinfo->flinfo->fn_extra =
331 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
332 sizeof(RecordIOData) - sizeof(ColumnIOData)
333 + ncolumns * sizeof(ColumnIOData));
334 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
335 my_extra->record_type = InvalidOid;
336 my_extra->record_typmod = 0;
339 if (my_extra->record_type != tupType ||
340 my_extra->record_typmod != tupTypmod)
343 sizeof(RecordIOData) - sizeof(ColumnIOData)
344 + ncolumns * sizeof(ColumnIOData));
345 my_extra->record_type = tupType;
346 my_extra->record_typmod = tupTypmod;
347 my_extra->ncolumns = ncolumns;
350 values = (Datum *) palloc(ncolumns * sizeof(Datum));
351 nulls = (bool *) palloc(ncolumns * sizeof(bool));
353 /* Break down the tuple into fields */
354 heap_deform_tuple(&tuple, tupdesc, values, nulls);
356 /* And build the result string */
357 initStringInfo(&buf);
359 appendStringInfoChar(&buf, '(');
361 for (i = 0; i < ncolumns; i++)
363 ColumnIOData *column_info = &my_extra->columns[i];
364 Oid column_type = tupdesc->attrs[i]->atttypid;
369 /* Ignore dropped columns in datatype */
370 if (tupdesc->attrs[i]->attisdropped)
374 appendStringInfoChar(&buf, ',');
379 /* emit nothing... */
384 * Convert the column value to text
386 if (column_info->column_type != column_type)
390 getTypeOutputInfo(column_type,
391 &column_info->typiofunc,
393 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
394 fcinfo->flinfo->fn_mcxt);
395 column_info->column_type = column_type;
398 value = OutputFunctionCall(&column_info->proc, values[i]);
400 /* Detect whether we need double quotes for this value */
401 nq = (value[0] == '\0'); /* force quotes for empty string */
402 for (tmp = value; *tmp; tmp++)
406 if (ch == '"' || ch == '\\' ||
407 ch == '(' || ch == ')' || ch == ',' ||
408 isspace((unsigned char) ch))
415 /* And emit the string */
417 appendStringInfoChar(&buf, '"');
418 for (tmp = value; *tmp; tmp++)
422 if (ch == '"' || ch == '\\')
423 appendStringInfoChar(&buf, ch);
424 appendStringInfoChar(&buf, ch);
427 appendStringInfoChar(&buf, '"');
430 appendStringInfoChar(&buf, ')');
434 ReleaseTupleDesc(tupdesc);
436 PG_RETURN_CSTRING(buf.data);
440 * record_recv - binary input routine for any composite type.
443 record_recv(PG_FUNCTION_ARGS)
445 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
446 Oid tupType = PG_GETARG_OID(1);
449 int32 typmod = PG_GETARG_INT32(2);
451 HeapTupleHeader result;
455 RecordIOData *my_extra;
464 * Use the passed type unless it's RECORD; we can't support input of
465 * anonymous types, mainly because there's no good way to figure out which
466 * anonymous type is wanted. Note that for RECORD, what we'll probably
467 * actually get is RECORD's typelem, ie, zero.
469 if (tupType == InvalidOid || tupType == RECORDOID)
471 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
472 errmsg("input of anonymous composite types is not implemented")));
473 tupTypmod = -1; /* for all non-anonymous types */
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 sizeof(RecordIOData) - sizeof(ColumnIOData)
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 sizeof(RecordIOData) - sizeof(ColumnIOData)
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 sizeof(RecordIOData) - sizeof(ColumnIOData)
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 sizeof(RecordIOData) - sizeof(ColumnIOData)
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;
717 /* Ignore dropped columns in datatype */
718 if (tupdesc->attrs[i]->attisdropped)
721 pq_sendint(&buf, column_type, sizeof(Oid));
725 /* emit -1 data length to signify a NULL */
726 pq_sendint(&buf, -1, 4);
731 * Convert the column value to binary
733 if (column_info->column_type != column_type)
737 getTypeBinaryOutputInfo(column_type,
738 &column_info->typiofunc,
740 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
741 fcinfo->flinfo->fn_mcxt);
742 column_info->column_type = column_type;
745 outputbytes = SendFunctionCall(&column_info->proc, values[i]);
747 /* We assume the result will not have been toasted */
748 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
749 pq_sendbytes(&buf, VARDATA(outputbytes),
750 VARSIZE(outputbytes) - VARHDRSZ);
756 ReleaseTupleDesc(tupdesc);
758 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
764 * Internal comparison function for records.
768 * Do not assume that the two inputs are exactly the same record type;
769 * for instance we might be comparing an anonymous ROW() construct against a
770 * named composite type. We will compare as long as they have the same number
771 * of non-dropped columns of the same types.
774 record_cmp(FunctionCallInfo fcinfo)
776 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
777 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
785 HeapTupleData tuple1;
786 HeapTupleData tuple2;
789 RecordCompareData *my_extra;
799 /* Extract type info from the tuples */
800 tupType1 = HeapTupleHeaderGetTypeId(record1);
801 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
802 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
803 ncolumns1 = tupdesc1->natts;
804 tupType2 = HeapTupleHeaderGetTypeId(record2);
805 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
806 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
807 ncolumns2 = tupdesc2->natts;
809 /* Build temporary HeapTuple control structures */
810 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
811 ItemPointerSetInvalid(&(tuple1.t_self));
812 tuple1.t_tableOid = InvalidOid;
813 tuple1.t_data = record1;
814 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
815 ItemPointerSetInvalid(&(tuple2.t_self));
816 tuple2.t_tableOid = InvalidOid;
817 tuple2.t_data = record2;
820 * We arrange to look up the needed comparison info just once per series
821 * of calls, assuming the record types don't change underneath us.
823 ncols = Max(ncolumns1, ncolumns2);
824 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
825 if (my_extra == NULL ||
826 my_extra->ncolumns < ncols)
828 fcinfo->flinfo->fn_extra =
829 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
830 sizeof(RecordCompareData) - sizeof(ColumnCompareData)
831 + ncols * sizeof(ColumnCompareData));
832 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
833 my_extra->ncolumns = ncols;
834 my_extra->record1_type = InvalidOid;
835 my_extra->record1_typmod = 0;
836 my_extra->record2_type = InvalidOid;
837 my_extra->record2_typmod = 0;
840 if (my_extra->record1_type != tupType1 ||
841 my_extra->record1_typmod != tupTypmod1 ||
842 my_extra->record2_type != tupType2 ||
843 my_extra->record2_typmod != tupTypmod2)
845 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
846 my_extra->record1_type = tupType1;
847 my_extra->record1_typmod = tupTypmod1;
848 my_extra->record2_type = tupType2;
849 my_extra->record2_typmod = tupTypmod2;
852 /* Break down the tuples into fields */
853 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
854 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
855 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
856 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
857 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
858 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
861 * Scan corresponding columns, allowing for dropped columns in different
862 * places in the two rows. i1 and i2 are physical column indexes, j is
863 * the logical column index.
866 while (i1 < ncolumns1 || i2 < ncolumns2)
868 TypeCacheEntry *typentry;
869 FunctionCallInfoData locfcinfo;
873 * Skip dropped columns
875 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
880 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
885 if (i1 >= ncolumns1 || i2 >= ncolumns2)
886 break; /* we'll deal with mismatch below loop */
889 * Have two matching columns, they must be same type
891 if (tupdesc1->attrs[i1]->atttypid !=
892 tupdesc2->attrs[i2]->atttypid)
894 (errcode(ERRCODE_DATATYPE_MISMATCH),
895 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
896 format_type_be(tupdesc1->attrs[i1]->atttypid),
897 format_type_be(tupdesc2->attrs[i2]->atttypid),
901 * Lookup the comparison function if not done already
903 typentry = my_extra->columns[j].typentry;
904 if (typentry == NULL ||
905 typentry->type_id != tupdesc1->attrs[i1]->atttypid)
907 typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
908 TYPECACHE_CMP_PROC_FINFO);
909 if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
911 (errcode(ERRCODE_UNDEFINED_FUNCTION),
912 errmsg("could not identify a comparison function for type %s",
913 format_type_be(typentry->type_id))));
914 my_extra->columns[j].typentry = typentry;
918 * We consider two NULLs equal; NULL > not-NULL.
920 if (!nulls1[i1] || !nulls2[i2])
924 /* arg1 is greater than arg2 */
930 /* arg1 is less than arg2 */
935 /* Compare the pair of elements */
936 InitFunctionCallInfoData(locfcinfo, &typentry->cmp_proc_finfo, 2,
938 locfcinfo.arg[0] = values1[i1];
939 locfcinfo.arg[1] = values2[i2];
940 locfcinfo.argnull[0] = false;
941 locfcinfo.argnull[1] = false;
942 locfcinfo.isnull = false;
943 cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
947 /* arg1 is less than arg2 */
951 else if (cmpresult > 0)
953 /* arg1 is greater than arg2 */
959 /* equal, so continue to next column */
964 * If we didn't break out of the loop early, check for column count
965 * mismatch. (We do not report such mismatch if we found unequal column
966 * values; is that a feature or a bug?)
970 if (i1 != ncolumns1 || i2 != ncolumns2)
972 (errcode(ERRCODE_DATATYPE_MISMATCH),
973 errmsg("cannot compare record types with different numbers of columns")));
980 ReleaseTupleDesc(tupdesc1);
981 ReleaseTupleDesc(tupdesc2);
983 /* Avoid leaking memory when handed toasted input. */
984 PG_FREE_IF_COPY(record1, 0);
985 PG_FREE_IF_COPY(record2, 1);
992 * compares two records for equality
994 * returns true if the records are equal, false otherwise.
996 * Note: we do not use record_cmp here, since equality may be meaningful in
997 * datatypes that don't have a total ordering (and hence no btree support).
1000 record_eq(PG_FUNCTION_ARGS)
1002 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1003 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1011 HeapTupleData tuple1;
1012 HeapTupleData tuple2;
1015 RecordCompareData *my_extra;
1025 /* Extract type info from the tuples */
1026 tupType1 = HeapTupleHeaderGetTypeId(record1);
1027 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1028 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1029 ncolumns1 = tupdesc1->natts;
1030 tupType2 = HeapTupleHeaderGetTypeId(record2);
1031 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1032 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1033 ncolumns2 = tupdesc2->natts;
1035 /* Build temporary HeapTuple control structures */
1036 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1037 ItemPointerSetInvalid(&(tuple1.t_self));
1038 tuple1.t_tableOid = InvalidOid;
1039 tuple1.t_data = record1;
1040 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1041 ItemPointerSetInvalid(&(tuple2.t_self));
1042 tuple2.t_tableOid = InvalidOid;
1043 tuple2.t_data = record2;
1046 * We arrange to look up the needed comparison info just once per series
1047 * of calls, assuming the record types don't change underneath us.
1049 ncols = Max(ncolumns1, ncolumns2);
1050 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1051 if (my_extra == NULL ||
1052 my_extra->ncolumns < ncols)
1054 fcinfo->flinfo->fn_extra =
1055 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1056 sizeof(RecordCompareData) - sizeof(ColumnCompareData)
1057 + ncols * sizeof(ColumnCompareData));
1058 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1059 my_extra->ncolumns = ncols;
1060 my_extra->record1_type = InvalidOid;
1061 my_extra->record1_typmod = 0;
1062 my_extra->record2_type = InvalidOid;
1063 my_extra->record2_typmod = 0;
1066 if (my_extra->record1_type != tupType1 ||
1067 my_extra->record1_typmod != tupTypmod1 ||
1068 my_extra->record2_type != tupType2 ||
1069 my_extra->record2_typmod != tupTypmod2)
1071 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1072 my_extra->record1_type = tupType1;
1073 my_extra->record1_typmod = tupTypmod1;
1074 my_extra->record2_type = tupType2;
1075 my_extra->record2_typmod = tupTypmod2;
1078 /* Break down the tuples into fields */
1079 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1080 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1081 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1082 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1083 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1084 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1087 * Scan corresponding columns, allowing for dropped columns in different
1088 * places in the two rows. i1 and i2 are physical column indexes, j is
1089 * the logical column index.
1092 while (i1 < ncolumns1 || i2 < ncolumns2)
1094 TypeCacheEntry *typentry;
1095 FunctionCallInfoData locfcinfo;
1099 * Skip dropped columns
1101 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1106 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1111 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1112 break; /* we'll deal with mismatch below loop */
1115 * Have two matching columns, they must be same type
1117 if (tupdesc1->attrs[i1]->atttypid !=
1118 tupdesc2->attrs[i2]->atttypid)
1120 (errcode(ERRCODE_DATATYPE_MISMATCH),
1121 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1122 format_type_be(tupdesc1->attrs[i1]->atttypid),
1123 format_type_be(tupdesc2->attrs[i2]->atttypid),
1127 * Lookup the equality function if not done already
1129 typentry = my_extra->columns[j].typentry;
1130 if (typentry == NULL ||
1131 typentry->type_id != tupdesc1->attrs[i1]->atttypid)
1133 typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
1134 TYPECACHE_EQ_OPR_FINFO);
1135 if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1137 (errcode(ERRCODE_UNDEFINED_FUNCTION),
1138 errmsg("could not identify an equality operator for type %s",
1139 format_type_be(typentry->type_id))));
1140 my_extra->columns[j].typentry = typentry;
1144 * We consider two NULLs equal; NULL > not-NULL.
1146 if (!nulls1[i1] || !nulls2[i2])
1148 if (nulls1[i1] || nulls2[i2])
1154 /* Compare the pair of elements */
1155 InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
1157 locfcinfo.arg[0] = values1[i1];
1158 locfcinfo.arg[1] = values2[i2];
1159 locfcinfo.argnull[0] = false;
1160 locfcinfo.argnull[1] = false;
1161 locfcinfo.isnull = false;
1162 oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
1170 /* equal, so continue to next column */
1175 * If we didn't break out of the loop early, check for column count
1176 * mismatch. (We do not report such mismatch if we found unequal column
1177 * values; is that a feature or a bug?)
1181 if (i1 != ncolumns1 || i2 != ncolumns2)
1183 (errcode(ERRCODE_DATATYPE_MISMATCH),
1184 errmsg("cannot compare record types with different numbers of columns")));
1191 ReleaseTupleDesc(tupdesc1);
1192 ReleaseTupleDesc(tupdesc2);
1194 /* Avoid leaking memory when handed toasted input. */
1195 PG_FREE_IF_COPY(record1, 0);
1196 PG_FREE_IF_COPY(record2, 1);
1198 PG_RETURN_BOOL(result);
1202 record_ne(PG_FUNCTION_ARGS)
1204 PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
1208 record_lt(PG_FUNCTION_ARGS)
1210 PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
1214 record_gt(PG_FUNCTION_ARGS)
1216 PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
1220 record_le(PG_FUNCTION_ARGS)
1222 PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
1226 record_ge(PG_FUNCTION_ARGS)
1228 PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
1232 btrecordcmp(PG_FUNCTION_ARGS)
1234 PG_RETURN_INT32(record_cmp(fcinfo));