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