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