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