1 /*-------------------------------------------------------------------------
4 * I/O and comparison functions for generic composite types.
6 * Portions Copyright (c) 1996-2012, 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 "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 */
102 * This comes from the composite type's pg_type.oid and stores system oids
103 * in user tables, specifically DatumTupleFields. This oid must be
104 * preserved by binary upgrades.
106 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
107 ncolumns = tupdesc->natts;
110 * We arrange to look up the needed I/O info just once per series of
111 * calls, assuming the record type doesn't change underneath us.
113 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
114 if (my_extra == NULL ||
115 my_extra->ncolumns != ncolumns)
117 fcinfo->flinfo->fn_extra =
118 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
119 sizeof(RecordIOData) - sizeof(ColumnIOData)
120 + ncolumns * sizeof(ColumnIOData));
121 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
122 my_extra->record_type = InvalidOid;
123 my_extra->record_typmod = 0;
126 if (my_extra->record_type != tupType ||
127 my_extra->record_typmod != tupTypmod)
130 sizeof(RecordIOData) - sizeof(ColumnIOData)
131 + ncolumns * sizeof(ColumnIOData));
132 my_extra->record_type = tupType;
133 my_extra->record_typmod = tupTypmod;
134 my_extra->ncolumns = ncolumns;
137 values = (Datum *) palloc(ncolumns * sizeof(Datum));
138 nulls = (bool *) palloc(ncolumns * sizeof(bool));
141 * Scan the string. We use "buf" to accumulate the de-quoted data for
142 * each column, which is then fed to the appropriate input converter.
145 /* Allow leading whitespace */
146 while (*ptr && isspace((unsigned char) *ptr))
150 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
151 errmsg("malformed record literal: \"%s\"", string),
152 errdetail("Missing left parenthesis.")));
154 initStringInfo(&buf);
156 for (i = 0; i < ncolumns; i++)
158 ColumnIOData *column_info = &my_extra->columns[i];
159 Oid column_type = tupdesc->attrs[i]->atttypid;
162 /* Ignore dropped columns in datatype, but fill with nulls */
163 if (tupdesc->attrs[i]->attisdropped)
165 values[i] = (Datum) 0;
172 /* Skip comma that separates prior field from this one */
176 /* *ptr must be ')' */
178 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
179 errmsg("malformed record literal: \"%s\"", string),
180 errdetail("Too few columns.")));
183 /* Check for null: completely empty input means null */
184 if (*ptr == ',' || *ptr == ')')
191 /* Extract string for this column */
192 bool inquote = false;
194 resetStringInfo(&buf);
195 while (inquote || !(*ptr == ',' || *ptr == ')'))
201 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
202 errmsg("malformed record literal: \"%s\"",
204 errdetail("Unexpected end of input.")));
209 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
210 errmsg("malformed record literal: \"%s\"",
212 errdetail("Unexpected end of input.")));
213 appendStringInfoChar(&buf, *ptr++);
219 else if (*ptr == '\"')
221 /* doubled quote within quote sequence */
222 appendStringInfoChar(&buf, *ptr++);
228 appendStringInfoChar(&buf, ch);
231 column_data = buf.data;
236 * Convert the column value
238 if (column_info->column_type != column_type)
240 getTypeInputInfo(column_type,
241 &column_info->typiofunc,
242 &column_info->typioparam);
243 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
244 fcinfo->flinfo->fn_mcxt);
245 column_info->column_type = column_type;
248 values[i] = InputFunctionCall(&column_info->proc,
250 column_info->typioparam,
251 tupdesc->attrs[i]->atttypmod);
254 * Prep for next column
261 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
262 errmsg("malformed record literal: \"%s\"", string),
263 errdetail("Too many columns.")));
264 /* Allow trailing whitespace */
265 while (*ptr && isspace((unsigned char) *ptr))
269 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
270 errmsg("malformed record literal: \"%s\"", string),
271 errdetail("Junk after right parenthesis.")));
273 tuple = heap_form_tuple(tupdesc, values, nulls);
276 * We cannot return tuple->t_data because heap_form_tuple allocates it as
277 * part of a larger chunk, and our caller may expect to be able to pfree
278 * our result. So must copy the info into a new palloc chunk.
280 result = (HeapTupleHeader) palloc(tuple->t_len);
281 memcpy(result, tuple->t_data, tuple->t_len);
283 heap_freetuple(tuple);
287 ReleaseTupleDesc(tupdesc);
289 PG_RETURN_HEAPTUPLEHEADER(result);
293 * record_out - output routine for any composite type.
296 record_out(PG_FUNCTION_ARGS)
298 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
303 RecordIOData *my_extra;
304 bool needComma = false;
311 /* Extract type info from the tuple itself */
312 tupType = HeapTupleHeaderGetTypeId(rec);
313 tupTypmod = HeapTupleHeaderGetTypMod(rec);
314 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
315 ncolumns = tupdesc->natts;
317 /* Build a temporary HeapTuple control structure */
318 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
319 ItemPointerSetInvalid(&(tuple.t_self));
320 tuple.t_tableOid = InvalidOid;
324 * We arrange to look up the needed I/O info just once per series of
325 * calls, assuming the record type doesn't change underneath us.
327 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
328 if (my_extra == NULL ||
329 my_extra->ncolumns != ncolumns)
331 fcinfo->flinfo->fn_extra =
332 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
333 sizeof(RecordIOData) - sizeof(ColumnIOData)
334 + ncolumns * sizeof(ColumnIOData));
335 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
336 my_extra->record_type = InvalidOid;
337 my_extra->record_typmod = 0;
340 if (my_extra->record_type != tupType ||
341 my_extra->record_typmod != tupTypmod)
344 sizeof(RecordIOData) - sizeof(ColumnIOData)
345 + ncolumns * sizeof(ColumnIOData));
346 my_extra->record_type = tupType;
347 my_extra->record_typmod = tupTypmod;
348 my_extra->ncolumns = ncolumns;
351 values = (Datum *) palloc(ncolumns * sizeof(Datum));
352 nulls = (bool *) palloc(ncolumns * sizeof(bool));
354 /* Break down the tuple into fields */
355 heap_deform_tuple(&tuple, tupdesc, values, nulls);
357 /* And build the result string */
358 initStringInfo(&buf);
360 appendStringInfoChar(&buf, '(');
362 for (i = 0; i < ncolumns; i++)
364 ColumnIOData *column_info = &my_extra->columns[i];
365 Oid column_type = tupdesc->attrs[i]->atttypid;
370 /* Ignore dropped columns in datatype */
371 if (tupdesc->attrs[i]->attisdropped)
375 appendStringInfoChar(&buf, ',');
380 /* emit nothing... */
385 * Convert the column value to text
387 if (column_info->column_type != column_type)
391 getTypeOutputInfo(column_type,
392 &column_info->typiofunc,
394 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
395 fcinfo->flinfo->fn_mcxt);
396 column_info->column_type = column_type;
399 value = OutputFunctionCall(&column_info->proc, values[i]);
401 /* Detect whether we need double quotes for this value */
402 nq = (value[0] == '\0'); /* force quotes for empty string */
403 for (tmp = value; *tmp; tmp++)
407 if (ch == '"' || ch == '\\' ||
408 ch == '(' || ch == ')' || ch == ',' ||
409 isspace((unsigned char) ch))
416 /* And emit the string */
418 appendStringInfoChar(&buf, '"');
419 for (tmp = value; *tmp; tmp++)
423 if (ch == '"' || ch == '\\')
424 appendStringInfoChar(&buf, ch);
425 appendStringInfoChar(&buf, ch);
428 appendStringInfoChar(&buf, '"');
431 appendStringInfoChar(&buf, ')');
435 ReleaseTupleDesc(tupdesc);
437 PG_RETURN_CSTRING(buf.data);
441 * record_recv - binary input routine for any composite type.
444 record_recv(PG_FUNCTION_ARGS)
446 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
447 Oid tupType = PG_GETARG_OID(1);
450 int32 typmod = PG_GETARG_INT32(2);
452 HeapTupleHeader result;
456 RecordIOData *my_extra;
465 * Use the passed type unless it's RECORD; we can't support input of
466 * anonymous types, mainly because there's no good way to figure out which
467 * anonymous type is wanted. Note that for RECORD, what we'll probably
468 * actually get is RECORD's typelem, ie, zero.
470 if (tupType == InvalidOid || tupType == RECORDOID)
472 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
473 errmsg("input of anonymous composite types is not implemented")));
474 tupTypmod = -1; /* for all non-anonymous types */
475 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
476 ncolumns = tupdesc->natts;
479 * We arrange to look up the needed I/O info just once per series of
480 * calls, assuming the record type doesn't change underneath us.
482 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
483 if (my_extra == NULL ||
484 my_extra->ncolumns != ncolumns)
486 fcinfo->flinfo->fn_extra =
487 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
488 sizeof(RecordIOData) - sizeof(ColumnIOData)
489 + ncolumns * sizeof(ColumnIOData));
490 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
491 my_extra->record_type = InvalidOid;
492 my_extra->record_typmod = 0;
495 if (my_extra->record_type != tupType ||
496 my_extra->record_typmod != tupTypmod)
499 sizeof(RecordIOData) - sizeof(ColumnIOData)
500 + ncolumns * sizeof(ColumnIOData));
501 my_extra->record_type = tupType;
502 my_extra->record_typmod = tupTypmod;
503 my_extra->ncolumns = ncolumns;
506 values = (Datum *) palloc(ncolumns * sizeof(Datum));
507 nulls = (bool *) palloc(ncolumns * sizeof(bool));
509 /* Fetch number of columns user thinks it has */
510 usercols = pq_getmsgint(buf, 4);
512 /* Need to scan to count nondeleted columns */
514 for (i = 0; i < ncolumns; i++)
516 if (!tupdesc->attrs[i]->attisdropped)
519 if (usercols != validcols)
521 (errcode(ERRCODE_DATATYPE_MISMATCH),
522 errmsg("wrong number of columns: %d, expected %d",
523 usercols, validcols)));
525 /* Process each column */
526 for (i = 0; i < ncolumns; i++)
528 ColumnIOData *column_info = &my_extra->columns[i];
529 Oid column_type = tupdesc->attrs[i]->atttypid;
532 StringInfoData item_buf;
536 /* Ignore dropped columns in datatype, but fill with nulls */
537 if (tupdesc->attrs[i]->attisdropped)
539 values[i] = (Datum) 0;
544 /* Verify column datatype */
545 coltypoid = pq_getmsgint(buf, sizeof(Oid));
546 if (coltypoid != column_type)
548 (errcode(ERRCODE_DATATYPE_MISMATCH),
549 errmsg("wrong data type: %u, expected %u",
550 coltypoid, column_type)));
552 /* Get and check the item length */
553 itemlen = pq_getmsgint(buf, 4);
554 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
556 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
557 errmsg("insufficient data left in message")));
561 /* -1 length means NULL */
564 csave = 0; /* keep compiler quiet */
569 * Rather than copying data around, we just set up a phony
570 * StringInfo pointing to the correct portion of the input buffer.
571 * We assume we can scribble on the input buffer so as to maintain
572 * the convention that StringInfos have a trailing null.
574 item_buf.data = &buf->data[buf->cursor];
575 item_buf.maxlen = itemlen + 1;
576 item_buf.len = itemlen;
579 buf->cursor += itemlen;
581 csave = buf->data[buf->cursor];
582 buf->data[buf->cursor] = '\0';
588 /* Now call the column's receiveproc */
589 if (column_info->column_type != column_type)
591 getTypeBinaryInputInfo(column_type,
592 &column_info->typiofunc,
593 &column_info->typioparam);
594 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
595 fcinfo->flinfo->fn_mcxt);
596 column_info->column_type = column_type;
599 values[i] = ReceiveFunctionCall(&column_info->proc,
601 column_info->typioparam,
602 tupdesc->attrs[i]->atttypmod);
606 /* Trouble if it didn't eat the whole buffer */
607 if (item_buf.cursor != itemlen)
609 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
610 errmsg("improper binary format in record column %d",
613 buf->data[buf->cursor] = csave;
617 tuple = heap_form_tuple(tupdesc, values, nulls);
620 * We cannot return tuple->t_data because heap_form_tuple allocates it as
621 * part of a larger chunk, and our caller may expect to be able to pfree
622 * our result. So must copy the info into a new palloc chunk.
624 result = (HeapTupleHeader) palloc(tuple->t_len);
625 memcpy(result, tuple->t_data, tuple->t_len);
627 heap_freetuple(tuple);
630 ReleaseTupleDesc(tupdesc);
632 PG_RETURN_HEAPTUPLEHEADER(result);
636 * record_send - binary output routine for any composite type.
639 record_send(PG_FUNCTION_ARGS)
641 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
646 RecordIOData *my_extra;
654 /* Extract type info from the tuple itself */
655 tupType = HeapTupleHeaderGetTypeId(rec);
656 tupTypmod = HeapTupleHeaderGetTypMod(rec);
657 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
658 ncolumns = tupdesc->natts;
660 /* Build a temporary HeapTuple control structure */
661 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
662 ItemPointerSetInvalid(&(tuple.t_self));
663 tuple.t_tableOid = InvalidOid;
667 * We arrange to look up the needed I/O info just once per series of
668 * calls, assuming the record type doesn't change underneath us.
670 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
671 if (my_extra == NULL ||
672 my_extra->ncolumns != ncolumns)
674 fcinfo->flinfo->fn_extra =
675 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
676 sizeof(RecordIOData) - sizeof(ColumnIOData)
677 + ncolumns * sizeof(ColumnIOData));
678 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
679 my_extra->record_type = InvalidOid;
680 my_extra->record_typmod = 0;
683 if (my_extra->record_type != tupType ||
684 my_extra->record_typmod != tupTypmod)
687 sizeof(RecordIOData) - sizeof(ColumnIOData)
688 + ncolumns * sizeof(ColumnIOData));
689 my_extra->record_type = tupType;
690 my_extra->record_typmod = tupTypmod;
691 my_extra->ncolumns = ncolumns;
694 values = (Datum *) palloc(ncolumns * sizeof(Datum));
695 nulls = (bool *) palloc(ncolumns * sizeof(bool));
697 /* Break down the tuple into fields */
698 heap_deform_tuple(&tuple, tupdesc, values, nulls);
700 /* And build the result string */
701 pq_begintypsend(&buf);
703 /* Need to scan to count nondeleted columns */
705 for (i = 0; i < ncolumns; i++)
707 if (!tupdesc->attrs[i]->attisdropped)
710 pq_sendint(&buf, validcols, 4);
712 for (i = 0; i < ncolumns; i++)
714 ColumnIOData *column_info = &my_extra->columns[i];
715 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)
738 getTypeBinaryOutputInfo(column_type,
739 &column_info->typiofunc,
741 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
742 fcinfo->flinfo->fn_mcxt);
743 column_info->column_type = column_type;
746 outputbytes = SendFunctionCall(&column_info->proc, values[i]);
748 /* We assume the result will not have been toasted */
749 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
750 pq_sendbytes(&buf, VARDATA(outputbytes),
751 VARSIZE(outputbytes) - VARHDRSZ);
757 ReleaseTupleDesc(tupdesc);
759 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
765 * Internal comparison function for records.
769 * Do not assume that the two inputs are exactly the same record type;
770 * for instance we might be comparing an anonymous ROW() construct against a
771 * named composite type. We will compare as long as they have the same number
772 * of non-dropped columns of the same types.
775 record_cmp(FunctionCallInfo fcinfo)
777 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
778 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
786 HeapTupleData tuple1;
787 HeapTupleData tuple2;
790 RecordCompareData *my_extra;
800 /* Extract type info from the tuples */
801 tupType1 = HeapTupleHeaderGetTypeId(record1);
802 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
803 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
804 ncolumns1 = tupdesc1->natts;
805 tupType2 = HeapTupleHeaderGetTypeId(record2);
806 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
807 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
808 ncolumns2 = tupdesc2->natts;
810 /* Build temporary HeapTuple control structures */
811 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
812 ItemPointerSetInvalid(&(tuple1.t_self));
813 tuple1.t_tableOid = InvalidOid;
814 tuple1.t_data = record1;
815 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
816 ItemPointerSetInvalid(&(tuple2.t_self));
817 tuple2.t_tableOid = InvalidOid;
818 tuple2.t_data = record2;
821 * We arrange to look up the needed comparison info just once per series
822 * of calls, assuming the record types don't change underneath us.
824 ncols = Max(ncolumns1, ncolumns2);
825 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
826 if (my_extra == NULL ||
827 my_extra->ncolumns < ncols)
829 fcinfo->flinfo->fn_extra =
830 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
831 sizeof(RecordCompareData) - sizeof(ColumnCompareData)
832 + ncols * sizeof(ColumnCompareData));
833 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
834 my_extra->ncolumns = ncols;
835 my_extra->record1_type = InvalidOid;
836 my_extra->record1_typmod = 0;
837 my_extra->record2_type = InvalidOid;
838 my_extra->record2_typmod = 0;
841 if (my_extra->record1_type != tupType1 ||
842 my_extra->record1_typmod != tupTypmod1 ||
843 my_extra->record2_type != tupType2 ||
844 my_extra->record2_typmod != tupTypmod2)
846 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
847 my_extra->record1_type = tupType1;
848 my_extra->record1_typmod = tupTypmod1;
849 my_extra->record2_type = tupType2;
850 my_extra->record2_typmod = tupTypmod2;
853 /* Break down the tuples into fields */
854 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
855 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
856 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
857 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
858 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
859 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
862 * Scan corresponding columns, allowing for dropped columns in different
863 * places in the two rows. i1 and i2 are physical column indexes, j is
864 * the logical column index.
867 while (i1 < ncolumns1 || i2 < ncolumns2)
869 TypeCacheEntry *typentry;
871 FunctionCallInfoData locfcinfo;
875 * Skip dropped columns
877 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
882 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
887 if (i1 >= ncolumns1 || i2 >= ncolumns2)
888 break; /* we'll deal with mismatch below loop */
891 * Have two matching columns, they must be same type
893 if (tupdesc1->attrs[i1]->atttypid !=
894 tupdesc2->attrs[i2]->atttypid)
896 (errcode(ERRCODE_DATATYPE_MISMATCH),
897 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
898 format_type_be(tupdesc1->attrs[i1]->atttypid),
899 format_type_be(tupdesc2->attrs[i2]->atttypid),
903 * If they're not same collation, we don't complain here, but the
904 * comparison function might.
906 collation = tupdesc1->attrs[i1]->attcollation;
907 if (collation != tupdesc2->attrs[i2]->attcollation)
908 collation = InvalidOid;
911 * Lookup the comparison function if not done already
913 typentry = my_extra->columns[j].typentry;
914 if (typentry == NULL ||
915 typentry->type_id != tupdesc1->attrs[i1]->atttypid)
917 typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
918 TYPECACHE_CMP_PROC_FINFO);
919 if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
921 (errcode(ERRCODE_UNDEFINED_FUNCTION),
922 errmsg("could not identify a comparison function for type %s",
923 format_type_be(typentry->type_id))));
924 my_extra->columns[j].typentry = typentry;
928 * We consider two NULLs equal; NULL > not-NULL.
930 if (!nulls1[i1] || !nulls2[i2])
934 /* arg1 is greater than arg2 */
940 /* arg1 is less than arg2 */
945 /* Compare the pair of elements */
946 InitFunctionCallInfoData(locfcinfo, &typentry->cmp_proc_finfo, 2,
947 collation, NULL, NULL);
948 locfcinfo.arg[0] = values1[i1];
949 locfcinfo.arg[1] = values2[i2];
950 locfcinfo.argnull[0] = false;
951 locfcinfo.argnull[1] = false;
952 locfcinfo.isnull = false;
953 cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
957 /* arg1 is less than arg2 */
961 else if (cmpresult > 0)
963 /* arg1 is greater than arg2 */
969 /* equal, so continue to next column */
974 * If we didn't break out of the loop early, check for column count
975 * mismatch. (We do not report such mismatch if we found unequal column
976 * values; is that a feature or a bug?)
980 if (i1 != ncolumns1 || i2 != ncolumns2)
982 (errcode(ERRCODE_DATATYPE_MISMATCH),
983 errmsg("cannot compare record types with different numbers of columns")));
990 ReleaseTupleDesc(tupdesc1);
991 ReleaseTupleDesc(tupdesc2);
993 /* Avoid leaking memory when handed toasted input. */
994 PG_FREE_IF_COPY(record1, 0);
995 PG_FREE_IF_COPY(record2, 1);
1002 * compares two records for equality
1004 * returns true if the records are equal, false otherwise.
1006 * Note: we do not use record_cmp here, since equality may be meaningful in
1007 * datatypes that don't have a total ordering (and hence no btree support).
1010 record_eq(PG_FUNCTION_ARGS)
1012 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1013 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1021 HeapTupleData tuple1;
1022 HeapTupleData tuple2;
1025 RecordCompareData *my_extra;
1035 /* Extract type info from the tuples */
1036 tupType1 = HeapTupleHeaderGetTypeId(record1);
1037 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1038 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1039 ncolumns1 = tupdesc1->natts;
1040 tupType2 = HeapTupleHeaderGetTypeId(record2);
1041 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1042 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1043 ncolumns2 = tupdesc2->natts;
1045 /* Build temporary HeapTuple control structures */
1046 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1047 ItemPointerSetInvalid(&(tuple1.t_self));
1048 tuple1.t_tableOid = InvalidOid;
1049 tuple1.t_data = record1;
1050 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1051 ItemPointerSetInvalid(&(tuple2.t_self));
1052 tuple2.t_tableOid = InvalidOid;
1053 tuple2.t_data = record2;
1056 * We arrange to look up the needed comparison info just once per series
1057 * of calls, assuming the record types don't change underneath us.
1059 ncols = Max(ncolumns1, ncolumns2);
1060 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1061 if (my_extra == NULL ||
1062 my_extra->ncolumns < ncols)
1064 fcinfo->flinfo->fn_extra =
1065 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1066 sizeof(RecordCompareData) - sizeof(ColumnCompareData)
1067 + ncols * sizeof(ColumnCompareData));
1068 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1069 my_extra->ncolumns = ncols;
1070 my_extra->record1_type = InvalidOid;
1071 my_extra->record1_typmod = 0;
1072 my_extra->record2_type = InvalidOid;
1073 my_extra->record2_typmod = 0;
1076 if (my_extra->record1_type != tupType1 ||
1077 my_extra->record1_typmod != tupTypmod1 ||
1078 my_extra->record2_type != tupType2 ||
1079 my_extra->record2_typmod != tupTypmod2)
1081 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1082 my_extra->record1_type = tupType1;
1083 my_extra->record1_typmod = tupTypmod1;
1084 my_extra->record2_type = tupType2;
1085 my_extra->record2_typmod = tupTypmod2;
1088 /* Break down the tuples into fields */
1089 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1090 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1091 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1092 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1093 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1094 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1097 * Scan corresponding columns, allowing for dropped columns in different
1098 * places in the two rows. i1 and i2 are physical column indexes, j is
1099 * the logical column index.
1102 while (i1 < ncolumns1 || i2 < ncolumns2)
1104 TypeCacheEntry *typentry;
1106 FunctionCallInfoData locfcinfo;
1110 * Skip dropped columns
1112 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1117 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1122 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1123 break; /* we'll deal with mismatch below loop */
1126 * Have two matching columns, they must be same type
1128 if (tupdesc1->attrs[i1]->atttypid !=
1129 tupdesc2->attrs[i2]->atttypid)
1131 (errcode(ERRCODE_DATATYPE_MISMATCH),
1132 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1133 format_type_be(tupdesc1->attrs[i1]->atttypid),
1134 format_type_be(tupdesc2->attrs[i2]->atttypid),
1138 * If they're not same collation, we don't complain here, but the
1139 * equality function might.
1141 collation = tupdesc1->attrs[i1]->attcollation;
1142 if (collation != tupdesc2->attrs[i2]->attcollation)
1143 collation = InvalidOid;
1146 * Lookup the equality function if not done already
1148 typentry = my_extra->columns[j].typentry;
1149 if (typentry == NULL ||
1150 typentry->type_id != tupdesc1->attrs[i1]->atttypid)
1152 typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
1153 TYPECACHE_EQ_OPR_FINFO);
1154 if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1156 (errcode(ERRCODE_UNDEFINED_FUNCTION),
1157 errmsg("could not identify an equality operator for type %s",
1158 format_type_be(typentry->type_id))));
1159 my_extra->columns[j].typentry = typentry;
1163 * We consider two NULLs equal; NULL > not-NULL.
1165 if (!nulls1[i1] || !nulls2[i2])
1167 if (nulls1[i1] || nulls2[i2])
1173 /* Compare the pair of elements */
1174 InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
1175 collation, NULL, NULL);
1176 locfcinfo.arg[0] = values1[i1];
1177 locfcinfo.arg[1] = values2[i2];
1178 locfcinfo.argnull[0] = false;
1179 locfcinfo.argnull[1] = false;
1180 locfcinfo.isnull = false;
1181 oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
1189 /* equal, so continue to next column */
1194 * If we didn't break out of the loop early, check for column count
1195 * mismatch. (We do not report such mismatch if we found unequal column
1196 * values; is that a feature or a bug?)
1200 if (i1 != ncolumns1 || i2 != ncolumns2)
1202 (errcode(ERRCODE_DATATYPE_MISMATCH),
1203 errmsg("cannot compare record types with different numbers of columns")));
1210 ReleaseTupleDesc(tupdesc1);
1211 ReleaseTupleDesc(tupdesc2);
1213 /* Avoid leaking memory when handed toasted input. */
1214 PG_FREE_IF_COPY(record1, 0);
1215 PG_FREE_IF_COPY(record2, 1);
1217 PG_RETURN_BOOL(result);
1221 record_ne(PG_FUNCTION_ARGS)
1223 PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
1227 record_lt(PG_FUNCTION_ARGS)
1229 PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
1233 record_gt(PG_FUNCTION_ARGS)
1235 PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
1239 record_le(PG_FUNCTION_ARGS)
1241 PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
1245 record_ge(PG_FUNCTION_ARGS)
1247 PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
1251 btrecordcmp(PG_FUNCTION_ARGS)
1253 PG_RETURN_INT32(record_cmp(fcinfo));