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