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