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