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