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 34562 : record_out(PG_FUNCTION_ARGS)
331 : {
332 34562 : HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
333 : Oid tupType;
334 : int32 tupTypmod;
335 : TupleDesc tupdesc;
336 : HeapTupleData tuple;
337 : RecordIOData *my_extra;
338 34562 : bool needComma = false;
339 : int ncolumns;
340 : int i;
341 : Datum *values;
342 : bool *nulls;
343 : StringInfoData buf;
344 :
345 34562 : check_stack_depth(); /* recurses for record-type columns */
346 :
347 : /* Extract type info from the tuple itself */
348 34562 : tupType = HeapTupleHeaderGetTypeId(rec);
349 34562 : tupTypmod = HeapTupleHeaderGetTypMod(rec);
350 34562 : tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
351 34562 : ncolumns = tupdesc->natts;
352 :
353 : /* Build a temporary HeapTuple control structure */
354 34562 : tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
355 34562 : ItemPointerSetInvalid(&(tuple.t_self));
356 34562 : tuple.t_tableOid = InvalidOid;
357 34562 : 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 34562 : my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
364 34562 : if (my_extra == NULL ||
365 30002 : my_extra->ncolumns != ncolumns)
366 : {
367 9168 : fcinfo->flinfo->fn_extra =
368 4584 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
369 : offsetof(RecordIOData, columns) +
370 4584 : ncolumns * sizeof(ColumnIOData));
371 4584 : my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
372 4584 : my_extra->record_type = InvalidOid;
373 4584 : my_extra->record_typmod = 0;
374 : }
375 :
376 34562 : if (my_extra->record_type != tupType ||
377 29978 : my_extra->record_typmod != tupTypmod)
378 : {
379 103136 : MemSet(my_extra, 0,
380 : offsetof(RecordIOData, columns) +
381 : ncolumns * sizeof(ColumnIOData));
382 4624 : my_extra->record_type = tupType;
383 4624 : my_extra->record_typmod = tupTypmod;
384 4624 : my_extra->ncolumns = ncolumns;
385 : }
386 :
387 34562 : values = (Datum *) palloc(ncolumns * sizeof(Datum));
388 34562 : nulls = (bool *) palloc(ncolumns * sizeof(bool));
389 :
390 : /* Break down the tuple into fields */
391 34562 : heap_deform_tuple(&tuple, tupdesc, values, nulls);
392 :
393 : /* And build the result string */
394 34562 : initStringInfo(&buf);
395 :
396 34562 : appendStringInfoChar(&buf, '(');
397 :
398 210640 : for (i = 0; i < ncolumns; i++)
399 : {
400 176078 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
401 176078 : ColumnIOData *column_info = &my_extra->columns[i];
402 176078 : 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 176078 : if (att->attisdropped)
410 492 : continue;
411 :
412 175586 : if (needComma)
413 141030 : appendStringInfoChar(&buf, ',');
414 175586 : needComma = true;
415 :
416 175586 : if (nulls[i])
417 : {
418 : /* emit nothing... */
419 4034 : continue;
420 : }
421 :
422 : /*
423 : * Convert the column value to text
424 : */
425 171552 : if (column_info->column_type != column_type)
426 : {
427 10542 : getTypeOutputInfo(column_type,
428 : &column_info->typiofunc,
429 : &column_info->typisvarlena);
430 10542 : fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
431 10542 : fcinfo->flinfo->fn_mcxt);
432 10542 : column_info->column_type = column_type;
433 : }
434 :
435 171552 : attr = values[i];
436 171552 : value = OutputFunctionCall(&column_info->proc, attr);
437 :
438 : /* Detect whether we need double quotes for this value */
439 171552 : nq = (value[0] == '\0'); /* force quotes for empty string */
440 108256184 : for (tmp = value; *tmp; tmp++)
441 : {
442 108136268 : char ch = *tmp;
443 :
444 108136268 : if (ch == '"' || ch == '\\' ||
445 108135386 : ch == '(' || ch == ')' || ch == ',' ||
446 108134488 : isspace((unsigned char) ch))
447 : {
448 51636 : nq = true;
449 51636 : break;
450 : }
451 : }
452 :
453 : /* And emit the string */
454 171552 : if (nq)
455 51640 : appendStringInfoCharMacro(&buf, '"');
456 108777852 : for (tmp = value; *tmp; tmp++)
457 : {
458 108606300 : char ch = *tmp;
459 :
460 108606300 : if (ch == '"' || ch == '\\')
461 960 : appendStringInfoCharMacro(&buf, ch);
462 108606300 : appendStringInfoCharMacro(&buf, ch);
463 : }
464 171552 : if (nq)
465 51640 : appendStringInfoCharMacro(&buf, '"');
466 : }
467 :
468 34562 : appendStringInfoChar(&buf, ')');
469 :
470 34562 : pfree(values);
471 34562 : pfree(nulls);
472 34562 : ReleaseTupleDesc(tupdesc);
473 :
474 34562 : 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 :
573 : /* Ignore dropped columns in datatype, but fill with nulls */
574 0 : if (att->attisdropped)
575 : {
576 0 : values[i] = (Datum) 0;
577 0 : nulls[i] = true;
578 0 : continue;
579 : }
580 :
581 : /* Check column type recorded in the data */
582 0 : coltypoid = pq_getmsgint(buf, sizeof(Oid));
583 :
584 : /*
585 : * From a security standpoint, it doesn't matter whether the input's
586 : * column type matches what we expect: the column type's receive
587 : * function has to be robust enough to cope with invalid data.
588 : * However, from a user-friendliness standpoint, it's nicer to
589 : * complain about type mismatches than to throw "improper binary
590 : * format" errors. But there's a problem: only built-in types have
591 : * OIDs that are stable enough to believe that a mismatch is a real
592 : * issue. So complain only if both OIDs are in the built-in range.
593 : * Otherwise, carry on with the column type we "should" be getting.
594 : */
595 0 : if (coltypoid != column_type &&
596 0 : coltypoid < FirstGenbkiObjectId &&
597 : column_type < FirstGenbkiObjectId)
598 0 : ereport(ERROR,
599 : (errcode(ERRCODE_DATATYPE_MISMATCH),
600 : errmsg("binary data has type %u (%s) instead of expected %u (%s) in record column %d",
601 : coltypoid,
602 : format_type_extended(coltypoid, -1,
603 : FORMAT_TYPE_ALLOW_INVALID),
604 : column_type,
605 : format_type_extended(column_type, -1,
606 : FORMAT_TYPE_ALLOW_INVALID),
607 : i + 1)));
608 :
609 : /* Get and check the item length */
610 0 : itemlen = pq_getmsgint(buf, 4);
611 0 : if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
612 0 : ereport(ERROR,
613 : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
614 : errmsg("insufficient data left in message")));
615 :
616 0 : if (itemlen == -1)
617 : {
618 : /* -1 length means NULL */
619 0 : bufptr = NULL;
620 0 : nulls[i] = true;
621 : }
622 : else
623 : {
624 : char *strbuff;
625 :
626 : /*
627 : * Rather than copying data around, we just initialize a
628 : * StringInfo pointing to the correct portion of the message
629 : * buffer.
630 : */
631 0 : strbuff = &buf->data[buf->cursor];
632 0 : buf->cursor += itemlen;
633 0 : initReadOnlyStringInfo(&item_buf, strbuff, itemlen);
634 :
635 0 : bufptr = &item_buf;
636 0 : nulls[i] = false;
637 : }
638 :
639 : /* Now call the column's receiveproc */
640 0 : if (column_info->column_type != column_type)
641 : {
642 0 : getTypeBinaryInputInfo(column_type,
643 : &column_info->typiofunc,
644 : &column_info->typioparam);
645 0 : fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
646 0 : fcinfo->flinfo->fn_mcxt);
647 0 : column_info->column_type = column_type;
648 : }
649 :
650 0 : values[i] = ReceiveFunctionCall(&column_info->proc,
651 : bufptr,
652 : column_info->typioparam,
653 : att->atttypmod);
654 :
655 0 : if (bufptr)
656 : {
657 : /* Trouble if it didn't eat the whole buffer */
658 0 : if (item_buf.cursor != itemlen)
659 0 : ereport(ERROR,
660 : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
661 : errmsg("improper binary format in record column %d",
662 : i + 1)));
663 : }
664 : }
665 :
666 0 : tuple = heap_form_tuple(tupdesc, values, nulls);
667 :
668 : /*
669 : * We cannot return tuple->t_data because heap_form_tuple allocates it as
670 : * part of a larger chunk, and our caller may expect to be able to pfree
671 : * our result. So must copy the info into a new palloc chunk.
672 : */
673 0 : result = (HeapTupleHeader) palloc(tuple->t_len);
674 0 : memcpy(result, tuple->t_data, tuple->t_len);
675 :
676 0 : heap_freetuple(tuple);
677 0 : pfree(values);
678 0 : pfree(nulls);
679 0 : ReleaseTupleDesc(tupdesc);
680 :
681 0 : PG_RETURN_HEAPTUPLEHEADER(result);
682 : }
683 :
684 : /*
685 : * record_send - binary output routine for any composite type.
686 : */
687 : Datum
688 0 : record_send(PG_FUNCTION_ARGS)
689 : {
690 0 : HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
691 : Oid tupType;
692 : int32 tupTypmod;
693 : TupleDesc tupdesc;
694 : HeapTupleData tuple;
695 : RecordIOData *my_extra;
696 : int ncolumns;
697 : int validcols;
698 : int i;
699 : Datum *values;
700 : bool *nulls;
701 : StringInfoData buf;
702 :
703 0 : check_stack_depth(); /* recurses for record-type columns */
704 :
705 : /* Extract type info from the tuple itself */
706 0 : tupType = HeapTupleHeaderGetTypeId(rec);
707 0 : tupTypmod = HeapTupleHeaderGetTypMod(rec);
708 0 : tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
709 0 : ncolumns = tupdesc->natts;
710 :
711 : /* Build a temporary HeapTuple control structure */
712 0 : tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
713 0 : ItemPointerSetInvalid(&(tuple.t_self));
714 0 : tuple.t_tableOid = InvalidOid;
715 0 : tuple.t_data = rec;
716 :
717 : /*
718 : * We arrange to look up the needed I/O info just once per series of
719 : * calls, assuming the record type doesn't change underneath us.
720 : */
721 0 : my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
722 0 : if (my_extra == NULL ||
723 0 : my_extra->ncolumns != ncolumns)
724 : {
725 0 : fcinfo->flinfo->fn_extra =
726 0 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
727 : offsetof(RecordIOData, columns) +
728 0 : ncolumns * sizeof(ColumnIOData));
729 0 : my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
730 0 : my_extra->record_type = InvalidOid;
731 0 : my_extra->record_typmod = 0;
732 : }
733 :
734 0 : if (my_extra->record_type != tupType ||
735 0 : my_extra->record_typmod != tupTypmod)
736 : {
737 0 : MemSet(my_extra, 0,
738 : offsetof(RecordIOData, columns) +
739 : ncolumns * sizeof(ColumnIOData));
740 0 : my_extra->record_type = tupType;
741 0 : my_extra->record_typmod = tupTypmod;
742 0 : my_extra->ncolumns = ncolumns;
743 : }
744 :
745 0 : values = (Datum *) palloc(ncolumns * sizeof(Datum));
746 0 : nulls = (bool *) palloc(ncolumns * sizeof(bool));
747 :
748 : /* Break down the tuple into fields */
749 0 : heap_deform_tuple(&tuple, tupdesc, values, nulls);
750 :
751 : /* And build the result string */
752 0 : pq_begintypsend(&buf);
753 :
754 : /* Need to scan to count nondeleted columns */
755 0 : validcols = 0;
756 0 : for (i = 0; i < ncolumns; i++)
757 : {
758 0 : if (!TupleDescAttr(tupdesc, i)->attisdropped)
759 0 : validcols++;
760 : }
761 0 : pq_sendint32(&buf, validcols);
762 :
763 0 : for (i = 0; i < ncolumns; i++)
764 : {
765 0 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
766 0 : ColumnIOData *column_info = &my_extra->columns[i];
767 0 : Oid column_type = att->atttypid;
768 : Datum attr;
769 : bytea *outputbytes;
770 :
771 : /* Ignore dropped columns in datatype */
772 0 : if (att->attisdropped)
773 0 : continue;
774 :
775 0 : pq_sendint32(&buf, column_type);
776 :
777 0 : if (nulls[i])
778 : {
779 : /* emit -1 data length to signify a NULL */
780 0 : pq_sendint32(&buf, -1);
781 0 : continue;
782 : }
783 :
784 : /*
785 : * Convert the column value to binary
786 : */
787 0 : if (column_info->column_type != column_type)
788 : {
789 0 : getTypeBinaryOutputInfo(column_type,
790 : &column_info->typiofunc,
791 : &column_info->typisvarlena);
792 0 : fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
793 0 : fcinfo->flinfo->fn_mcxt);
794 0 : column_info->column_type = column_type;
795 : }
796 :
797 0 : attr = values[i];
798 0 : outputbytes = SendFunctionCall(&column_info->proc, attr);
799 0 : pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ);
800 0 : pq_sendbytes(&buf, VARDATA(outputbytes),
801 0 : VARSIZE(outputbytes) - VARHDRSZ);
802 : }
803 :
804 0 : pfree(values);
805 0 : pfree(nulls);
806 0 : ReleaseTupleDesc(tupdesc);
807 :
808 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
809 : }
810 :
811 :
812 : /*
813 : * record_cmp()
814 : * Internal comparison function for records.
815 : *
816 : * Returns -1, 0 or 1
817 : *
818 : * Do not assume that the two inputs are exactly the same record type;
819 : * for instance we might be comparing an anonymous ROW() construct against a
820 : * named composite type. We will compare as long as they have the same number
821 : * of non-dropped columns of the same types.
822 : */
823 : static int
824 4340 : record_cmp(FunctionCallInfo fcinfo)
825 : {
826 4340 : HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
827 4340 : HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
828 4340 : int result = 0;
829 : Oid tupType1;
830 : Oid tupType2;
831 : int32 tupTypmod1;
832 : int32 tupTypmod2;
833 : TupleDesc tupdesc1;
834 : TupleDesc tupdesc2;
835 : HeapTupleData tuple1;
836 : HeapTupleData tuple2;
837 : int ncolumns1;
838 : int ncolumns2;
839 : RecordCompareData *my_extra;
840 : int ncols;
841 : Datum *values1;
842 : Datum *values2;
843 : bool *nulls1;
844 : bool *nulls2;
845 : int i1;
846 : int i2;
847 : int j;
848 :
849 4340 : check_stack_depth(); /* recurses for record-type columns */
850 :
851 : /* Extract type info from the tuples */
852 4340 : tupType1 = HeapTupleHeaderGetTypeId(record1);
853 4340 : tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
854 4340 : tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
855 4340 : ncolumns1 = tupdesc1->natts;
856 4340 : tupType2 = HeapTupleHeaderGetTypeId(record2);
857 4340 : tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
858 4340 : tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
859 4340 : ncolumns2 = tupdesc2->natts;
860 :
861 : /* Build temporary HeapTuple control structures */
862 4340 : tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
863 4340 : ItemPointerSetInvalid(&(tuple1.t_self));
864 4340 : tuple1.t_tableOid = InvalidOid;
865 4340 : tuple1.t_data = record1;
866 4340 : tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
867 4340 : ItemPointerSetInvalid(&(tuple2.t_self));
868 4340 : tuple2.t_tableOid = InvalidOid;
869 4340 : tuple2.t_data = record2;
870 :
871 : /*
872 : * We arrange to look up the needed comparison info just once per series
873 : * of calls, assuming the record types don't change underneath us.
874 : */
875 4340 : ncols = Max(ncolumns1, ncolumns2);
876 4340 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
877 4340 : if (my_extra == NULL ||
878 3928 : my_extra->ncolumns < ncols)
879 : {
880 824 : fcinfo->flinfo->fn_extra =
881 412 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
882 412 : offsetof(RecordCompareData, columns) +
883 : ncols * sizeof(ColumnCompareData));
884 412 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
885 412 : my_extra->ncolumns = ncols;
886 412 : my_extra->record1_type = InvalidOid;
887 412 : my_extra->record1_typmod = 0;
888 412 : my_extra->record2_type = InvalidOid;
889 412 : my_extra->record2_typmod = 0;
890 : }
891 :
892 4340 : if (my_extra->record1_type != tupType1 ||
893 3928 : my_extra->record1_typmod != tupTypmod1 ||
894 3922 : my_extra->record2_type != tupType2 ||
895 3922 : my_extra->record2_typmod != tupTypmod2)
896 : {
897 1376 : MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
898 418 : my_extra->record1_type = tupType1;
899 418 : my_extra->record1_typmod = tupTypmod1;
900 418 : my_extra->record2_type = tupType2;
901 418 : my_extra->record2_typmod = tupTypmod2;
902 : }
903 :
904 : /* Break down the tuples into fields */
905 4340 : values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
906 4340 : nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
907 4340 : heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
908 4340 : values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
909 4340 : nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
910 4340 : heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
911 :
912 : /*
913 : * Scan corresponding columns, allowing for dropped columns in different
914 : * places in the two rows. i1 and i2 are physical column indexes, j is
915 : * the logical column index.
916 : */
917 4340 : i1 = i2 = j = 0;
918 6622 : while (i1 < ncolumns1 || i2 < ncolumns2)
919 : {
920 : Form_pg_attribute att1;
921 : Form_pg_attribute att2;
922 : TypeCacheEntry *typentry;
923 : Oid collation;
924 :
925 : /*
926 : * Skip dropped columns
927 : */
928 5834 : if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
929 : {
930 0 : i1++;
931 0 : continue;
932 : }
933 5834 : if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
934 : {
935 0 : i2++;
936 0 : continue;
937 : }
938 5834 : if (i1 >= ncolumns1 || i2 >= ncolumns2)
939 : break; /* we'll deal with mismatch below loop */
940 :
941 5828 : att1 = TupleDescAttr(tupdesc1, i1);
942 5828 : att2 = TupleDescAttr(tupdesc2, i2);
943 :
944 : /*
945 : * Have two matching columns, they must be same type
946 : */
947 5828 : if (att1->atttypid != att2->atttypid)
948 6 : ereport(ERROR,
949 : (errcode(ERRCODE_DATATYPE_MISMATCH),
950 : errmsg("cannot compare dissimilar column types %s and %s at record column %d",
951 : format_type_be(att1->atttypid),
952 : format_type_be(att2->atttypid),
953 : j + 1)));
954 :
955 : /*
956 : * If they're not same collation, we don't complain here, but the
957 : * comparison function might.
958 : */
959 5822 : collation = att1->attcollation;
960 5822 : if (collation != att2->attcollation)
961 0 : collation = InvalidOid;
962 :
963 : /*
964 : * Lookup the comparison function if not done already
965 : */
966 5822 : typentry = my_extra->columns[j].typentry;
967 5822 : if (typentry == NULL ||
968 5124 : typentry->type_id != att1->atttypid)
969 : {
970 698 : typentry = lookup_type_cache(att1->atttypid,
971 : TYPECACHE_CMP_PROC_FINFO);
972 698 : if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
973 6 : ereport(ERROR,
974 : (errcode(ERRCODE_UNDEFINED_FUNCTION),
975 : errmsg("could not identify a comparison function for type %s",
976 : format_type_be(typentry->type_id))));
977 692 : my_extra->columns[j].typentry = typentry;
978 : }
979 :
980 : /*
981 : * We consider two NULLs equal; NULL > not-NULL.
982 : */
983 5816 : if (!nulls1[i1] || !nulls2[i2])
984 : {
985 5806 : LOCAL_FCINFO(locfcinfo, 2);
986 : int32 cmpresult;
987 :
988 5806 : if (nulls1[i1])
989 : {
990 : /* arg1 is greater than arg2 */
991 12 : result = 1;
992 3534 : break;
993 : }
994 5794 : if (nulls2[i2])
995 : {
996 : /* arg1 is less than arg2 */
997 0 : result = -1;
998 0 : break;
999 : }
1000 :
1001 : /* Compare the pair of elements */
1002 5794 : InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2,
1003 : collation, NULL, NULL);
1004 5794 : locfcinfo->args[0].value = values1[i1];
1005 5794 : locfcinfo->args[0].isnull = false;
1006 5794 : locfcinfo->args[1].value = values2[i2];
1007 5794 : locfcinfo->args[1].isnull = false;
1008 5794 : cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo));
1009 :
1010 : /* We don't expect comparison support functions to return null */
1011 : Assert(!locfcinfo->isnull);
1012 :
1013 5794 : if (cmpresult < 0)
1014 : {
1015 : /* arg1 is less than arg2 */
1016 1866 : result = -1;
1017 1866 : break;
1018 : }
1019 3928 : else if (cmpresult > 0)
1020 : {
1021 : /* arg1 is greater than arg2 */
1022 1656 : result = 1;
1023 1656 : break;
1024 : }
1025 : }
1026 :
1027 : /* equal, so continue to next column */
1028 2282 : i1++, i2++, j++;
1029 : }
1030 :
1031 : /*
1032 : * If we didn't break out of the loop early, check for column count
1033 : * mismatch. (We do not report such mismatch if we found unequal column
1034 : * values; is that a feature or a bug?)
1035 : */
1036 4328 : if (result == 0)
1037 : {
1038 794 : if (i1 != ncolumns1 || i2 != ncolumns2)
1039 6 : ereport(ERROR,
1040 : (errcode(ERRCODE_DATATYPE_MISMATCH),
1041 : errmsg("cannot compare record types with different numbers of columns")));
1042 : }
1043 :
1044 4322 : pfree(values1);
1045 4322 : pfree(nulls1);
1046 4322 : pfree(values2);
1047 4322 : pfree(nulls2);
1048 4322 : ReleaseTupleDesc(tupdesc1);
1049 4322 : ReleaseTupleDesc(tupdesc2);
1050 :
1051 : /* Avoid leaking memory when handed toasted input. */
1052 4322 : PG_FREE_IF_COPY(record1, 0);
1053 4322 : PG_FREE_IF_COPY(record2, 1);
1054 :
1055 4322 : return result;
1056 : }
1057 :
1058 : /*
1059 : * record_eq :
1060 : * compares two records for equality
1061 : * result :
1062 : * returns true if the records are equal, false otherwise.
1063 : *
1064 : * Note: we do not use record_cmp here, since equality may be meaningful in
1065 : * datatypes that don't have a total ordering (and hence no btree support).
1066 : */
1067 : Datum
1068 3750 : record_eq(PG_FUNCTION_ARGS)
1069 : {
1070 3750 : HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1071 3750 : HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1072 3750 : bool result = true;
1073 : Oid tupType1;
1074 : Oid tupType2;
1075 : int32 tupTypmod1;
1076 : int32 tupTypmod2;
1077 : TupleDesc tupdesc1;
1078 : TupleDesc tupdesc2;
1079 : HeapTupleData tuple1;
1080 : HeapTupleData tuple2;
1081 : int ncolumns1;
1082 : int ncolumns2;
1083 : RecordCompareData *my_extra;
1084 : int ncols;
1085 : Datum *values1;
1086 : Datum *values2;
1087 : bool *nulls1;
1088 : bool *nulls2;
1089 : int i1;
1090 : int i2;
1091 : int j;
1092 :
1093 3750 : check_stack_depth(); /* recurses for record-type columns */
1094 :
1095 : /* Extract type info from the tuples */
1096 3750 : tupType1 = HeapTupleHeaderGetTypeId(record1);
1097 3750 : tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1098 3750 : tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1099 3750 : ncolumns1 = tupdesc1->natts;
1100 3750 : tupType2 = HeapTupleHeaderGetTypeId(record2);
1101 3750 : tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1102 3750 : tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1103 3750 : ncolumns2 = tupdesc2->natts;
1104 :
1105 : /* Build temporary HeapTuple control structures */
1106 3750 : tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1107 3750 : ItemPointerSetInvalid(&(tuple1.t_self));
1108 3750 : tuple1.t_tableOid = InvalidOid;
1109 3750 : tuple1.t_data = record1;
1110 3750 : tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1111 3750 : ItemPointerSetInvalid(&(tuple2.t_self));
1112 3750 : tuple2.t_tableOid = InvalidOid;
1113 3750 : tuple2.t_data = record2;
1114 :
1115 : /*
1116 : * We arrange to look up the needed comparison info just once per series
1117 : * of calls, assuming the record types don't change underneath us.
1118 : */
1119 3750 : ncols = Max(ncolumns1, ncolumns2);
1120 3750 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1121 3750 : if (my_extra == NULL ||
1122 3368 : my_extra->ncolumns < ncols)
1123 : {
1124 764 : fcinfo->flinfo->fn_extra =
1125 382 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1126 382 : offsetof(RecordCompareData, columns) +
1127 : ncols * sizeof(ColumnCompareData));
1128 382 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1129 382 : my_extra->ncolumns = ncols;
1130 382 : my_extra->record1_type = InvalidOid;
1131 382 : my_extra->record1_typmod = 0;
1132 382 : my_extra->record2_type = InvalidOid;
1133 382 : my_extra->record2_typmod = 0;
1134 : }
1135 :
1136 3750 : if (my_extra->record1_type != tupType1 ||
1137 3368 : my_extra->record1_typmod != tupTypmod1 ||
1138 3368 : my_extra->record2_type != tupType2 ||
1139 3368 : my_extra->record2_typmod != tupTypmod2)
1140 : {
1141 1184 : MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1142 382 : my_extra->record1_type = tupType1;
1143 382 : my_extra->record1_typmod = tupTypmod1;
1144 382 : my_extra->record2_type = tupType2;
1145 382 : my_extra->record2_typmod = tupTypmod2;
1146 : }
1147 :
1148 : /* Break down the tuples into fields */
1149 3750 : values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1150 3750 : nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1151 3750 : heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1152 3750 : values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1153 3750 : nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1154 3750 : heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1155 :
1156 : /*
1157 : * Scan corresponding columns, allowing for dropped columns in different
1158 : * places in the two rows. i1 and i2 are physical column indexes, j is
1159 : * the logical column index.
1160 : */
1161 3750 : i1 = i2 = j = 0;
1162 6248 : while (i1 < ncolumns1 || i2 < ncolumns2)
1163 : {
1164 5450 : LOCAL_FCINFO(locfcinfo, 2);
1165 : Form_pg_attribute att1;
1166 : Form_pg_attribute att2;
1167 : TypeCacheEntry *typentry;
1168 : Oid collation;
1169 : bool oprresult;
1170 :
1171 : /*
1172 : * Skip dropped columns
1173 : */
1174 5450 : if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1175 : {
1176 0 : i1++;
1177 0 : continue;
1178 : }
1179 5450 : if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1180 : {
1181 0 : i2++;
1182 0 : continue;
1183 : }
1184 5450 : if (i1 >= ncolumns1 || i2 >= ncolumns2)
1185 : break; /* we'll deal with mismatch below loop */
1186 :
1187 5444 : att1 = TupleDescAttr(tupdesc1, i1);
1188 5444 : att2 = TupleDescAttr(tupdesc2, i2);
1189 :
1190 : /*
1191 : * Have two matching columns, they must be same type
1192 : */
1193 5444 : if (att1->atttypid != att2->atttypid)
1194 12 : ereport(ERROR,
1195 : (errcode(ERRCODE_DATATYPE_MISMATCH),
1196 : errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1197 : format_type_be(att1->atttypid),
1198 : format_type_be(att2->atttypid),
1199 : j + 1)));
1200 :
1201 : /*
1202 : * If they're not same collation, we don't complain here, but the
1203 : * equality function might.
1204 : */
1205 5432 : collation = att1->attcollation;
1206 5432 : if (collation != att2->attcollation)
1207 0 : collation = InvalidOid;
1208 :
1209 : /*
1210 : * Lookup the equality function if not done already
1211 : */
1212 5432 : typentry = my_extra->columns[j].typentry;
1213 5432 : if (typentry == NULL ||
1214 4672 : typentry->type_id != att1->atttypid)
1215 : {
1216 760 : typentry = lookup_type_cache(att1->atttypid,
1217 : TYPECACHE_EQ_OPR_FINFO);
1218 760 : if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1219 6 : ereport(ERROR,
1220 : (errcode(ERRCODE_UNDEFINED_FUNCTION),
1221 : errmsg("could not identify an equality operator for type %s",
1222 : format_type_be(typentry->type_id))));
1223 754 : my_extra->columns[j].typentry = typentry;
1224 : }
1225 :
1226 : /*
1227 : * We consider two NULLs equal; NULL > not-NULL.
1228 : */
1229 5426 : if (!nulls1[i1] || !nulls2[i2])
1230 : {
1231 5176 : if (nulls1[i1] || nulls2[i2])
1232 : {
1233 6 : result = false;
1234 6 : break;
1235 : }
1236 :
1237 : /* Compare the pair of elements */
1238 5170 : InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
1239 : collation, NULL, NULL);
1240 5170 : locfcinfo->args[0].value = values1[i1];
1241 5170 : locfcinfo->args[0].isnull = false;
1242 5170 : locfcinfo->args[1].value = values2[i2];
1243 5170 : locfcinfo->args[1].isnull = false;
1244 5170 : oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
1245 5170 : if (locfcinfo->isnull || !oprresult)
1246 : {
1247 2922 : result = false;
1248 2922 : break;
1249 : }
1250 : }
1251 :
1252 : /* equal, so continue to next column */
1253 2498 : i1++, i2++, j++;
1254 : }
1255 :
1256 : /*
1257 : * If we didn't break out of the loop early, check for column count
1258 : * mismatch. (We do not report such mismatch if we found unequal column
1259 : * values; is that a feature or a bug?)
1260 : */
1261 3732 : if (result)
1262 : {
1263 804 : if (i1 != ncolumns1 || i2 != ncolumns2)
1264 6 : ereport(ERROR,
1265 : (errcode(ERRCODE_DATATYPE_MISMATCH),
1266 : errmsg("cannot compare record types with different numbers of columns")));
1267 : }
1268 :
1269 3726 : pfree(values1);
1270 3726 : pfree(nulls1);
1271 3726 : pfree(values2);
1272 3726 : pfree(nulls2);
1273 3726 : ReleaseTupleDesc(tupdesc1);
1274 3726 : ReleaseTupleDesc(tupdesc2);
1275 :
1276 : /* Avoid leaking memory when handed toasted input. */
1277 3726 : PG_FREE_IF_COPY(record1, 0);
1278 3726 : PG_FREE_IF_COPY(record2, 1);
1279 :
1280 3726 : PG_RETURN_BOOL(result);
1281 : }
1282 :
1283 : Datum
1284 54 : record_ne(PG_FUNCTION_ARGS)
1285 : {
1286 54 : PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
1287 : }
1288 :
1289 : Datum
1290 36 : record_lt(PG_FUNCTION_ARGS)
1291 : {
1292 36 : PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
1293 : }
1294 :
1295 : Datum
1296 12 : record_gt(PG_FUNCTION_ARGS)
1297 : {
1298 12 : PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
1299 : }
1300 :
1301 : Datum
1302 12 : record_le(PG_FUNCTION_ARGS)
1303 : {
1304 12 : PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
1305 : }
1306 :
1307 : Datum
1308 42 : record_ge(PG_FUNCTION_ARGS)
1309 : {
1310 42 : PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
1311 : }
1312 :
1313 : Datum
1314 4238 : btrecordcmp(PG_FUNCTION_ARGS)
1315 : {
1316 4238 : PG_RETURN_INT32(record_cmp(fcinfo));
1317 : }
1318 :
1319 :
1320 : /*
1321 : * record_image_cmp :
1322 : * Internal byte-oriented comparison function for records.
1323 : *
1324 : * Returns -1, 0 or 1
1325 : *
1326 : * Note: The normal concepts of "equality" do not apply here; different
1327 : * representation of values considered to be equal are not considered to be
1328 : * identical. As an example, for the citext type 'A' and 'a' are equal, but
1329 : * they are not identical.
1330 : */
1331 : static int
1332 852 : record_image_cmp(FunctionCallInfo fcinfo)
1333 : {
1334 852 : HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1335 852 : HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1336 852 : int result = 0;
1337 : Oid tupType1;
1338 : Oid tupType2;
1339 : int32 tupTypmod1;
1340 : int32 tupTypmod2;
1341 : TupleDesc tupdesc1;
1342 : TupleDesc tupdesc2;
1343 : HeapTupleData tuple1;
1344 : HeapTupleData tuple2;
1345 : int ncolumns1;
1346 : int ncolumns2;
1347 : RecordCompareData *my_extra;
1348 : int ncols;
1349 : Datum *values1;
1350 : Datum *values2;
1351 : bool *nulls1;
1352 : bool *nulls2;
1353 : int i1;
1354 : int i2;
1355 : int j;
1356 :
1357 : /* Extract type info from the tuples */
1358 852 : tupType1 = HeapTupleHeaderGetTypeId(record1);
1359 852 : tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1360 852 : tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1361 852 : ncolumns1 = tupdesc1->natts;
1362 852 : tupType2 = HeapTupleHeaderGetTypeId(record2);
1363 852 : tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1364 852 : tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1365 852 : ncolumns2 = tupdesc2->natts;
1366 :
1367 : /* Build temporary HeapTuple control structures */
1368 852 : tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1369 852 : ItemPointerSetInvalid(&(tuple1.t_self));
1370 852 : tuple1.t_tableOid = InvalidOid;
1371 852 : tuple1.t_data = record1;
1372 852 : tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1373 852 : ItemPointerSetInvalid(&(tuple2.t_self));
1374 852 : tuple2.t_tableOid = InvalidOid;
1375 852 : tuple2.t_data = record2;
1376 :
1377 : /*
1378 : * We arrange to look up the needed comparison info just once per series
1379 : * of calls, assuming the record types don't change underneath us.
1380 : */
1381 852 : ncols = Max(ncolumns1, ncolumns2);
1382 852 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1383 852 : if (my_extra == NULL ||
1384 606 : my_extra->ncolumns < ncols)
1385 : {
1386 492 : fcinfo->flinfo->fn_extra =
1387 246 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1388 246 : offsetof(RecordCompareData, columns) +
1389 : ncols * sizeof(ColumnCompareData));
1390 246 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1391 246 : my_extra->ncolumns = ncols;
1392 246 : my_extra->record1_type = InvalidOid;
1393 246 : my_extra->record1_typmod = 0;
1394 246 : my_extra->record2_type = InvalidOid;
1395 246 : my_extra->record2_typmod = 0;
1396 : }
1397 :
1398 852 : if (my_extra->record1_type != tupType1 ||
1399 606 : my_extra->record1_typmod != tupTypmod1 ||
1400 606 : my_extra->record2_type != tupType2 ||
1401 606 : my_extra->record2_typmod != tupTypmod2)
1402 : {
1403 876 : MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1404 246 : my_extra->record1_type = tupType1;
1405 246 : my_extra->record1_typmod = tupTypmod1;
1406 246 : my_extra->record2_type = tupType2;
1407 246 : my_extra->record2_typmod = tupTypmod2;
1408 : }
1409 :
1410 : /* Break down the tuples into fields */
1411 852 : values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1412 852 : nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1413 852 : heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1414 852 : values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1415 852 : nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1416 852 : heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1417 :
1418 : /*
1419 : * Scan corresponding columns, allowing for dropped columns in different
1420 : * places in the two rows. i1 and i2 are physical column indexes, j is
1421 : * the logical column index.
1422 : */
1423 852 : i1 = i2 = j = 0;
1424 1666 : while (i1 < ncolumns1 || i2 < ncolumns2)
1425 : {
1426 : Form_pg_attribute att1;
1427 : Form_pg_attribute att2;
1428 :
1429 : /*
1430 : * Skip dropped columns
1431 : */
1432 1498 : if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1433 : {
1434 0 : i1++;
1435 0 : continue;
1436 : }
1437 1498 : if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1438 : {
1439 0 : i2++;
1440 0 : continue;
1441 : }
1442 1498 : if (i1 >= ncolumns1 || i2 >= ncolumns2)
1443 : break; /* we'll deal with mismatch below loop */
1444 :
1445 1492 : att1 = TupleDescAttr(tupdesc1, i1);
1446 1492 : att2 = TupleDescAttr(tupdesc2, i2);
1447 :
1448 : /*
1449 : * Have two matching columns, they must be same type
1450 : */
1451 1492 : if (att1->atttypid != att2->atttypid)
1452 6 : ereport(ERROR,
1453 : (errcode(ERRCODE_DATATYPE_MISMATCH),
1454 : errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1455 : format_type_be(att1->atttypid),
1456 : format_type_be(att2->atttypid),
1457 : j + 1)));
1458 :
1459 : /*
1460 : * The same type should have the same length (or both should be
1461 : * variable).
1462 : */
1463 : Assert(att1->attlen == att2->attlen);
1464 :
1465 : /*
1466 : * We consider two NULLs equal; NULL > not-NULL.
1467 : */
1468 1486 : if (!nulls1[i1] || !nulls2[i2])
1469 : {
1470 1486 : int cmpresult = 0;
1471 :
1472 1486 : if (nulls1[i1])
1473 : {
1474 : /* arg1 is greater than arg2 */
1475 0 : result = 1;
1476 0 : break;
1477 : }
1478 1486 : if (nulls2[i2])
1479 : {
1480 : /* arg1 is less than arg2 */
1481 0 : result = -1;
1482 0 : break;
1483 : }
1484 :
1485 : /* Compare the pair of elements */
1486 1486 : if (att1->attbyval)
1487 : {
1488 978 : if (values1[i1] != values2[i2])
1489 512 : cmpresult = (values1[i1] < values2[i2]) ? -1 : 1;
1490 : }
1491 508 : else if (att1->attlen > 0)
1492 : {
1493 36 : cmpresult = memcmp(DatumGetPointer(values1[i1]),
1494 36 : DatumGetPointer(values2[i2]),
1495 36 : att1->attlen);
1496 : }
1497 472 : else if (att1->attlen == -1)
1498 : {
1499 : Size len1,
1500 : len2;
1501 : struct varlena *arg1val;
1502 : struct varlena *arg2val;
1503 :
1504 472 : len1 = toast_raw_datum_size(values1[i1]);
1505 472 : len2 = toast_raw_datum_size(values2[i2]);
1506 472 : arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
1507 472 : arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
1508 :
1509 472 : cmpresult = memcmp(VARDATA_ANY(arg1val),
1510 472 : VARDATA_ANY(arg2val),
1511 472 : Min(len1, len2) - VARHDRSZ);
1512 472 : if ((cmpresult == 0) && (len1 != len2))
1513 6 : cmpresult = (len1 < len2) ? -1 : 1;
1514 :
1515 472 : if ((Pointer) arg1val != (Pointer) values1[i1])
1516 0 : pfree(arg1val);
1517 472 : if ((Pointer) arg2val != (Pointer) values2[i2])
1518 0 : pfree(arg2val);
1519 : }
1520 : else
1521 0 : elog(ERROR, "unexpected attlen: %d", att1->attlen);
1522 :
1523 1486 : if (cmpresult < 0)
1524 : {
1525 : /* arg1 is less than arg2 */
1526 368 : result = -1;
1527 368 : break;
1528 : }
1529 1118 : else if (cmpresult > 0)
1530 : {
1531 : /* arg1 is greater than arg2 */
1532 304 : result = 1;
1533 304 : break;
1534 : }
1535 : }
1536 :
1537 : /* equal, so continue to next column */
1538 814 : i1++, i2++, j++;
1539 : }
1540 :
1541 : /*
1542 : * If we didn't break out of the loop early, check for column count
1543 : * mismatch. (We do not report such mismatch if we found unequal column
1544 : * values; is that a feature or a bug?)
1545 : */
1546 846 : if (result == 0)
1547 : {
1548 174 : if (i1 != ncolumns1 || i2 != ncolumns2)
1549 6 : ereport(ERROR,
1550 : (errcode(ERRCODE_DATATYPE_MISMATCH),
1551 : errmsg("cannot compare record types with different numbers of columns")));
1552 : }
1553 :
1554 840 : pfree(values1);
1555 840 : pfree(nulls1);
1556 840 : pfree(values2);
1557 840 : pfree(nulls2);
1558 840 : ReleaseTupleDesc(tupdesc1);
1559 840 : ReleaseTupleDesc(tupdesc2);
1560 :
1561 : /* Avoid leaking memory when handed toasted input. */
1562 840 : PG_FREE_IF_COPY(record1, 0);
1563 840 : PG_FREE_IF_COPY(record2, 1);
1564 :
1565 840 : return result;
1566 : }
1567 :
1568 : /*
1569 : * record_image_eq :
1570 : * compares two records for identical contents, based on byte images
1571 : * result :
1572 : * returns true if the records are identical, false otherwise.
1573 : *
1574 : * Note: we do not use record_image_cmp here, since we can avoid
1575 : * de-toasting for unequal lengths this way.
1576 : */
1577 : Datum
1578 260 : record_image_eq(PG_FUNCTION_ARGS)
1579 : {
1580 260 : HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1581 260 : HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1582 260 : bool result = true;
1583 : Oid tupType1;
1584 : Oid tupType2;
1585 : int32 tupTypmod1;
1586 : int32 tupTypmod2;
1587 : TupleDesc tupdesc1;
1588 : TupleDesc tupdesc2;
1589 : HeapTupleData tuple1;
1590 : HeapTupleData tuple2;
1591 : int ncolumns1;
1592 : int ncolumns2;
1593 : RecordCompareData *my_extra;
1594 : int ncols;
1595 : Datum *values1;
1596 : Datum *values2;
1597 : bool *nulls1;
1598 : bool *nulls2;
1599 : int i1;
1600 : int i2;
1601 : int j;
1602 :
1603 : /* Extract type info from the tuples */
1604 260 : tupType1 = HeapTupleHeaderGetTypeId(record1);
1605 260 : tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1606 260 : tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1607 260 : ncolumns1 = tupdesc1->natts;
1608 260 : tupType2 = HeapTupleHeaderGetTypeId(record2);
1609 260 : tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1610 260 : tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1611 260 : ncolumns2 = tupdesc2->natts;
1612 :
1613 : /* Build temporary HeapTuple control structures */
1614 260 : tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1615 260 : ItemPointerSetInvalid(&(tuple1.t_self));
1616 260 : tuple1.t_tableOid = InvalidOid;
1617 260 : tuple1.t_data = record1;
1618 260 : tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1619 260 : ItemPointerSetInvalid(&(tuple2.t_self));
1620 260 : tuple2.t_tableOid = InvalidOid;
1621 260 : tuple2.t_data = record2;
1622 :
1623 : /*
1624 : * We arrange to look up the needed comparison info just once per series
1625 : * of calls, assuming the record types don't change underneath us.
1626 : */
1627 260 : ncols = Max(ncolumns1, ncolumns2);
1628 260 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1629 260 : if (my_extra == NULL ||
1630 130 : my_extra->ncolumns < ncols)
1631 : {
1632 260 : fcinfo->flinfo->fn_extra =
1633 130 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1634 130 : offsetof(RecordCompareData, columns) +
1635 : ncols * sizeof(ColumnCompareData));
1636 130 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1637 130 : my_extra->ncolumns = ncols;
1638 130 : my_extra->record1_type = InvalidOid;
1639 130 : my_extra->record1_typmod = 0;
1640 130 : my_extra->record2_type = InvalidOid;
1641 130 : my_extra->record2_typmod = 0;
1642 : }
1643 :
1644 260 : if (my_extra->record1_type != tupType1 ||
1645 130 : my_extra->record1_typmod != tupTypmod1 ||
1646 130 : my_extra->record2_type != tupType2 ||
1647 130 : my_extra->record2_typmod != tupTypmod2)
1648 : {
1649 424 : MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1650 130 : my_extra->record1_type = tupType1;
1651 130 : my_extra->record1_typmod = tupTypmod1;
1652 130 : my_extra->record2_type = tupType2;
1653 130 : my_extra->record2_typmod = tupTypmod2;
1654 : }
1655 :
1656 : /* Break down the tuples into fields */
1657 260 : values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1658 260 : nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1659 260 : heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1660 260 : values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1661 260 : nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1662 260 : heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1663 :
1664 : /*
1665 : * Scan corresponding columns, allowing for dropped columns in different
1666 : * places in the two rows. i1 and i2 are physical column indexes, j is
1667 : * the logical column index.
1668 : */
1669 260 : i1 = i2 = j = 0;
1670 988 : while (i1 < ncolumns1 || i2 < ncolumns2)
1671 : {
1672 : Form_pg_attribute att1;
1673 : Form_pg_attribute att2;
1674 :
1675 : /*
1676 : * Skip dropped columns
1677 : */
1678 798 : if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1679 : {
1680 0 : i1++;
1681 0 : continue;
1682 : }
1683 798 : if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1684 : {
1685 0 : i2++;
1686 0 : continue;
1687 : }
1688 798 : if (i1 >= ncolumns1 || i2 >= ncolumns2)
1689 : break; /* we'll deal with mismatch below loop */
1690 :
1691 792 : att1 = TupleDescAttr(tupdesc1, i1);
1692 792 : att2 = TupleDescAttr(tupdesc2, i2);
1693 :
1694 : /*
1695 : * Have two matching columns, they must be same type
1696 : */
1697 792 : if (att1->atttypid != att2->atttypid)
1698 6 : ereport(ERROR,
1699 : (errcode(ERRCODE_DATATYPE_MISMATCH),
1700 : errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1701 : format_type_be(att1->atttypid),
1702 : format_type_be(att2->atttypid),
1703 : j + 1)));
1704 :
1705 : /*
1706 : * We consider two NULLs equal; NULL > not-NULL.
1707 : */
1708 786 : if (!nulls1[i1] || !nulls2[i2])
1709 : {
1710 774 : if (nulls1[i1] || nulls2[i2])
1711 : {
1712 0 : result = false;
1713 0 : break;
1714 : }
1715 :
1716 : /* Compare the pair of elements */
1717 774 : result = datum_image_eq(values1[i1], values2[i2], att1->attbyval, att2->attlen);
1718 774 : if (!result)
1719 58 : break;
1720 : }
1721 :
1722 : /* equal, so continue to next column */
1723 728 : i1++, i2++, j++;
1724 : }
1725 :
1726 : /*
1727 : * If we didn't break out of the loop early, check for column count
1728 : * mismatch. (We do not report such mismatch if we found unequal column
1729 : * values; is that a feature or a bug?)
1730 : */
1731 254 : if (result)
1732 : {
1733 196 : if (i1 != ncolumns1 || i2 != ncolumns2)
1734 6 : ereport(ERROR,
1735 : (errcode(ERRCODE_DATATYPE_MISMATCH),
1736 : errmsg("cannot compare record types with different numbers of columns")));
1737 : }
1738 :
1739 248 : pfree(values1);
1740 248 : pfree(nulls1);
1741 248 : pfree(values2);
1742 248 : pfree(nulls2);
1743 248 : ReleaseTupleDesc(tupdesc1);
1744 248 : ReleaseTupleDesc(tupdesc2);
1745 :
1746 : /* Avoid leaking memory when handed toasted input. */
1747 248 : PG_FREE_IF_COPY(record1, 0);
1748 248 : PG_FREE_IF_COPY(record2, 1);
1749 :
1750 248 : PG_RETURN_BOOL(result);
1751 : }
1752 :
1753 : Datum
1754 48 : record_image_ne(PG_FUNCTION_ARGS)
1755 : {
1756 48 : PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo)));
1757 : }
1758 :
1759 : Datum
1760 72 : record_image_lt(PG_FUNCTION_ARGS)
1761 : {
1762 72 : PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0);
1763 : }
1764 :
1765 : Datum
1766 18 : record_image_gt(PG_FUNCTION_ARGS)
1767 : {
1768 18 : PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0);
1769 : }
1770 :
1771 : Datum
1772 12 : record_image_le(PG_FUNCTION_ARGS)
1773 : {
1774 12 : PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0);
1775 : }
1776 :
1777 : Datum
1778 18 : record_image_ge(PG_FUNCTION_ARGS)
1779 : {
1780 18 : PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0);
1781 : }
1782 :
1783 : Datum
1784 732 : btrecordimagecmp(PG_FUNCTION_ARGS)
1785 : {
1786 732 : PG_RETURN_INT32(record_image_cmp(fcinfo));
1787 : }
1788 :
1789 :
1790 : /*
1791 : * Row type hash functions
1792 : */
1793 :
1794 : Datum
1795 900 : hash_record(PG_FUNCTION_ARGS)
1796 : {
1797 900 : HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0);
1798 900 : uint32 result = 0;
1799 : Oid tupType;
1800 : int32 tupTypmod;
1801 : TupleDesc tupdesc;
1802 : HeapTupleData tuple;
1803 : int ncolumns;
1804 : RecordCompareData *my_extra;
1805 : Datum *values;
1806 : bool *nulls;
1807 :
1808 900 : check_stack_depth(); /* recurses for record-type columns */
1809 :
1810 : /* Extract type info from tuple */
1811 900 : tupType = HeapTupleHeaderGetTypeId(record);
1812 900 : tupTypmod = HeapTupleHeaderGetTypMod(record);
1813 900 : tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
1814 900 : ncolumns = tupdesc->natts;
1815 :
1816 : /* Build temporary HeapTuple control structure */
1817 900 : tuple.t_len = HeapTupleHeaderGetDatumLength(record);
1818 900 : ItemPointerSetInvalid(&(tuple.t_self));
1819 900 : tuple.t_tableOid = InvalidOid;
1820 900 : tuple.t_data = record;
1821 :
1822 : /*
1823 : * We arrange to look up the needed hashing info just once per series of
1824 : * calls, assuming the record type doesn't change underneath us.
1825 : */
1826 900 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1827 900 : if (my_extra == NULL ||
1828 852 : my_extra->ncolumns < ncolumns)
1829 : {
1830 96 : fcinfo->flinfo->fn_extra =
1831 48 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1832 48 : offsetof(RecordCompareData, columns) +
1833 : ncolumns * sizeof(ColumnCompareData));
1834 48 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1835 48 : my_extra->ncolumns = ncolumns;
1836 48 : my_extra->record1_type = InvalidOid;
1837 48 : my_extra->record1_typmod = 0;
1838 : }
1839 :
1840 900 : if (my_extra->record1_type != tupType ||
1841 852 : my_extra->record1_typmod != tupTypmod)
1842 : {
1843 150 : MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData));
1844 48 : my_extra->record1_type = tupType;
1845 48 : my_extra->record1_typmod = tupTypmod;
1846 : }
1847 :
1848 : /* Break down the tuple into fields */
1849 900 : values = (Datum *) palloc(ncolumns * sizeof(Datum));
1850 900 : nulls = (bool *) palloc(ncolumns * sizeof(bool));
1851 900 : heap_deform_tuple(&tuple, tupdesc, values, nulls);
1852 :
1853 2730 : for (int i = 0; i < ncolumns; i++)
1854 : {
1855 : Form_pg_attribute att;
1856 : TypeCacheEntry *typentry;
1857 : uint32 element_hash;
1858 :
1859 1836 : att = TupleDescAttr(tupdesc, i);
1860 :
1861 1836 : if (att->attisdropped)
1862 0 : continue;
1863 :
1864 : /*
1865 : * Lookup the hash function if not done already
1866 : */
1867 1836 : typentry = my_extra->columns[i].typentry;
1868 1836 : if (typentry == NULL ||
1869 1740 : typentry->type_id != att->atttypid)
1870 : {
1871 96 : typentry = lookup_type_cache(att->atttypid,
1872 : TYPECACHE_HASH_PROC_FINFO);
1873 96 : if (!OidIsValid(typentry->hash_proc_finfo.fn_oid))
1874 6 : ereport(ERROR,
1875 : (errcode(ERRCODE_UNDEFINED_FUNCTION),
1876 : errmsg("could not identify a hash function for type %s",
1877 : format_type_be(typentry->type_id))));
1878 90 : my_extra->columns[i].typentry = typentry;
1879 : }
1880 :
1881 : /* Compute hash of element */
1882 1830 : if (nulls[i])
1883 : {
1884 0 : element_hash = 0;
1885 : }
1886 : else
1887 : {
1888 1830 : LOCAL_FCINFO(locfcinfo, 1);
1889 :
1890 1830 : InitFunctionCallInfoData(*locfcinfo, &typentry->hash_proc_finfo, 1,
1891 : att->attcollation, NULL, NULL);
1892 1830 : locfcinfo->args[0].value = values[i];
1893 1830 : locfcinfo->args[0].isnull = false;
1894 1830 : element_hash = DatumGetUInt32(FunctionCallInvoke(locfcinfo));
1895 :
1896 : /* We don't expect hash support functions to return null */
1897 : Assert(!locfcinfo->isnull);
1898 : }
1899 :
1900 : /* see hash_array() */
1901 1830 : result = (result << 5) - result + element_hash;
1902 : }
1903 :
1904 894 : pfree(values);
1905 894 : pfree(nulls);
1906 894 : ReleaseTupleDesc(tupdesc);
1907 :
1908 : /* Avoid leaking memory when handed toasted input. */
1909 894 : PG_FREE_IF_COPY(record, 0);
1910 :
1911 894 : PG_RETURN_UINT32(result);
1912 : }
1913 :
1914 : Datum
1915 30 : hash_record_extended(PG_FUNCTION_ARGS)
1916 : {
1917 30 : HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0);
1918 30 : uint64 seed = PG_GETARG_INT64(1);
1919 30 : uint64 result = 0;
1920 : Oid tupType;
1921 : int32 tupTypmod;
1922 : TupleDesc tupdesc;
1923 : HeapTupleData tuple;
1924 : int ncolumns;
1925 : RecordCompareData *my_extra;
1926 : Datum *values;
1927 : bool *nulls;
1928 :
1929 30 : check_stack_depth(); /* recurses for record-type columns */
1930 :
1931 : /* Extract type info from tuple */
1932 30 : tupType = HeapTupleHeaderGetTypeId(record);
1933 30 : tupTypmod = HeapTupleHeaderGetTypMod(record);
1934 30 : tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
1935 30 : ncolumns = tupdesc->natts;
1936 :
1937 : /* Build temporary HeapTuple control structure */
1938 30 : tuple.t_len = HeapTupleHeaderGetDatumLength(record);
1939 30 : ItemPointerSetInvalid(&(tuple.t_self));
1940 30 : tuple.t_tableOid = InvalidOid;
1941 30 : tuple.t_data = record;
1942 :
1943 : /*
1944 : * We arrange to look up the needed hashing info just once per series of
1945 : * calls, assuming the record type doesn't change underneath us.
1946 : */
1947 30 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1948 30 : if (my_extra == NULL ||
1949 0 : my_extra->ncolumns < ncolumns)
1950 : {
1951 60 : fcinfo->flinfo->fn_extra =
1952 30 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1953 30 : offsetof(RecordCompareData, columns) +
1954 : ncolumns * sizeof(ColumnCompareData));
1955 30 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1956 30 : my_extra->ncolumns = ncolumns;
1957 30 : my_extra->record1_type = InvalidOid;
1958 30 : my_extra->record1_typmod = 0;
1959 : }
1960 :
1961 30 : if (my_extra->record1_type != tupType ||
1962 0 : my_extra->record1_typmod != tupTypmod)
1963 : {
1964 90 : MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData));
1965 30 : my_extra->record1_type = tupType;
1966 30 : my_extra->record1_typmod = tupTypmod;
1967 : }
1968 :
1969 : /* Break down the tuple into fields */
1970 30 : values = (Datum *) palloc(ncolumns * sizeof(Datum));
1971 30 : nulls = (bool *) palloc(ncolumns * sizeof(bool));
1972 30 : heap_deform_tuple(&tuple, tupdesc, values, nulls);
1973 :
1974 78 : for (int i = 0; i < ncolumns; i++)
1975 : {
1976 : Form_pg_attribute att;
1977 : TypeCacheEntry *typentry;
1978 : uint64 element_hash;
1979 :
1980 54 : att = TupleDescAttr(tupdesc, i);
1981 :
1982 54 : if (att->attisdropped)
1983 0 : continue;
1984 :
1985 : /*
1986 : * Lookup the hash function if not done already
1987 : */
1988 54 : typentry = my_extra->columns[i].typentry;
1989 54 : if (typentry == NULL ||
1990 0 : typentry->type_id != att->atttypid)
1991 : {
1992 54 : typentry = lookup_type_cache(att->atttypid,
1993 : TYPECACHE_HASH_EXTENDED_PROC_FINFO);
1994 54 : if (!OidIsValid(typentry->hash_extended_proc_finfo.fn_oid))
1995 6 : ereport(ERROR,
1996 : (errcode(ERRCODE_UNDEFINED_FUNCTION),
1997 : errmsg("could not identify an extended hash function for type %s",
1998 : format_type_be(typentry->type_id))));
1999 48 : my_extra->columns[i].typentry = typentry;
2000 : }
2001 :
2002 : /* Compute hash of element */
2003 48 : if (nulls[i])
2004 : {
2005 0 : element_hash = 0;
2006 : }
2007 : else
2008 : {
2009 48 : LOCAL_FCINFO(locfcinfo, 2);
2010 :
2011 48 : InitFunctionCallInfoData(*locfcinfo, &typentry->hash_extended_proc_finfo, 2,
2012 : att->attcollation, NULL, NULL);
2013 48 : locfcinfo->args[0].value = values[i];
2014 48 : locfcinfo->args[0].isnull = false;
2015 48 : locfcinfo->args[1].value = Int64GetDatum(seed);
2016 48 : locfcinfo->args[0].isnull = false;
2017 48 : element_hash = DatumGetUInt64(FunctionCallInvoke(locfcinfo));
2018 :
2019 : /* We don't expect hash support functions to return null */
2020 : Assert(!locfcinfo->isnull);
2021 : }
2022 :
2023 : /* see hash_array_extended() */
2024 48 : result = (result << 5) - result + element_hash;
2025 : }
2026 :
2027 24 : pfree(values);
2028 24 : pfree(nulls);
2029 24 : ReleaseTupleDesc(tupdesc);
2030 :
2031 : /* Avoid leaking memory when handed toasted input. */
2032 24 : PG_FREE_IF_COPY(record, 0);
2033 :
2034 24 : PG_RETURN_UINT64(result);
2035 : }
|