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