1 /*-------------------------------------------------------------------------
2 *
3 * rowtypes.c
4 * I/O and comparison functions for generic composite types.
5 *
6 * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 *
10 * IDENTIFICATION
11 * src/backend/utils/adt/rowtypes.c
12 *
13 *-------------------------------------------------------------------------
14 */
15 #include "postgres.h"
16
17 #include <ctype.h>
18
19 #include "access/htup_details.h"
20 #include "access/tuptoaster.h"
21 #include "catalog/pg_type.h"
22 #include "funcapi.h"
23 #include "libpq/pqformat.h"
24 #include "miscadmin.h"
25 #include "utils/builtins.h"
26 #include "utils/lsyscache.h"
27 #include "utils/typcache.h"
28
29
30 /*
31 * structure to cache metadata needed for record I/O
32 */
33 typedef struct ColumnIOData
34 {
35 Oid column_type;
36 Oid typiofunc;
37 Oid typioparam;
38 bool typisvarlena;
39 FmgrInfo proc;
40 } ColumnIOData;
41
42 typedef struct RecordIOData
43 {
44 Oid record_type;
45 int32 record_typmod;
46 int ncolumns;
47 ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
48 } RecordIOData;
49
50 /*
51 * structure to cache metadata needed for record comparison
52 */
53 typedef struct ColumnCompareData
54 {
55 TypeCacheEntry *typentry; /* has everything we need, actually */
56 } ColumnCompareData;
57
58 typedef struct RecordCompareData
59 {
60 int ncolumns; /* allocated length of columns[] */
61 Oid record1_type;
62 int32 record1_typmod;
63 Oid record2_type;
64 int32 record2_typmod;
65 ColumnCompareData columns[FLEXIBLE_ARRAY_MEMBER];
66 } RecordCompareData;
67
68
69 /*
70 * record_in - input routine for any composite type.
71 */
72 Datum
record_in(PG_FUNCTION_ARGS)73 record_in(PG_FUNCTION_ARGS)
74 {
75 char *string = PG_GETARG_CSTRING(0);
76 Oid tupType = PG_GETARG_OID(1);
77 int32 tupTypmod = PG_GETARG_INT32(2);
78 HeapTupleHeader result;
79 TupleDesc tupdesc;
80 HeapTuple tuple;
81 RecordIOData *my_extra;
82 bool needComma = false;
83 int ncolumns;
84 int i;
85 char *ptr;
86 Datum *values;
87 bool *nulls;
88 StringInfoData buf;
89
90 check_stack_depth(); /* recurses for record-type columns */
91
92 /*
93 * Give a friendly error message if we did not get enough info to identify
94 * the target record type. (lookup_rowtype_tupdesc would fail anyway, but
95 * with a non-user-friendly message.) In ordinary SQL usage, we'll get -1
96 * for typmod, since composite types and RECORD have no type modifiers at
97 * the SQL level, and thus must fail for RECORD. However some callers can
98 * supply a valid typmod, and then we can do something useful for RECORD.
99 */
100 if (tupType == RECORDOID && tupTypmod < 0)
101 ereport(ERROR,
102 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
103 errmsg("input of anonymous composite types is not implemented")));
104
105 /*
106 * This comes from the composite type's pg_type.oid and stores system oids
107 * in user tables, specifically DatumTupleFields. This oid must be
108 * preserved by binary upgrades.
109 */
110 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
111 ncolumns = tupdesc->natts;
112
113 /*
114 * We arrange to look up the needed I/O info just once per series of
115 * calls, assuming the record type doesn't change underneath us.
116 */
117 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
118 if (my_extra == NULL ||
119 my_extra->ncolumns != ncolumns)
120 {
121 fcinfo->flinfo->fn_extra =
122 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
123 offsetof(RecordIOData, columns) +
124 ncolumns * sizeof(ColumnIOData));
125 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
126 my_extra->record_type = InvalidOid;
127 my_extra->record_typmod = 0;
128 }
129
130 if (my_extra->record_type != tupType ||
131 my_extra->record_typmod != tupTypmod)
132 {
133 MemSet(my_extra, 0,
134 offsetof(RecordIOData, columns) +
135 ncolumns * sizeof(ColumnIOData));
136 my_extra->record_type = tupType;
137 my_extra->record_typmod = tupTypmod;
138 my_extra->ncolumns = ncolumns;
139 }
140
141 values = (Datum *) palloc(ncolumns * sizeof(Datum));
142 nulls = (bool *) palloc(ncolumns * sizeof(bool));
143
144 /*
145 * Scan the string. We use "buf" to accumulate the de-quoted data for
146 * each column, which is then fed to the appropriate input converter.
147 */
148 ptr = string;
149 /* Allow leading whitespace */
150 while (*ptr && isspace((unsigned char) *ptr))
151 ptr++;
152 if (*ptr++ != '(')
153 ereport(ERROR,
154 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
155 errmsg("malformed record literal: \"%s\"", string),
156 errdetail("Missing left parenthesis.")));
157
158 initStringInfo(&buf);
159
160 for (i = 0; i < ncolumns; i++)
161 {
162 ColumnIOData *column_info = &my_extra->columns[i];
163 Oid column_type = tupdesc->attrs[i]->atttypid;
164 char *column_data;
165
166 /* Ignore dropped columns in datatype, but fill with nulls */
167 if (tupdesc->attrs[i]->attisdropped)
168 {
169 values[i] = (Datum) 0;
170 nulls[i] = true;
171 continue;
172 }
173
174 if (needComma)
175 {
176 /* Skip comma that separates prior field from this one */
177 if (*ptr == ',')
178 ptr++;
179 else
180 /* *ptr must be ')' */
181 ereport(ERROR,
182 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
183 errmsg("malformed record literal: \"%s\"", string),
184 errdetail("Too few columns.")));
185 }
186
187 /* Check for null: completely empty input means null */
188 if (*ptr == ',' || *ptr == ')')
189 {
190 column_data = NULL;
191 nulls[i] = true;
192 }
193 else
194 {
195 /* Extract string for this column */
196 bool inquote = false;
197
198 resetStringInfo(&buf);
199 while (inquote || !(*ptr == ',' || *ptr == ')'))
200 {
201 char ch = *ptr++;
202
203 if (ch == '\0')
204 ereport(ERROR,
205 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
206 errmsg("malformed record literal: \"%s\"",
207 string),
208 errdetail("Unexpected end of input.")));
209 if (ch == '\\')
210 {
211 if (*ptr == '\0')
212 ereport(ERROR,
213 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
214 errmsg("malformed record literal: \"%s\"",
215 string),
216 errdetail("Unexpected end of input.")));
217 appendStringInfoChar(&buf, *ptr++);
218 }
219 else if (ch == '"')
220 {
221 if (!inquote)
222 inquote = true;
223 else if (*ptr == '"')
224 {
225 /* doubled quote within quote sequence */
226 appendStringInfoChar(&buf, *ptr++);
227 }
228 else
229 inquote = false;
230 }
231 else
232 appendStringInfoChar(&buf, ch);
233 }
234
235 column_data = buf.data;
236 nulls[i] = false;
237 }
238
239 /*
240 * Convert the column value
241 */
242 if (column_info->column_type != column_type)
243 {
244 getTypeInputInfo(column_type,
245 &column_info->typiofunc,
246 &column_info->typioparam);
247 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
248 fcinfo->flinfo->fn_mcxt);
249 column_info->column_type = column_type;
250 }
251
252 values[i] = InputFunctionCall(&column_info->proc,
253 column_data,
254 column_info->typioparam,
255 tupdesc->attrs[i]->atttypmod);
256
257 /*
258 * Prep for next column
259 */
260 needComma = true;
261 }
262
263 if (*ptr++ != ')')
264 ereport(ERROR,
265 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
266 errmsg("malformed record literal: \"%s\"", string),
267 errdetail("Too many columns.")));
268 /* Allow trailing whitespace */
269 while (*ptr && isspace((unsigned char) *ptr))
270 ptr++;
271 if (*ptr)
272 ereport(ERROR,
273 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
274 errmsg("malformed record literal: \"%s\"", string),
275 errdetail("Junk after right parenthesis.")));
276
277 tuple = heap_form_tuple(tupdesc, values, nulls);
278
279 /*
280 * We cannot return tuple->t_data because heap_form_tuple allocates it as
281 * part of a larger chunk, and our caller may expect to be able to pfree
282 * our result. So must copy the info into a new palloc chunk.
283 */
284 result = (HeapTupleHeader) palloc(tuple->t_len);
285 memcpy(result, tuple->t_data, tuple->t_len);
286
287 heap_freetuple(tuple);
288 pfree(buf.data);
289 pfree(values);
290 pfree(nulls);
291 ReleaseTupleDesc(tupdesc);
292
293 PG_RETURN_HEAPTUPLEHEADER(result);
294 }
295
296 /*
297 * record_out - output routine for any composite type.
298 */
299 Datum
record_out(PG_FUNCTION_ARGS)300 record_out(PG_FUNCTION_ARGS)
301 {
302 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
303 Oid tupType;
304 int32 tupTypmod;
305 TupleDesc tupdesc;
306 HeapTupleData tuple;
307 RecordIOData *my_extra;
308 bool needComma = false;
309 int ncolumns;
310 int i;
311 Datum *values;
312 bool *nulls;
313 StringInfoData buf;
314
315 check_stack_depth(); /* recurses for record-type columns */
316
317 /* Extract type info from the tuple itself */
318 tupType = HeapTupleHeaderGetTypeId(rec);
319 tupTypmod = HeapTupleHeaderGetTypMod(rec);
320 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
321 ncolumns = tupdesc->natts;
322
323 /* Build a temporary HeapTuple control structure */
324 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
325 ItemPointerSetInvalid(&(tuple.t_self));
326 tuple.t_tableOid = InvalidOid;
327 tuple.t_data = rec;
328
329 /*
330 * We arrange to look up the needed I/O info just once per series of
331 * calls, assuming the record type doesn't change underneath us.
332 */
333 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
334 if (my_extra == NULL ||
335 my_extra->ncolumns != ncolumns)
336 {
337 fcinfo->flinfo->fn_extra =
338 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
339 offsetof(RecordIOData, columns) +
340 ncolumns * sizeof(ColumnIOData));
341 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
342 my_extra->record_type = InvalidOid;
343 my_extra->record_typmod = 0;
344 }
345
346 if (my_extra->record_type != tupType ||
347 my_extra->record_typmod != tupTypmod)
348 {
349 MemSet(my_extra, 0,
350 offsetof(RecordIOData, columns) +
351 ncolumns * sizeof(ColumnIOData));
352 my_extra->record_type = tupType;
353 my_extra->record_typmod = tupTypmod;
354 my_extra->ncolumns = ncolumns;
355 }
356
357 values = (Datum *) palloc(ncolumns * sizeof(Datum));
358 nulls = (bool *) palloc(ncolumns * sizeof(bool));
359
360 /* Break down the tuple into fields */
361 heap_deform_tuple(&tuple, tupdesc, values, nulls);
362
363 /* And build the result string */
364 initStringInfo(&buf);
365
366 appendStringInfoChar(&buf, '(');
367
368 for (i = 0; i < ncolumns; i++)
369 {
370 ColumnIOData *column_info = &my_extra->columns[i];
371 Oid column_type = tupdesc->attrs[i]->atttypid;
372 Datum attr;
373 char *value;
374 char *tmp;
375 bool nq;
376
377 /* Ignore dropped columns in datatype */
378 if (tupdesc->attrs[i]->attisdropped)
379 continue;
380
381 if (needComma)
382 appendStringInfoChar(&buf, ',');
383 needComma = true;
384
385 if (nulls[i])
386 {
387 /* emit nothing... */
388 continue;
389 }
390
391 /*
392 * Convert the column value to text
393 */
394 if (column_info->column_type != column_type)
395 {
396 getTypeOutputInfo(column_type,
397 &column_info->typiofunc,
398 &column_info->typisvarlena);
399 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
400 fcinfo->flinfo->fn_mcxt);
401 column_info->column_type = column_type;
402 }
403
404 attr = values[i];
405 value = OutputFunctionCall(&column_info->proc, attr);
406
407 /* Detect whether we need double quotes for this value */
408 nq = (value[0] == '\0'); /* force quotes for empty string */
409 for (tmp = value; *tmp; tmp++)
410 {
411 char ch = *tmp;
412
413 if (ch == '"' || ch == '\\' ||
414 ch == '(' || ch == ')' || ch == ',' ||
415 isspace((unsigned char) ch))
416 {
417 nq = true;
418 break;
419 }
420 }
421
422 /* And emit the string */
423 if (nq)
424 appendStringInfoCharMacro(&buf, '"');
425 for (tmp = value; *tmp; tmp++)
426 {
427 char ch = *tmp;
428
429 if (ch == '"' || ch == '\\')
430 appendStringInfoCharMacro(&buf, ch);
431 appendStringInfoCharMacro(&buf, ch);
432 }
433 if (nq)
434 appendStringInfoCharMacro(&buf, '"');
435 }
436
437 appendStringInfoChar(&buf, ')');
438
439 pfree(values);
440 pfree(nulls);
441 ReleaseTupleDesc(tupdesc);
442
443 PG_RETURN_CSTRING(buf.data);
444 }
445
446 /*
447 * record_recv - binary input routine for any composite type.
448 */
449 Datum
record_recv(PG_FUNCTION_ARGS)450 record_recv(PG_FUNCTION_ARGS)
451 {
452 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
453 Oid tupType = PG_GETARG_OID(1);
454 int32 tupTypmod = PG_GETARG_INT32(2);
455 HeapTupleHeader result;
456 TupleDesc tupdesc;
457 HeapTuple tuple;
458 RecordIOData *my_extra;
459 int ncolumns;
460 int usercols;
461 int validcols;
462 int i;
463 Datum *values;
464 bool *nulls;
465
466 check_stack_depth(); /* recurses for record-type columns */
467
468 /*
469 * Give a friendly error message if we did not get enough info to identify
470 * the target record type. (lookup_rowtype_tupdesc would fail anyway, but
471 * with a non-user-friendly message.) In ordinary SQL usage, we'll get -1
472 * for typmod, since composite types and RECORD have no type modifiers at
473 * the SQL level, and thus must fail for RECORD. However some callers can
474 * supply a valid typmod, and then we can do something useful for RECORD.
475 */
476 if (tupType == RECORDOID && tupTypmod < 0)
477 ereport(ERROR,
478 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
479 errmsg("input of anonymous composite types is not implemented")));
480
481 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
482 ncolumns = tupdesc->natts;
483
484 /*
485 * We arrange to look up the needed I/O info just once per series of
486 * calls, assuming the record type doesn't change underneath us.
487 */
488 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
489 if (my_extra == NULL ||
490 my_extra->ncolumns != ncolumns)
491 {
492 fcinfo->flinfo->fn_extra =
493 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
494 offsetof(RecordIOData, columns) +
495 ncolumns * sizeof(ColumnIOData));
496 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
497 my_extra->record_type = InvalidOid;
498 my_extra->record_typmod = 0;
499 }
500
501 if (my_extra->record_type != tupType ||
502 my_extra->record_typmod != tupTypmod)
503 {
504 MemSet(my_extra, 0,
505 offsetof(RecordIOData, columns) +
506 ncolumns * sizeof(ColumnIOData));
507 my_extra->record_type = tupType;
508 my_extra->record_typmod = tupTypmod;
509 my_extra->ncolumns = ncolumns;
510 }
511
512 values = (Datum *) palloc(ncolumns * sizeof(Datum));
513 nulls = (bool *) palloc(ncolumns * sizeof(bool));
514
515 /* Fetch number of columns user thinks it has */
516 usercols = pq_getmsgint(buf, 4);
517
518 /* Need to scan to count nondeleted columns */
519 validcols = 0;
520 for (i = 0; i < ncolumns; i++)
521 {
522 if (!tupdesc->attrs[i]->attisdropped)
523 validcols++;
524 }
525 if (usercols != validcols)
526 ereport(ERROR,
527 (errcode(ERRCODE_DATATYPE_MISMATCH),
528 errmsg("wrong number of columns: %d, expected %d",
529 usercols, validcols)));
530
531 /* Process each column */
532 for (i = 0; i < ncolumns; i++)
533 {
534 ColumnIOData *column_info = &my_extra->columns[i];
535 Oid column_type = tupdesc->attrs[i]->atttypid;
536 Oid coltypoid;
537 int itemlen;
538 StringInfoData item_buf;
539 StringInfo bufptr;
540 char csave;
541
542 /* Ignore dropped columns in datatype, but fill with nulls */
543 if (tupdesc->attrs[i]->attisdropped)
544 {
545 values[i] = (Datum) 0;
546 nulls[i] = true;
547 continue;
548 }
549
550 /* Verify column datatype */
551 coltypoid = pq_getmsgint(buf, sizeof(Oid));
552 if (coltypoid != column_type)
553 ereport(ERROR,
554 (errcode(ERRCODE_DATATYPE_MISMATCH),
555 errmsg("wrong data type: %u, expected %u",
556 coltypoid, column_type)));
557
558 /* Get and check the item length */
559 itemlen = pq_getmsgint(buf, 4);
560 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
561 ereport(ERROR,
562 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
563 errmsg("insufficient data left in message")));
564
565 if (itemlen == -1)
566 {
567 /* -1 length means NULL */
568 bufptr = NULL;
569 nulls[i] = true;
570 csave = 0; /* keep compiler quiet */
571 }
572 else
573 {
574 /*
575 * Rather than copying data around, we just set up a phony
576 * StringInfo pointing to the correct portion of the input buffer.
577 * We assume we can scribble on the input buffer so as to maintain
578 * the convention that StringInfos have a trailing null.
579 */
580 item_buf.data = &buf->data[buf->cursor];
581 item_buf.maxlen = itemlen + 1;
582 item_buf.len = itemlen;
583 item_buf.cursor = 0;
584
585 buf->cursor += itemlen;
586
587 csave = buf->data[buf->cursor];
588 buf->data[buf->cursor] = '\0';
589
590 bufptr = &item_buf;
591 nulls[i] = false;
592 }
593
594 /* Now call the column's receiveproc */
595 if (column_info->column_type != column_type)
596 {
597 getTypeBinaryInputInfo(column_type,
598 &column_info->typiofunc,
599 &column_info->typioparam);
600 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
601 fcinfo->flinfo->fn_mcxt);
602 column_info->column_type = column_type;
603 }
604
605 values[i] = ReceiveFunctionCall(&column_info->proc,
606 bufptr,
607 column_info->typioparam,
608 tupdesc->attrs[i]->atttypmod);
609
610 if (bufptr)
611 {
612 /* Trouble if it didn't eat the whole buffer */
613 if (item_buf.cursor != itemlen)
614 ereport(ERROR,
615 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
616 errmsg("improper binary format in record column %d",
617 i + 1)));
618
619 buf->data[buf->cursor] = csave;
620 }
621 }
622
623 tuple = heap_form_tuple(tupdesc, values, nulls);
624
625 /*
626 * We cannot return tuple->t_data because heap_form_tuple allocates it as
627 * part of a larger chunk, and our caller may expect to be able to pfree
628 * our result. So must copy the info into a new palloc chunk.
629 */
630 result = (HeapTupleHeader) palloc(tuple->t_len);
631 memcpy(result, tuple->t_data, tuple->t_len);
632
633 heap_freetuple(tuple);
634 pfree(values);
635 pfree(nulls);
636 ReleaseTupleDesc(tupdesc);
637
638 PG_RETURN_HEAPTUPLEHEADER(result);
639 }
640
641 /*
642 * record_send - binary output routine for any composite type.
643 */
644 Datum
record_send(PG_FUNCTION_ARGS)645 record_send(PG_FUNCTION_ARGS)
646 {
647 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
648 Oid tupType;
649 int32 tupTypmod;
650 TupleDesc tupdesc;
651 HeapTupleData tuple;
652 RecordIOData *my_extra;
653 int ncolumns;
654 int validcols;
655 int i;
656 Datum *values;
657 bool *nulls;
658 StringInfoData buf;
659
660 check_stack_depth(); /* recurses for record-type columns */
661
662 /* Extract type info from the tuple itself */
663 tupType = HeapTupleHeaderGetTypeId(rec);
664 tupTypmod = HeapTupleHeaderGetTypMod(rec);
665 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
666 ncolumns = tupdesc->natts;
667
668 /* Build a temporary HeapTuple control structure */
669 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
670 ItemPointerSetInvalid(&(tuple.t_self));
671 tuple.t_tableOid = InvalidOid;
672 tuple.t_data = rec;
673
674 /*
675 * We arrange to look up the needed I/O info just once per series of
676 * calls, assuming the record type doesn't change underneath us.
677 */
678 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
679 if (my_extra == NULL ||
680 my_extra->ncolumns != ncolumns)
681 {
682 fcinfo->flinfo->fn_extra =
683 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
684 offsetof(RecordIOData, columns) +
685 ncolumns * sizeof(ColumnIOData));
686 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
687 my_extra->record_type = InvalidOid;
688 my_extra->record_typmod = 0;
689 }
690
691 if (my_extra->record_type != tupType ||
692 my_extra->record_typmod != tupTypmod)
693 {
694 MemSet(my_extra, 0,
695 offsetof(RecordIOData, columns) +
696 ncolumns * sizeof(ColumnIOData));
697 my_extra->record_type = tupType;
698 my_extra->record_typmod = tupTypmod;
699 my_extra->ncolumns = ncolumns;
700 }
701
702 values = (Datum *) palloc(ncolumns * sizeof(Datum));
703 nulls = (bool *) palloc(ncolumns * sizeof(bool));
704
705 /* Break down the tuple into fields */
706 heap_deform_tuple(&tuple, tupdesc, values, nulls);
707
708 /* And build the result string */
709 pq_begintypsend(&buf);
710
711 /* Need to scan to count nondeleted columns */
712 validcols = 0;
713 for (i = 0; i < ncolumns; i++)
714 {
715 if (!tupdesc->attrs[i]->attisdropped)
716 validcols++;
717 }
718 pq_sendint(&buf, validcols, 4);
719
720 for (i = 0; i < ncolumns; i++)
721 {
722 ColumnIOData *column_info = &my_extra->columns[i];
723 Oid column_type = tupdesc->attrs[i]->atttypid;
724 Datum attr;
725 bytea *outputbytes;
726
727 /* Ignore dropped columns in datatype */
728 if (tupdesc->attrs[i]->attisdropped)
729 continue;
730
731 pq_sendint(&buf, column_type, sizeof(Oid));
732
733 if (nulls[i])
734 {
735 /* emit -1 data length to signify a NULL */
736 pq_sendint(&buf, -1, 4);
737 continue;
738 }
739
740 /*
741 * Convert the column value to binary
742 */
743 if (column_info->column_type != column_type)
744 {
745 getTypeBinaryOutputInfo(column_type,
746 &column_info->typiofunc,
747 &column_info->typisvarlena);
748 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
749 fcinfo->flinfo->fn_mcxt);
750 column_info->column_type = column_type;
751 }
752
753 attr = values[i];
754 outputbytes = SendFunctionCall(&column_info->proc, attr);
755 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
756 pq_sendbytes(&buf, VARDATA(outputbytes),
757 VARSIZE(outputbytes) - VARHDRSZ);
758 }
759
760 pfree(values);
761 pfree(nulls);
762 ReleaseTupleDesc(tupdesc);
763
764 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
765 }
766
767
768 /*
769 * record_cmp()
770 * Internal comparison function for records.
771 *
772 * Returns -1, 0 or 1
773 *
774 * Do not assume that the two inputs are exactly the same record type;
775 * for instance we might be comparing an anonymous ROW() construct against a
776 * named composite type. We will compare as long as they have the same number
777 * of non-dropped columns of the same types.
778 */
779 static int
record_cmp(FunctionCallInfo fcinfo)780 record_cmp(FunctionCallInfo fcinfo)
781 {
782 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
783 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
784 int result = 0;
785 Oid tupType1;
786 Oid tupType2;
787 int32 tupTypmod1;
788 int32 tupTypmod2;
789 TupleDesc tupdesc1;
790 TupleDesc tupdesc2;
791 HeapTupleData tuple1;
792 HeapTupleData tuple2;
793 int ncolumns1;
794 int ncolumns2;
795 RecordCompareData *my_extra;
796 int ncols;
797 Datum *values1;
798 Datum *values2;
799 bool *nulls1;
800 bool *nulls2;
801 int i1;
802 int i2;
803 int j;
804
805 check_stack_depth(); /* recurses for record-type columns */
806
807 /* Extract type info from the tuples */
808 tupType1 = HeapTupleHeaderGetTypeId(record1);
809 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
810 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
811 ncolumns1 = tupdesc1->natts;
812 tupType2 = HeapTupleHeaderGetTypeId(record2);
813 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
814 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
815 ncolumns2 = tupdesc2->natts;
816
817 /* Build temporary HeapTuple control structures */
818 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
819 ItemPointerSetInvalid(&(tuple1.t_self));
820 tuple1.t_tableOid = InvalidOid;
821 tuple1.t_data = record1;
822 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
823 ItemPointerSetInvalid(&(tuple2.t_self));
824 tuple2.t_tableOid = InvalidOid;
825 tuple2.t_data = record2;
826
827 /*
828 * We arrange to look up the needed comparison info just once per series
829 * of calls, assuming the record types don't change underneath us.
830 */
831 ncols = Max(ncolumns1, ncolumns2);
832 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
833 if (my_extra == NULL ||
834 my_extra->ncolumns < ncols)
835 {
836 fcinfo->flinfo->fn_extra =
837 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
838 offsetof(RecordCompareData, columns) +
839 ncols * sizeof(ColumnCompareData));
840 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
841 my_extra->ncolumns = ncols;
842 my_extra->record1_type = InvalidOid;
843 my_extra->record1_typmod = 0;
844 my_extra->record2_type = InvalidOid;
845 my_extra->record2_typmod = 0;
846 }
847
848 if (my_extra->record1_type != tupType1 ||
849 my_extra->record1_typmod != tupTypmod1 ||
850 my_extra->record2_type != tupType2 ||
851 my_extra->record2_typmod != tupTypmod2)
852 {
853 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
854 my_extra->record1_type = tupType1;
855 my_extra->record1_typmod = tupTypmod1;
856 my_extra->record2_type = tupType2;
857 my_extra->record2_typmod = tupTypmod2;
858 }
859
860 /* Break down the tuples into fields */
861 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
862 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
863 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
864 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
865 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
866 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
867
868 /*
869 * Scan corresponding columns, allowing for dropped columns in different
870 * places in the two rows. i1 and i2 are physical column indexes, j is
871 * the logical column index.
872 */
873 i1 = i2 = j = 0;
874 while (i1 < ncolumns1 || i2 < ncolumns2)
875 {
876 TypeCacheEntry *typentry;
877 Oid collation;
878
879 /*
880 * Skip dropped columns
881 */
882 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
883 {
884 i1++;
885 continue;
886 }
887 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
888 {
889 i2++;
890 continue;
891 }
892 if (i1 >= ncolumns1 || i2 >= ncolumns2)
893 break; /* we'll deal with mismatch below loop */
894
895 /*
896 * Have two matching columns, they must be same type
897 */
898 if (tupdesc1->attrs[i1]->atttypid !=
899 tupdesc2->attrs[i2]->atttypid)
900 ereport(ERROR,
901 (errcode(ERRCODE_DATATYPE_MISMATCH),
902 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
903 format_type_be(tupdesc1->attrs[i1]->atttypid),
904 format_type_be(tupdesc2->attrs[i2]->atttypid),
905 j + 1)));
906
907 /*
908 * If they're not same collation, we don't complain here, but the
909 * comparison function might.
910 */
911 collation = tupdesc1->attrs[i1]->attcollation;
912 if (collation != tupdesc2->attrs[i2]->attcollation)
913 collation = InvalidOid;
914
915 /*
916 * Lookup the comparison function if not done already
917 */
918 typentry = my_extra->columns[j].typentry;
919 if (typentry == NULL ||
920 typentry->type_id != tupdesc1->attrs[i1]->atttypid)
921 {
922 typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
923 TYPECACHE_CMP_PROC_FINFO);
924 if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
925 ereport(ERROR,
926 (errcode(ERRCODE_UNDEFINED_FUNCTION),
927 errmsg("could not identify a comparison function for type %s",
928 format_type_be(typentry->type_id))));
929 my_extra->columns[j].typentry = typentry;
930 }
931
932 /*
933 * We consider two NULLs equal; NULL > not-NULL.
934 */
935 if (!nulls1[i1] || !nulls2[i2])
936 {
937 FunctionCallInfoData locfcinfo;
938 int32 cmpresult;
939
940 if (nulls1[i1])
941 {
942 /* arg1 is greater than arg2 */
943 result = 1;
944 break;
945 }
946 if (nulls2[i2])
947 {
948 /* arg1 is less than arg2 */
949 result = -1;
950 break;
951 }
952
953 /* Compare the pair of elements */
954 InitFunctionCallInfoData(locfcinfo, &typentry->cmp_proc_finfo, 2,
955 collation, NULL, NULL);
956 locfcinfo.arg[0] = values1[i1];
957 locfcinfo.arg[1] = values2[i2];
958 locfcinfo.argnull[0] = false;
959 locfcinfo.argnull[1] = false;
960 locfcinfo.isnull = false;
961 cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
962
963 if (cmpresult < 0)
964 {
965 /* arg1 is less than arg2 */
966 result = -1;
967 break;
968 }
969 else if (cmpresult > 0)
970 {
971 /* arg1 is greater than arg2 */
972 result = 1;
973 break;
974 }
975 }
976
977 /* equal, so continue to next column */
978 i1++, i2++, j++;
979 }
980
981 /*
982 * If we didn't break out of the loop early, check for column count
983 * mismatch. (We do not report such mismatch if we found unequal column
984 * values; is that a feature or a bug?)
985 */
986 if (result == 0)
987 {
988 if (i1 != ncolumns1 || i2 != ncolumns2)
989 ereport(ERROR,
990 (errcode(ERRCODE_DATATYPE_MISMATCH),
991 errmsg("cannot compare record types with different numbers of columns")));
992 }
993
994 pfree(values1);
995 pfree(nulls1);
996 pfree(values2);
997 pfree(nulls2);
998 ReleaseTupleDesc(tupdesc1);
999 ReleaseTupleDesc(tupdesc2);
1000
1001 /* Avoid leaking memory when handed toasted input. */
1002 PG_FREE_IF_COPY(record1, 0);
1003 PG_FREE_IF_COPY(record2, 1);
1004
1005 return result;
1006 }
1007
1008 /*
1009 * record_eq :
1010 * compares two records for equality
1011 * result :
1012 * returns true if the records are equal, false otherwise.
1013 *
1014 * Note: we do not use record_cmp here, since equality may be meaningful in
1015 * datatypes that don't have a total ordering (and hence no btree support).
1016 */
1017 Datum
record_eq(PG_FUNCTION_ARGS)1018 record_eq(PG_FUNCTION_ARGS)
1019 {
1020 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1021 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1022 bool result = true;
1023 Oid tupType1;
1024 Oid tupType2;
1025 int32 tupTypmod1;
1026 int32 tupTypmod2;
1027 TupleDesc tupdesc1;
1028 TupleDesc tupdesc2;
1029 HeapTupleData tuple1;
1030 HeapTupleData tuple2;
1031 int ncolumns1;
1032 int ncolumns2;
1033 RecordCompareData *my_extra;
1034 int ncols;
1035 Datum *values1;
1036 Datum *values2;
1037 bool *nulls1;
1038 bool *nulls2;
1039 int i1;
1040 int i2;
1041 int j;
1042
1043 check_stack_depth(); /* recurses for record-type columns */
1044
1045 /* Extract type info from the tuples */
1046 tupType1 = HeapTupleHeaderGetTypeId(record1);
1047 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1048 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1049 ncolumns1 = tupdesc1->natts;
1050 tupType2 = HeapTupleHeaderGetTypeId(record2);
1051 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1052 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1053 ncolumns2 = tupdesc2->natts;
1054
1055 /* Build temporary HeapTuple control structures */
1056 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1057 ItemPointerSetInvalid(&(tuple1.t_self));
1058 tuple1.t_tableOid = InvalidOid;
1059 tuple1.t_data = record1;
1060 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1061 ItemPointerSetInvalid(&(tuple2.t_self));
1062 tuple2.t_tableOid = InvalidOid;
1063 tuple2.t_data = record2;
1064
1065 /*
1066 * We arrange to look up the needed comparison info just once per series
1067 * of calls, assuming the record types don't change underneath us.
1068 */
1069 ncols = Max(ncolumns1, ncolumns2);
1070 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1071 if (my_extra == NULL ||
1072 my_extra->ncolumns < ncols)
1073 {
1074 fcinfo->flinfo->fn_extra =
1075 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1076 offsetof(RecordCompareData, columns) +
1077 ncols * sizeof(ColumnCompareData));
1078 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1079 my_extra->ncolumns = ncols;
1080 my_extra->record1_type = InvalidOid;
1081 my_extra->record1_typmod = 0;
1082 my_extra->record2_type = InvalidOid;
1083 my_extra->record2_typmod = 0;
1084 }
1085
1086 if (my_extra->record1_type != tupType1 ||
1087 my_extra->record1_typmod != tupTypmod1 ||
1088 my_extra->record2_type != tupType2 ||
1089 my_extra->record2_typmod != tupTypmod2)
1090 {
1091 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1092 my_extra->record1_type = tupType1;
1093 my_extra->record1_typmod = tupTypmod1;
1094 my_extra->record2_type = tupType2;
1095 my_extra->record2_typmod = tupTypmod2;
1096 }
1097
1098 /* Break down the tuples into fields */
1099 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1100 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1101 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1102 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1103 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1104 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1105
1106 /*
1107 * Scan corresponding columns, allowing for dropped columns in different
1108 * places in the two rows. i1 and i2 are physical column indexes, j is
1109 * the logical column index.
1110 */
1111 i1 = i2 = j = 0;
1112 while (i1 < ncolumns1 || i2 < ncolumns2)
1113 {
1114 TypeCacheEntry *typentry;
1115 Oid collation;
1116 FunctionCallInfoData locfcinfo;
1117 bool oprresult;
1118
1119 /*
1120 * Skip dropped columns
1121 */
1122 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1123 {
1124 i1++;
1125 continue;
1126 }
1127 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1128 {
1129 i2++;
1130 continue;
1131 }
1132 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1133 break; /* we'll deal with mismatch below loop */
1134
1135 /*
1136 * Have two matching columns, they must be same type
1137 */
1138 if (tupdesc1->attrs[i1]->atttypid !=
1139 tupdesc2->attrs[i2]->atttypid)
1140 ereport(ERROR,
1141 (errcode(ERRCODE_DATATYPE_MISMATCH),
1142 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1143 format_type_be(tupdesc1->attrs[i1]->atttypid),
1144 format_type_be(tupdesc2->attrs[i2]->atttypid),
1145 j + 1)));
1146
1147 /*
1148 * If they're not same collation, we don't complain here, but the
1149 * equality function might.
1150 */
1151 collation = tupdesc1->attrs[i1]->attcollation;
1152 if (collation != tupdesc2->attrs[i2]->attcollation)
1153 collation = InvalidOid;
1154
1155 /*
1156 * Lookup the equality function if not done already
1157 */
1158 typentry = my_extra->columns[j].typentry;
1159 if (typentry == NULL ||
1160 typentry->type_id != tupdesc1->attrs[i1]->atttypid)
1161 {
1162 typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
1163 TYPECACHE_EQ_OPR_FINFO);
1164 if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1165 ereport(ERROR,
1166 (errcode(ERRCODE_UNDEFINED_FUNCTION),
1167 errmsg("could not identify an equality operator for type %s",
1168 format_type_be(typentry->type_id))));
1169 my_extra->columns[j].typentry = typentry;
1170 }
1171
1172 /*
1173 * We consider two NULLs equal; NULL > not-NULL.
1174 */
1175 if (!nulls1[i1] || !nulls2[i2])
1176 {
1177 if (nulls1[i1] || nulls2[i2])
1178 {
1179 result = false;
1180 break;
1181 }
1182
1183 /* Compare the pair of elements */
1184 InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
1185 collation, NULL, NULL);
1186 locfcinfo.arg[0] = values1[i1];
1187 locfcinfo.arg[1] = values2[i2];
1188 locfcinfo.argnull[0] = false;
1189 locfcinfo.argnull[1] = false;
1190 locfcinfo.isnull = false;
1191 oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
1192 if (!oprresult)
1193 {
1194 result = false;
1195 break;
1196 }
1197 }
1198
1199 /* equal, so continue to next column */
1200 i1++, i2++, j++;
1201 }
1202
1203 /*
1204 * If we didn't break out of the loop early, check for column count
1205 * mismatch. (We do not report such mismatch if we found unequal column
1206 * values; is that a feature or a bug?)
1207 */
1208 if (result)
1209 {
1210 if (i1 != ncolumns1 || i2 != ncolumns2)
1211 ereport(ERROR,
1212 (errcode(ERRCODE_DATATYPE_MISMATCH),
1213 errmsg("cannot compare record types with different numbers of columns")));
1214 }
1215
1216 pfree(values1);
1217 pfree(nulls1);
1218 pfree(values2);
1219 pfree(nulls2);
1220 ReleaseTupleDesc(tupdesc1);
1221 ReleaseTupleDesc(tupdesc2);
1222
1223 /* Avoid leaking memory when handed toasted input. */
1224 PG_FREE_IF_COPY(record1, 0);
1225 PG_FREE_IF_COPY(record2, 1);
1226
1227 PG_RETURN_BOOL(result);
1228 }
1229
1230 Datum
record_ne(PG_FUNCTION_ARGS)1231 record_ne(PG_FUNCTION_ARGS)
1232 {
1233 PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
1234 }
1235
1236 Datum
record_lt(PG_FUNCTION_ARGS)1237 record_lt(PG_FUNCTION_ARGS)
1238 {
1239 PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
1240 }
1241
1242 Datum
record_gt(PG_FUNCTION_ARGS)1243 record_gt(PG_FUNCTION_ARGS)
1244 {
1245 PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
1246 }
1247
1248 Datum
record_le(PG_FUNCTION_ARGS)1249 record_le(PG_FUNCTION_ARGS)
1250 {
1251 PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
1252 }
1253
1254 Datum
record_ge(PG_FUNCTION_ARGS)1255 record_ge(PG_FUNCTION_ARGS)
1256 {
1257 PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
1258 }
1259
1260 Datum
btrecordcmp(PG_FUNCTION_ARGS)1261 btrecordcmp(PG_FUNCTION_ARGS)
1262 {
1263 PG_RETURN_INT32(record_cmp(fcinfo));
1264 }
1265
1266
1267 /*
1268 * record_image_cmp :
1269 * Internal byte-oriented comparison function for records.
1270 *
1271 * Returns -1, 0 or 1
1272 *
1273 * Note: The normal concepts of "equality" do not apply here; different
1274 * representation of values considered to be equal are not considered to be
1275 * identical. As an example, for the citext type 'A' and 'a' are equal, but
1276 * they are not identical.
1277 */
1278 static int
record_image_cmp(FunctionCallInfo fcinfo)1279 record_image_cmp(FunctionCallInfo fcinfo)
1280 {
1281 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1282 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1283 int result = 0;
1284 Oid tupType1;
1285 Oid tupType2;
1286 int32 tupTypmod1;
1287 int32 tupTypmod2;
1288 TupleDesc tupdesc1;
1289 TupleDesc tupdesc2;
1290 HeapTupleData tuple1;
1291 HeapTupleData tuple2;
1292 int ncolumns1;
1293 int ncolumns2;
1294 RecordCompareData *my_extra;
1295 int ncols;
1296 Datum *values1;
1297 Datum *values2;
1298 bool *nulls1;
1299 bool *nulls2;
1300 int i1;
1301 int i2;
1302 int j;
1303
1304 /* Extract type info from the tuples */
1305 tupType1 = HeapTupleHeaderGetTypeId(record1);
1306 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1307 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1308 ncolumns1 = tupdesc1->natts;
1309 tupType2 = HeapTupleHeaderGetTypeId(record2);
1310 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1311 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1312 ncolumns2 = tupdesc2->natts;
1313
1314 /* Build temporary HeapTuple control structures */
1315 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1316 ItemPointerSetInvalid(&(tuple1.t_self));
1317 tuple1.t_tableOid = InvalidOid;
1318 tuple1.t_data = record1;
1319 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1320 ItemPointerSetInvalid(&(tuple2.t_self));
1321 tuple2.t_tableOid = InvalidOid;
1322 tuple2.t_data = record2;
1323
1324 /*
1325 * We arrange to look up the needed comparison info just once per series
1326 * of calls, assuming the record types don't change underneath us.
1327 */
1328 ncols = Max(ncolumns1, ncolumns2);
1329 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1330 if (my_extra == NULL ||
1331 my_extra->ncolumns < ncols)
1332 {
1333 fcinfo->flinfo->fn_extra =
1334 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1335 offsetof(RecordCompareData, columns) +
1336 ncols * sizeof(ColumnCompareData));
1337 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1338 my_extra->ncolumns = ncols;
1339 my_extra->record1_type = InvalidOid;
1340 my_extra->record1_typmod = 0;
1341 my_extra->record2_type = InvalidOid;
1342 my_extra->record2_typmod = 0;
1343 }
1344
1345 if (my_extra->record1_type != tupType1 ||
1346 my_extra->record1_typmod != tupTypmod1 ||
1347 my_extra->record2_type != tupType2 ||
1348 my_extra->record2_typmod != tupTypmod2)
1349 {
1350 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1351 my_extra->record1_type = tupType1;
1352 my_extra->record1_typmod = tupTypmod1;
1353 my_extra->record2_type = tupType2;
1354 my_extra->record2_typmod = tupTypmod2;
1355 }
1356
1357 /* Break down the tuples into fields */
1358 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1359 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1360 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1361 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1362 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1363 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1364
1365 /*
1366 * Scan corresponding columns, allowing for dropped columns in different
1367 * places in the two rows. i1 and i2 are physical column indexes, j is
1368 * the logical column index.
1369 */
1370 i1 = i2 = j = 0;
1371 while (i1 < ncolumns1 || i2 < ncolumns2)
1372 {
1373 /*
1374 * Skip dropped columns
1375 */
1376 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1377 {
1378 i1++;
1379 continue;
1380 }
1381 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1382 {
1383 i2++;
1384 continue;
1385 }
1386 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1387 break; /* we'll deal with mismatch below loop */
1388
1389 /*
1390 * Have two matching columns, they must be same type
1391 */
1392 if (tupdesc1->attrs[i1]->atttypid !=
1393 tupdesc2->attrs[i2]->atttypid)
1394 ereport(ERROR,
1395 (errcode(ERRCODE_DATATYPE_MISMATCH),
1396 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1397 format_type_be(tupdesc1->attrs[i1]->atttypid),
1398 format_type_be(tupdesc2->attrs[i2]->atttypid),
1399 j + 1)));
1400
1401 /*
1402 * The same type should have the same length (or both should be
1403 * variable).
1404 */
1405 Assert(tupdesc1->attrs[i1]->attlen ==
1406 tupdesc2->attrs[i2]->attlen);
1407
1408 /*
1409 * We consider two NULLs equal; NULL > not-NULL.
1410 */
1411 if (!nulls1[i1] || !nulls2[i2])
1412 {
1413 int cmpresult = 0;
1414
1415 if (nulls1[i1])
1416 {
1417 /* arg1 is greater than arg2 */
1418 result = 1;
1419 break;
1420 }
1421 if (nulls2[i2])
1422 {
1423 /* arg1 is less than arg2 */
1424 result = -1;
1425 break;
1426 }
1427
1428 /* Compare the pair of elements */
1429 if (tupdesc1->attrs[i1]->attbyval)
1430 {
1431 switch (tupdesc1->attrs[i1]->attlen)
1432 {
1433 case 1:
1434 if (GET_1_BYTE(values1[i1]) !=
1435 GET_1_BYTE(values2[i2]))
1436 {
1437 cmpresult = (GET_1_BYTE(values1[i1]) <
1438 GET_1_BYTE(values2[i2])) ? -1 : 1;
1439 }
1440 break;
1441 case 2:
1442 if (GET_2_BYTES(values1[i1]) !=
1443 GET_2_BYTES(values2[i2]))
1444 {
1445 cmpresult = (GET_2_BYTES(values1[i1]) <
1446 GET_2_BYTES(values2[i2])) ? -1 : 1;
1447 }
1448 break;
1449 case 4:
1450 if (GET_4_BYTES(values1[i1]) !=
1451 GET_4_BYTES(values2[i2]))
1452 {
1453 cmpresult = (GET_4_BYTES(values1[i1]) <
1454 GET_4_BYTES(values2[i2])) ? -1 : 1;
1455 }
1456 break;
1457 #if SIZEOF_DATUM == 8
1458 case 8:
1459 if (GET_8_BYTES(values1[i1]) !=
1460 GET_8_BYTES(values2[i2]))
1461 {
1462 cmpresult = (GET_8_BYTES(values1[i1]) <
1463 GET_8_BYTES(values2[i2])) ? -1 : 1;
1464 }
1465 break;
1466 #endif
1467 default:
1468 Assert(false); /* cannot happen */
1469 }
1470 }
1471 else if (tupdesc1->attrs[i1]->attlen > 0)
1472 {
1473 cmpresult = memcmp(DatumGetPointer(values1[i1]),
1474 DatumGetPointer(values2[i2]),
1475 tupdesc1->attrs[i1]->attlen);
1476 }
1477 else if (tupdesc1->attrs[i1]->attlen == -1)
1478 {
1479 Size len1,
1480 len2;
1481 struct varlena *arg1val;
1482 struct varlena *arg2val;
1483
1484 len1 = toast_raw_datum_size(values1[i1]);
1485 len2 = toast_raw_datum_size(values2[i2]);
1486 arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
1487 arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
1488
1489 cmpresult = memcmp(VARDATA_ANY(arg1val),
1490 VARDATA_ANY(arg2val),
1491 Min(len1, len2) - VARHDRSZ);
1492 if ((cmpresult == 0) && (len1 != len2))
1493 cmpresult = (len1 < len2) ? -1 : 1;
1494
1495 if ((Pointer) arg1val != (Pointer) values1[i1])
1496 pfree(arg1val);
1497 if ((Pointer) arg2val != (Pointer) values2[i2])
1498 pfree(arg2val);
1499 }
1500 else
1501 elog(ERROR, "unexpected attlen: %d", tupdesc1->attrs[i1]->attlen);
1502
1503 if (cmpresult < 0)
1504 {
1505 /* arg1 is less than arg2 */
1506 result = -1;
1507 break;
1508 }
1509 else if (cmpresult > 0)
1510 {
1511 /* arg1 is greater than arg2 */
1512 result = 1;
1513 break;
1514 }
1515 }
1516
1517 /* equal, so continue to next column */
1518 i1++, i2++, j++;
1519 }
1520
1521 /*
1522 * If we didn't break out of the loop early, check for column count
1523 * mismatch. (We do not report such mismatch if we found unequal column
1524 * values; is that a feature or a bug?)
1525 */
1526 if (result == 0)
1527 {
1528 if (i1 != ncolumns1 || i2 != ncolumns2)
1529 ereport(ERROR,
1530 (errcode(ERRCODE_DATATYPE_MISMATCH),
1531 errmsg("cannot compare record types with different numbers of columns")));
1532 }
1533
1534 pfree(values1);
1535 pfree(nulls1);
1536 pfree(values2);
1537 pfree(nulls2);
1538 ReleaseTupleDesc(tupdesc1);
1539 ReleaseTupleDesc(tupdesc2);
1540
1541 /* Avoid leaking memory when handed toasted input. */
1542 PG_FREE_IF_COPY(record1, 0);
1543 PG_FREE_IF_COPY(record2, 1);
1544
1545 return result;
1546 }
1547
1548 /*
1549 * record_image_eq :
1550 * compares two records for identical contents, based on byte images
1551 * result :
1552 * returns true if the records are identical, false otherwise.
1553 *
1554 * Note: we do not use record_image_cmp here, since we can avoid
1555 * de-toasting for unequal lengths this way.
1556 */
1557 Datum
record_image_eq(PG_FUNCTION_ARGS)1558 record_image_eq(PG_FUNCTION_ARGS)
1559 {
1560 HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1561 HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1562 bool result = true;
1563 Oid tupType1;
1564 Oid tupType2;
1565 int32 tupTypmod1;
1566 int32 tupTypmod2;
1567 TupleDesc tupdesc1;
1568 TupleDesc tupdesc2;
1569 HeapTupleData tuple1;
1570 HeapTupleData tuple2;
1571 int ncolumns1;
1572 int ncolumns2;
1573 RecordCompareData *my_extra;
1574 int ncols;
1575 Datum *values1;
1576 Datum *values2;
1577 bool *nulls1;
1578 bool *nulls2;
1579 int i1;
1580 int i2;
1581 int j;
1582
1583 /* Extract type info from the tuples */
1584 tupType1 = HeapTupleHeaderGetTypeId(record1);
1585 tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1586 tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1587 ncolumns1 = tupdesc1->natts;
1588 tupType2 = HeapTupleHeaderGetTypeId(record2);
1589 tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1590 tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1591 ncolumns2 = tupdesc2->natts;
1592
1593 /* Build temporary HeapTuple control structures */
1594 tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1595 ItemPointerSetInvalid(&(tuple1.t_self));
1596 tuple1.t_tableOid = InvalidOid;
1597 tuple1.t_data = record1;
1598 tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1599 ItemPointerSetInvalid(&(tuple2.t_self));
1600 tuple2.t_tableOid = InvalidOid;
1601 tuple2.t_data = record2;
1602
1603 /*
1604 * We arrange to look up the needed comparison info just once per series
1605 * of calls, assuming the record types don't change underneath us.
1606 */
1607 ncols = Max(ncolumns1, ncolumns2);
1608 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1609 if (my_extra == NULL ||
1610 my_extra->ncolumns < ncols)
1611 {
1612 fcinfo->flinfo->fn_extra =
1613 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1614 offsetof(RecordCompareData, columns) +
1615 ncols * sizeof(ColumnCompareData));
1616 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1617 my_extra->ncolumns = ncols;
1618 my_extra->record1_type = InvalidOid;
1619 my_extra->record1_typmod = 0;
1620 my_extra->record2_type = InvalidOid;
1621 my_extra->record2_typmod = 0;
1622 }
1623
1624 if (my_extra->record1_type != tupType1 ||
1625 my_extra->record1_typmod != tupTypmod1 ||
1626 my_extra->record2_type != tupType2 ||
1627 my_extra->record2_typmod != tupTypmod2)
1628 {
1629 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1630 my_extra->record1_type = tupType1;
1631 my_extra->record1_typmod = tupTypmod1;
1632 my_extra->record2_type = tupType2;
1633 my_extra->record2_typmod = tupTypmod2;
1634 }
1635
1636 /* Break down the tuples into fields */
1637 values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1638 nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1639 heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1640 values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1641 nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1642 heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1643
1644 /*
1645 * Scan corresponding columns, allowing for dropped columns in different
1646 * places in the two rows. i1 and i2 are physical column indexes, j is
1647 * the logical column index.
1648 */
1649 i1 = i2 = j = 0;
1650 while (i1 < ncolumns1 || i2 < ncolumns2)
1651 {
1652 /*
1653 * Skip dropped columns
1654 */
1655 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1656 {
1657 i1++;
1658 continue;
1659 }
1660 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1661 {
1662 i2++;
1663 continue;
1664 }
1665 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1666 break; /* we'll deal with mismatch below loop */
1667
1668 /*
1669 * Have two matching columns, they must be same type
1670 */
1671 if (tupdesc1->attrs[i1]->atttypid !=
1672 tupdesc2->attrs[i2]->atttypid)
1673 ereport(ERROR,
1674 (errcode(ERRCODE_DATATYPE_MISMATCH),
1675 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1676 format_type_be(tupdesc1->attrs[i1]->atttypid),
1677 format_type_be(tupdesc2->attrs[i2]->atttypid),
1678 j + 1)));
1679
1680 /*
1681 * We consider two NULLs equal; NULL > not-NULL.
1682 */
1683 if (!nulls1[i1] || !nulls2[i2])
1684 {
1685 if (nulls1[i1] || nulls2[i2])
1686 {
1687 result = false;
1688 break;
1689 }
1690
1691 /* Compare the pair of elements */
1692 if (tupdesc1->attrs[i1]->attbyval)
1693 {
1694 switch (tupdesc1->attrs[i1]->attlen)
1695 {
1696 case 1:
1697 result = (GET_1_BYTE(values1[i1]) ==
1698 GET_1_BYTE(values2[i2]));
1699 break;
1700 case 2:
1701 result = (GET_2_BYTES(values1[i1]) ==
1702 GET_2_BYTES(values2[i2]));
1703 break;
1704 case 4:
1705 result = (GET_4_BYTES(values1[i1]) ==
1706 GET_4_BYTES(values2[i2]));
1707 break;
1708 #if SIZEOF_DATUM == 8
1709 case 8:
1710 result = (GET_8_BYTES(values1[i1]) ==
1711 GET_8_BYTES(values2[i2]));
1712 break;
1713 #endif
1714 default:
1715 Assert(false); /* cannot happen */
1716 }
1717 }
1718 else if (tupdesc1->attrs[i1]->attlen > 0)
1719 {
1720 result = (memcmp(DatumGetPointer(values1[i1]),
1721 DatumGetPointer(values2[i2]),
1722 tupdesc1->attrs[i1]->attlen) == 0);
1723 }
1724 else if (tupdesc1->attrs[i1]->attlen == -1)
1725 {
1726 Size len1,
1727 len2;
1728
1729 len1 = toast_raw_datum_size(values1[i1]);
1730 len2 = toast_raw_datum_size(values2[i2]);
1731 /* No need to de-toast if lengths don't match. */
1732 if (len1 != len2)
1733 result = false;
1734 else
1735 {
1736 struct varlena *arg1val;
1737 struct varlena *arg2val;
1738
1739 arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
1740 arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
1741
1742 result = (memcmp(VARDATA_ANY(arg1val),
1743 VARDATA_ANY(arg2val),
1744 len1 - VARHDRSZ) == 0);
1745
1746 /* Only free memory if it's a copy made here. */
1747 if ((Pointer) arg1val != (Pointer) values1[i1])
1748 pfree(arg1val);
1749 if ((Pointer) arg2val != (Pointer) values2[i2])
1750 pfree(arg2val);
1751 }
1752 }
1753 else
1754 elog(ERROR, "unexpected attlen: %d", tupdesc1->attrs[i1]->attlen);
1755
1756 if (!result)
1757 break;
1758 }
1759
1760 /* equal, so continue to next column */
1761 i1++, i2++, j++;
1762 }
1763
1764 /*
1765 * If we didn't break out of the loop early, check for column count
1766 * mismatch. (We do not report such mismatch if we found unequal column
1767 * values; is that a feature or a bug?)
1768 */
1769 if (result)
1770 {
1771 if (i1 != ncolumns1 || i2 != ncolumns2)
1772 ereport(ERROR,
1773 (errcode(ERRCODE_DATATYPE_MISMATCH),
1774 errmsg("cannot compare record types with different numbers of columns")));
1775 }
1776
1777 pfree(values1);
1778 pfree(nulls1);
1779 pfree(values2);
1780 pfree(nulls2);
1781 ReleaseTupleDesc(tupdesc1);
1782 ReleaseTupleDesc(tupdesc2);
1783
1784 /* Avoid leaking memory when handed toasted input. */
1785 PG_FREE_IF_COPY(record1, 0);
1786 PG_FREE_IF_COPY(record2, 1);
1787
1788 PG_RETURN_BOOL(result);
1789 }
1790
1791 Datum
record_image_ne(PG_FUNCTION_ARGS)1792 record_image_ne(PG_FUNCTION_ARGS)
1793 {
1794 PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo)));
1795 }
1796
1797 Datum
record_image_lt(PG_FUNCTION_ARGS)1798 record_image_lt(PG_FUNCTION_ARGS)
1799 {
1800 PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0);
1801 }
1802
1803 Datum
record_image_gt(PG_FUNCTION_ARGS)1804 record_image_gt(PG_FUNCTION_ARGS)
1805 {
1806 PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0);
1807 }
1808
1809 Datum
record_image_le(PG_FUNCTION_ARGS)1810 record_image_le(PG_FUNCTION_ARGS)
1811 {
1812 PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0);
1813 }
1814
1815 Datum
record_image_ge(PG_FUNCTION_ARGS)1816 record_image_ge(PG_FUNCTION_ARGS)
1817 {
1818 PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0);
1819 }
1820
1821 Datum
btrecordimagecmp(PG_FUNCTION_ARGS)1822 btrecordimagecmp(PG_FUNCTION_ARGS)
1823 {
1824 PG_RETURN_INT32(record_image_cmp(fcinfo));
1825 }
1826