Age Owner Branch data 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-2024, 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
7318 tgl@sss.pgh.pa.us 74 :CBC 851 : record_in(PG_FUNCTION_ARGS)
75 : : {
7252 76 : 851 : char *string = PG_GETARG_CSTRING(0);
77 : 851 : Oid tupType = PG_GETARG_OID(1);
3159 78 : 851 : int32 tupTypmod = PG_GETARG_INT32(2);
492 79 : 851 : Node *escontext = fcinfo->context;
80 : : HeapTupleHeader result;
81 : : TupleDesc tupdesc;
82 : : HeapTuple tuple;
83 : : RecordIOData *my_extra;
7193 84 : 851 : bool needComma = false;
85 : : int ncolumns;
86 : : int i;
87 : : char *ptr;
88 : : Datum *values;
89 : : bool *nulls;
90 : : StringInfoData buf;
91 : :
3114 noah@leadboat.com 92 : 851 : 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 : : */
3159 tgl@sss.pgh.pa.us 102 [ + + - + ]: 851 : if (tupType == RECORDOID && tupTypmod < 0)
492 tgl@sss.pgh.pa.us 103 [ # # ]:UBC 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 : : */
7252 tgl@sss.pgh.pa.us 112 :CBC 851 : tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
113 : 851 : 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 : 851 : my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
120 [ + + ]: 851 : if (my_extra == NULL ||
121 [ - + ]: 493 : my_extra->ncolumns != ncolumns)
122 : : {
123 : 716 : fcinfo->flinfo->fn_extra =
124 : 358 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
125 : : offsetof(RecordIOData, columns) +
3341 126 : 358 : ncolumns * sizeof(ColumnIOData));
7252 127 : 358 : my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
128 : 358 : my_extra->record_type = InvalidOid;
129 : 358 : my_extra->record_typmod = 0;
130 : : }
131 : :
132 [ + + ]: 851 : if (my_extra->record_type != tupType ||
133 [ - + ]: 493 : my_extra->record_typmod != tupTypmod)
134 : : {
135 [ + - + - : 8570 : MemSet(my_extra, 0,
+ - + - +
+ ]
136 : : offsetof(RecordIOData, columns) +
137 : : ncolumns * sizeof(ColumnIOData));
138 : 358 : my_extra->record_type = tupType;
139 : 358 : my_extra->record_typmod = tupTypmod;
140 : 358 : my_extra->ncolumns = ncolumns;
141 : : }
142 : :
143 : 851 : values = (Datum *) palloc(ncolumns * sizeof(Datum));
5642 144 : 851 : nulls = (bool *) palloc(ncolumns * sizeof(bool));
145 : :
146 : : /*
147 : : * Scan the string. We use "buf" to accumulate the de-quoted data for
148 : : * each column, which is then fed to the appropriate input converter.
149 : : */
7252 150 : 851 : ptr = string;
151 : : /* Allow leading whitespace */
152 [ + - + + ]: 854 : while (*ptr && isspace((unsigned char) *ptr))
153 : 3 : ptr++;
154 [ + + ]: 851 : if (*ptr++ != '(')
155 : : {
492 156 [ + - ]: 5 : errsave(escontext,
157 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
158 : : errmsg("malformed record literal: \"%s\"", string),
159 : : errdetail("Missing left parenthesis.")));
492 tgl@sss.pgh.pa.us 160 :UBC 0 : goto fail;
161 : : }
162 : :
7252 tgl@sss.pgh.pa.us 163 :CBC 846 : initStringInfo(&buf);
164 : :
165 [ + + ]: 3993 : for (i = 0; i < ncolumns; i++)
166 : : {
2429 andres@anarazel.de 167 : 3166 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
7252 tgl@sss.pgh.pa.us 168 : 3166 : ColumnIOData *column_info = &my_extra->columns[i];
2429 andres@anarazel.de 169 : 3166 : Oid column_type = att->atttypid;
170 : : char *column_data;
171 : :
172 : : /* Ignore dropped columns in datatype, but fill with nulls */
173 [ + + ]: 3166 : if (att->attisdropped)
174 : : {
7193 tgl@sss.pgh.pa.us 175 : 165 : values[i] = (Datum) 0;
5642 176 : 165 : nulls[i] = true;
7193 177 : 165 : continue;
178 : : }
179 : :
180 [ + + ]: 3001 : if (needComma)
181 : : {
182 : : /* Skip comma that separates prior field from this one */
183 [ + + ]: 2155 : if (*ptr == ',')
184 : 2152 : ptr++;
185 : : else
186 : : /* *ptr must be ')' */
187 : : {
492 188 [ + - ]: 3 : errsave(escontext,
189 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
190 : : errmsg("malformed record literal: \"%s\"", string),
191 : : errdetail("Too few columns.")));
492 tgl@sss.pgh.pa.us 192 :UBC 0 : goto fail;
193 : : }
194 : : }
195 : :
196 : : /* Check for null: completely empty input means null */
7252 tgl@sss.pgh.pa.us 197 [ + + + + ]:CBC 2998 : if (*ptr == ',' || *ptr == ')')
198 : : {
6585 199 : 235 : column_data = NULL;
5642 200 : 235 : nulls[i] = true;
201 : : }
202 : : else
203 : : {
204 : : /* Extract string for this column */
7168 bruce@momjian.us 205 : 2763 : bool inquote = false;
206 : :
6252 neilc@samurai.com 207 : 2763 : resetStringInfo(&buf);
7252 tgl@sss.pgh.pa.us 208 [ + + + + : 16057 : while (inquote || !(*ptr == ',' || *ptr == ')'))
+ + ]
209 : : {
7168 bruce@momjian.us 210 : 13297 : char ch = *ptr++;
211 : :
7252 tgl@sss.pgh.pa.us 212 [ + + ]: 13297 : if (ch == '\0')
213 : : {
492 214 [ - + ]: 3 : errsave(escontext,
215 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
216 : : errmsg("malformed record literal: \"%s\"",
217 : : string),
218 : : errdetail("Unexpected end of input.")));
219 : 3 : goto fail;
220 : : }
7252 221 [ + + ]: 13294 : if (ch == '\\')
222 : : {
223 [ - + ]: 3 : if (*ptr == '\0')
224 : : {
492 tgl@sss.pgh.pa.us 225 [ # # ]:UBC 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 : : }
7252 tgl@sss.pgh.pa.us 232 :CBC 3 : appendStringInfoChar(&buf, *ptr++);
233 : : }
3036 peter_e@gmx.net 234 [ + + ]: 13291 : else if (ch == '"')
235 : : {
7252 tgl@sss.pgh.pa.us 236 [ + + ]: 883 : if (!inquote)
237 : 426 : inquote = true;
3036 peter_e@gmx.net 238 [ + + ]: 457 : else if (*ptr == '"')
239 : : {
240 : : /* doubled quote within quote sequence */
7252 tgl@sss.pgh.pa.us 241 : 31 : appendStringInfoChar(&buf, *ptr++);
242 : : }
243 : : else
244 : 426 : inquote = false;
245 : : }
246 : : else
247 : 12408 : appendStringInfoChar(&buf, ch);
248 : : }
249 : :
6585 250 : 2760 : column_data = buf.data;
5642 251 : 2760 : nulls[i] = false;
252 : : }
253 : :
254 : : /*
255 : : * Convert the column value
256 : : */
6585 257 [ + + ]: 2995 : if (column_info->column_type != column_type)
258 : : {
259 : 904 : getTypeInputInfo(column_type,
260 : : &column_info->typiofunc,
261 : : &column_info->typioparam);
262 : 904 : fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
263 : 904 : fcinfo->flinfo->fn_mcxt);
264 : 904 : column_info->column_type = column_type;
265 : : }
266 : :
492 267 [ + + ]: 2991 : if (!InputFunctionCallSafe(&column_info->proc,
268 : : column_data,
269 : : column_info->typioparam,
270 : : att->atttypmod,
271 : : escontext,
272 : 2995 : &values[i]))
273 : 9 : goto fail;
274 : :
275 : : /*
276 : : * Prep for next column
277 : : */
7193 278 : 2982 : needComma = true;
279 : : }
280 : :
7252 281 [ + + ]: 827 : if (*ptr++ != ')')
282 : : {
492 283 [ + - ]: 3 : errsave(escontext,
284 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
285 : : errmsg("malformed record literal: \"%s\"", string),
286 : : errdetail("Too many columns.")));
492 tgl@sss.pgh.pa.us 287 :UBC 0 : goto fail;
288 : : }
289 : : /* Allow trailing whitespace */
7252 tgl@sss.pgh.pa.us 290 [ + + + + ]:CBC 833 : while (*ptr && isspace((unsigned char) *ptr))
291 : 9 : ptr++;
292 [ + + ]: 824 : if (*ptr)
293 : : {
492 294 [ + - ]: 3 : errsave(escontext,
295 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
296 : : errmsg("malformed record literal: \"%s\"", string),
297 : : errdetail("Junk after right parenthesis.")));
492 tgl@sss.pgh.pa.us 298 :UBC 0 : goto fail;
299 : : }
300 : :
5642 tgl@sss.pgh.pa.us 301 :CBC 821 : 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 : : */
6936 308 : 821 : result = (HeapTupleHeader) palloc(tuple->t_len);
309 : 821 : memcpy(result, tuple->t_data, tuple->t_len);
310 : :
311 : 821 : heap_freetuple(tuple);
7252 312 : 821 : pfree(buf.data);
313 : 821 : pfree(values);
314 : 821 : pfree(nulls);
6512 315 [ + - ]: 821 : ReleaseTupleDesc(tupdesc);
316 : :
6936 317 : 821 : PG_RETURN_HEAPTUPLEHEADER(result);
318 : :
319 : : /* exit here once we've done lookup_rowtype_tupdesc */
492 320 : 12 : fail:
321 [ + - ]: 12 : ReleaseTupleDesc(tupdesc);
322 : 12 : PG_RETURN_NULL();
323 : : }
324 : :
325 : : /*
326 : : * record_out - output routine for any composite type.
327 : : */
328 : : Datum
7318 329 : 17415 : record_out(PG_FUNCTION_ARGS)
330 : : {
7252 331 : 17415 : HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
332 : : Oid tupType;
333 : : int32 tupTypmod;
334 : : TupleDesc tupdesc;
335 : : HeapTupleData tuple;
336 : : RecordIOData *my_extra;
7193 337 : 17415 : bool needComma = false;
338 : : int ncolumns;
339 : : int i;
340 : : Datum *values;
341 : : bool *nulls;
342 : : StringInfoData buf;
343 : :
3114 noah@leadboat.com 344 : 17415 : check_stack_depth(); /* recurses for record-type columns */
345 : :
346 : : /* Extract type info from the tuple itself */
6924 tgl@sss.pgh.pa.us 347 : 17415 : tupType = HeapTupleHeaderGetTypeId(rec);
348 : 17415 : tupTypmod = HeapTupleHeaderGetTypMod(rec);
7252 349 : 17415 : tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
350 : 17415 : ncolumns = tupdesc->natts;
351 : :
352 : : /* Build a temporary HeapTuple control structure */
353 : 17415 : tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
354 : 17415 : ItemPointerSetInvalid(&(tuple.t_self));
355 : 17415 : tuple.t_tableOid = InvalidOid;
356 : 17415 : 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 : 17415 : my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
363 [ + + ]: 17415 : if (my_extra == NULL ||
364 [ + + ]: 15058 : my_extra->ncolumns != ncolumns)
365 : : {
366 : 4738 : fcinfo->flinfo->fn_extra =
367 : 2369 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
368 : : offsetof(RecordIOData, columns) +
3341 369 : 2369 : ncolumns * sizeof(ColumnIOData));
7252 370 : 2369 : my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
371 : 2369 : my_extra->record_type = InvalidOid;
372 : 2369 : my_extra->record_typmod = 0;
373 : : }
374 : :
375 [ + + ]: 17415 : if (my_extra->record_type != tupType ||
376 [ + + ]: 15046 : my_extra->record_typmod != tupTypmod)
377 : : {
378 [ + - + - : 53087 : MemSet(my_extra, 0,
+ - + - +
+ ]
379 : : offsetof(RecordIOData, columns) +
380 : : ncolumns * sizeof(ColumnIOData));
381 : 2389 : my_extra->record_type = tupType;
382 : 2389 : my_extra->record_typmod = tupTypmod;
383 : 2389 : my_extra->ncolumns = ncolumns;
384 : : }
385 : :
386 : 17415 : values = (Datum *) palloc(ncolumns * sizeof(Datum));
5642 387 : 17415 : nulls = (bool *) palloc(ncolumns * sizeof(bool));
388 : :
389 : : /* Break down the tuple into fields */
390 : 17415 : heap_deform_tuple(&tuple, tupdesc, values, nulls);
391 : :
392 : : /* And build the result string */
7252 393 : 17415 : initStringInfo(&buf);
394 : :
395 : 17415 : appendStringInfoChar(&buf, '(');
396 : :
397 [ + + ]: 105729 : for (i = 0; i < ncolumns; i++)
398 : : {
2429 andres@anarazel.de 399 : 88314 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
7252 tgl@sss.pgh.pa.us 400 : 88314 : ColumnIOData *column_info = &my_extra->columns[i];
2429 andres@anarazel.de 401 : 88314 : Oid column_type = att->atttypid;
402 : : Datum attr;
403 : : char *value;
404 : : char *tmp;
405 : : bool nq;
406 : :
407 : : /* Ignore dropped columns in datatype */
408 [ + + ]: 88314 : if (att->attisdropped)
7193 tgl@sss.pgh.pa.us 409 : 246 : continue;
410 : :
411 [ + + ]: 88068 : if (needComma)
7252 412 : 70656 : appendStringInfoChar(&buf, ',');
7193 413 : 88068 : needComma = true;
414 : :
5642 415 [ + + ]: 88068 : if (nulls[i])
416 : : {
417 : : /* emit nothing... */
7252 418 : 2049 : continue;
419 : : }
420 : :
421 : : /*
422 : : * Convert the column value to text
423 : : */
424 [ + + ]: 86019 : if (column_info->column_type != column_type)
425 : : {
426 : 5421 : getTypeOutputInfo(column_type,
427 : : &column_info->typiofunc,
428 : : &column_info->typisvarlena);
429 : 5421 : fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
430 : 5421 : fcinfo->flinfo->fn_mcxt);
431 : 5421 : column_info->column_type = column_type;
432 : : }
433 : :
3815 434 : 86019 : attr = values[i];
4170 435 : 86019 : value = OutputFunctionCall(&column_info->proc, attr);
436 : :
437 : : /* Detect whether we need double quotes for this value */
7252 438 : 86019 : nq = (value[0] == '\0'); /* force quotes for empty string */
439 [ + + ]: 54128906 : for (tmp = value; *tmp; tmp++)
440 : : {
441 : 54068744 : char ch = *tmp;
442 : :
443 [ + + + + : 54068744 : if (ch == '"' || ch == '\\' ||
+ + ]
444 [ + - + + ]: 54068297 : ch == '(' || ch == ')' || ch == ',' ||
445 [ + + ]: 54067818 : isspace((unsigned char) ch))
446 : : {
447 : 25857 : nq = true;
448 : 25857 : break;
449 : : }
450 : : }
451 : :
452 : : /* And emit the string */
453 [ + + ]: 86019 : if (nq)
4170 454 [ - + ]: 25859 : appendStringInfoCharMacro(&buf, '"');
7252 455 [ + + ]: 54390046 : for (tmp = value; *tmp; tmp++)
456 : : {
457 : 54304027 : char ch = *tmp;
458 : :
459 [ + + + + ]: 54304027 : if (ch == '"' || ch == '\\')
4170 460 [ - + ]: 498 : appendStringInfoCharMacro(&buf, ch);
461 [ + + ]: 54304027 : appendStringInfoCharMacro(&buf, ch);
462 : : }
7252 463 [ + + ]: 86019 : if (nq)
4170 464 [ - + ]: 25859 : appendStringInfoCharMacro(&buf, '"');
465 : : }
466 : :
7252 467 : 17415 : appendStringInfoChar(&buf, ')');
468 : :
469 : 17415 : pfree(values);
470 : 17415 : pfree(nulls);
6512 471 [ + + ]: 17415 : ReleaseTupleDesc(tupdesc);
472 : :
7252 473 : 17415 : PG_RETURN_CSTRING(buf.data);
474 : : }
475 : :
476 : : /*
477 : : * record_recv - binary input routine for any composite type.
478 : : */
479 : : Datum
7318 tgl@sss.pgh.pa.us 480 :UBC 0 : record_recv(PG_FUNCTION_ARGS)
481 : : {
7252 482 : 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
483 : 0 : Oid tupType = PG_GETARG_OID(1);
3159 484 : 0 : int32 tupTypmod = PG_GETARG_INT32(2);
485 : : HeapTupleHeader result;
486 : : TupleDesc tupdesc;
487 : : HeapTuple tuple;
488 : : RecordIOData *my_extra;
489 : : int ncolumns;
490 : : int usercols;
491 : : int validcols;
492 : : int i;
493 : : Datum *values;
494 : : bool *nulls;
495 : :
3114 noah@leadboat.com 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 : : */
3159 tgl@sss.pgh.pa.us 506 [ # # # # ]: 0 : if (tupType == RECORDOID && tupTypmod < 0)
7252 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 : : offsetof(RecordIOData, columns) +
3341 525 : 0 : ncolumns * sizeof(ColumnIOData));
7252 526 : 0 : my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
527 : 0 : my_extra->record_type = InvalidOid;
528 : 0 : my_extra->record_typmod = 0;
529 : : }
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 : : }
541 : :
542 : 0 : values = (Datum *) palloc(ncolumns * sizeof(Datum));
5642 543 : 0 : nulls = (bool *) palloc(ncolumns * sizeof(bool));
544 : :
545 : : /* Fetch number of columns user thinks it has */
7193 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 : : {
2429 andres@anarazel.de 552 [ # # ]: 0 : if (!TupleDescAttr(tupdesc, i)->attisdropped)
7193 tgl@sss.pgh.pa.us 553 : 0 : validcols++;
554 : : }
555 [ # # ]: 0 : if (usercols != validcols)
7252 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 : : {
2429 andres@anarazel.de 564 : 0 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
7252 tgl@sss.pgh.pa.us 565 : 0 : ColumnIOData *column_info = &my_extra->columns[i];
2429 andres@anarazel.de 566 : 0 : Oid column_type = att->atttypid;
567 : : Oid coltypoid;
568 : : int itemlen;
569 : : StringInfoData item_buf;
570 : : StringInfo bufptr;
571 : :
572 : : /* Ignore dropped columns in datatype, but fill with nulls */
573 [ # # ]: 0 : if (att->attisdropped)
574 : : {
7193 tgl@sss.pgh.pa.us 575 : 0 : values[i] = (Datum) 0;
5642 576 : 0 : nulls[i] = true;
7193 577 : 0 : continue;
578 : : }
579 : :
580 : : /* Check column type recorded in the data */
7252 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 : : */
1363 594 [ # # # # ]: 0 : if (coltypoid != column_type &&
595 [ # # ]: 0 : coltypoid < FirstGenbkiObjectId &&
596 : : column_type < FirstGenbkiObjectId)
7193 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 */
7252 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 */
6585 618 : 0 : bufptr = NULL;
5642 619 : 0 : nulls[i] = true;
620 : : }
621 : : else
622 : : {
623 : : 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 : : */
171 drowley@postgresql.o 630 :UNC 0 : strbuff = &buf->data[buf->cursor];
7252 tgl@sss.pgh.pa.us 631 :UBC 0 : buf->cursor += itemlen;
171 drowley@postgresql.o 632 :UNC 0 : initReadOnlyStringInfo(&item_buf, strbuff, itemlen);
633 : :
6585 tgl@sss.pgh.pa.us 634 :UBC 0 : bufptr = &item_buf;
5642 635 : 0 : nulls[i] = false;
636 : : }
637 : :
638 : : /* Now call the column's receiveproc */
6585 639 [ # # ]: 0 : if (column_info->column_type != column_type)
640 : : {
641 : 0 : getTypeBinaryInputInfo(column_type,
642 : : &column_info->typiofunc,
643 : : &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 : : }
648 : :
649 : 0 : values[i] = ReceiveFunctionCall(&column_info->proc,
650 : : bufptr,
651 : : column_info->typioparam,
652 : : att->atttypmod);
653 : :
654 [ # # ]: 0 : if (bufptr)
655 : : {
656 : : /* Trouble if it didn't eat the whole buffer */
7252 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 : : }
663 : : }
664 : :
5642 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 : : */
6936 672 : 0 : result = (HeapTupleHeader) palloc(tuple->t_len);
673 : 0 : memcpy(result, tuple->t_data, tuple->t_len);
674 : :
675 : 0 : heap_freetuple(tuple);
7252 676 : 0 : pfree(values);
677 : 0 : pfree(nulls);
6512 678 [ # # ]: 0 : ReleaseTupleDesc(tupdesc);
679 : :
6936 680 : 0 : PG_RETURN_HEAPTUPLEHEADER(result);
681 : : }
682 : :
683 : : /*
684 : : * record_send - binary output routine for any composite type.
685 : : */
686 : : Datum
7318 687 : 0 : record_send(PG_FUNCTION_ARGS)
688 : : {
7252 689 : 0 : HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
690 : : Oid tupType;
691 : : int32 tupTypmod;
692 : : TupleDesc tupdesc;
693 : : HeapTupleData tuple;
694 : : RecordIOData *my_extra;
695 : : int ncolumns;
696 : : int validcols;
697 : : int i;
698 : : Datum *values;
699 : : bool *nulls;
700 : : StringInfoData buf;
701 : :
3114 noah@leadboat.com 702 : 0 : check_stack_depth(); /* recurses for record-type columns */
703 : :
704 : : /* Extract type info from the tuple itself */
6924 tgl@sss.pgh.pa.us 705 : 0 : tupType = HeapTupleHeaderGetTypeId(rec);
706 : 0 : tupTypmod = HeapTupleHeaderGetTypMod(rec);
7252 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 : : offsetof(RecordIOData, columns) +
3341 727 : 0 : ncolumns * sizeof(ColumnIOData));
7252 728 : 0 : my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
729 : 0 : my_extra->record_type = InvalidOid;
730 : 0 : my_extra->record_typmod = 0;
731 : : }
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 : : }
743 : :
744 : 0 : values = (Datum *) palloc(ncolumns * sizeof(Datum));
5642 745 : 0 : nulls = (bool *) palloc(ncolumns * sizeof(bool));
746 : :
747 : : /* Break down the tuple into fields */
748 : 0 : heap_deform_tuple(&tuple, tupdesc, values, nulls);
749 : :
750 : : /* And build the result string */
7252 751 : 0 : pq_begintypsend(&buf);
752 : :
753 : : /* Need to scan to count nondeleted columns */
7193 754 : 0 : validcols = 0;
755 [ # # ]: 0 : for (i = 0; i < ncolumns; i++)
756 : : {
2429 andres@anarazel.de 757 [ # # ]: 0 : if (!TupleDescAttr(tupdesc, i)->attisdropped)
7193 tgl@sss.pgh.pa.us 758 : 0 : validcols++;
759 : : }
2377 andres@anarazel.de 760 : 0 : pq_sendint32(&buf, validcols);
761 : :
7252 tgl@sss.pgh.pa.us 762 [ # # ]: 0 : for (i = 0; i < ncolumns; i++)
763 : : {
2429 andres@anarazel.de 764 : 0 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
7252 tgl@sss.pgh.pa.us 765 : 0 : ColumnIOData *column_info = &my_extra->columns[i];
2429 andres@anarazel.de 766 : 0 : Oid column_type = att->atttypid;
767 : : Datum attr;
768 : : bytea *outputbytes;
769 : :
770 : : /* Ignore dropped columns in datatype */
771 [ # # ]: 0 : if (att->attisdropped)
7193 tgl@sss.pgh.pa.us 772 : 0 : continue;
773 : :
2377 andres@anarazel.de 774 : 0 : pq_sendint32(&buf, column_type);
775 : :
5642 tgl@sss.pgh.pa.us 776 [ # # ]: 0 : if (nulls[i])
777 : : {
778 : : /* emit -1 data length to signify a NULL */
2377 andres@anarazel.de 779 : 0 : pq_sendint32(&buf, -1);
7252 tgl@sss.pgh.pa.us 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 : : &column_info->typiofunc,
790 : : &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 : : }
795 : :
3815 796 : 0 : attr = values[i];
4170 797 : 0 : outputbytes = SendFunctionCall(&column_info->proc, attr);
2377 andres@anarazel.de 798 : 0 : pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ);
7252 tgl@sss.pgh.pa.us 799 : 0 : pq_sendbytes(&buf, VARDATA(outputbytes),
800 : 0 : VARSIZE(outputbytes) - VARHDRSZ);
801 : : }
802 : :
803 : 0 : pfree(values);
804 : 0 : pfree(nulls);
6512 805 [ # # ]: 0 : ReleaseTupleDesc(tupdesc);
806 : :
7252 807 : 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
808 : : }
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
5662 tgl@sss.pgh.pa.us 823 :CBC 2254 : record_cmp(FunctionCallInfo fcinfo)
824 : : {
825 : 2254 : HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
826 : 2254 : HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
827 : 2254 : int result = 0;
828 : : Oid tupType1;
829 : : Oid tupType2;
830 : : int32 tupTypmod1;
831 : : int32 tupTypmod2;
832 : : TupleDesc tupdesc1;
833 : : TupleDesc tupdesc2;
834 : : HeapTupleData tuple1;
835 : : HeapTupleData tuple2;
836 : : int ncolumns1;
837 : : int ncolumns2;
838 : : RecordCompareData *my_extra;
839 : : int ncols;
840 : : Datum *values1;
841 : : Datum *values2;
842 : : bool *nulls1;
843 : : bool *nulls2;
844 : : int i1;
845 : : int i2;
846 : : int j;
847 : :
3114 noah@leadboat.com 848 : 2254 : check_stack_depth(); /* recurses for record-type columns */
849 : :
850 : : /* Extract type info from the tuples */
5662 tgl@sss.pgh.pa.us 851 : 2254 : tupType1 = HeapTupleHeaderGetTypeId(record1);
852 : 2254 : tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
853 : 2254 : tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
854 : 2254 : ncolumns1 = tupdesc1->natts;
855 : 2254 : tupType2 = HeapTupleHeaderGetTypeId(record2);
856 : 2254 : tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
857 : 2254 : tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
858 : 2254 : ncolumns2 = tupdesc2->natts;
859 : :
860 : : /* Build temporary HeapTuple control structures */
861 : 2254 : tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
862 : 2254 : ItemPointerSetInvalid(&(tuple1.t_self));
863 : 2254 : tuple1.t_tableOid = InvalidOid;
864 : 2254 : tuple1.t_data = record1;
865 : 2254 : tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
866 : 2254 : ItemPointerSetInvalid(&(tuple2.t_self));
867 : 2254 : tuple2.t_tableOid = InvalidOid;
868 : 2254 : 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 : 2254 : ncols = Max(ncolumns1, ncolumns2);
875 : 2254 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
876 [ + + ]: 2254 : if (my_extra == NULL ||
877 [ - + ]: 2048 : my_extra->ncolumns < ncols)
878 : : {
879 : 412 : fcinfo->flinfo->fn_extra =
880 : 206 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
3341 881 : 206 : offsetof(RecordCompareData, columns) +
882 : : ncols * sizeof(ColumnCompareData));
5662 883 : 206 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
884 : 206 : my_extra->ncolumns = ncols;
885 : 206 : my_extra->record1_type = InvalidOid;
886 : 206 : my_extra->record1_typmod = 0;
887 : 206 : my_extra->record2_type = InvalidOid;
888 : 206 : my_extra->record2_typmod = 0;
889 : : }
890 : :
891 [ + + ]: 2254 : if (my_extra->record1_type != tupType1 ||
892 [ + + ]: 2048 : my_extra->record1_typmod != tupTypmod1 ||
893 [ + - ]: 2045 : my_extra->record2_type != tupType2 ||
894 [ - + ]: 2045 : my_extra->record2_typmod != tupTypmod2)
895 : : {
896 [ + - + - : 688 : MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
+ - + - +
+ ]
897 : 209 : my_extra->record1_type = tupType1;
898 : 209 : my_extra->record1_typmod = tupTypmod1;
899 : 209 : my_extra->record2_type = tupType2;
900 : 209 : my_extra->record2_typmod = tupTypmod2;
901 : : }
902 : :
903 : : /* Break down the tuples into fields */
904 : 2254 : values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
905 : 2254 : nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
906 : 2254 : heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
907 : 2254 : values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
908 : 2254 : nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
909 : 2254 : 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 : 2254 : i1 = i2 = j = 0;
917 [ + + - + ]: 3407 : while (i1 < ncolumns1 || i2 < ncolumns2)
918 : : {
919 : : Form_pg_attribute att1;
920 : : Form_pg_attribute att2;
921 : : TypeCacheEntry *typentry;
922 : : Oid collation;
923 : :
924 : : /*
925 : : * Skip dropped columns
926 : : */
2429 andres@anarazel.de 927 [ + - - + ]: 3010 : if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
928 : : {
5662 tgl@sss.pgh.pa.us 929 :UBC 0 : i1++;
930 : 0 : continue;
931 : : }
2429 andres@anarazel.de 932 [ + + - + ]:CBC 3010 : if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
933 : : {
5662 tgl@sss.pgh.pa.us 934 :UBC 0 : i2++;
935 : 0 : continue;
936 : : }
5662 tgl@sss.pgh.pa.us 937 [ + - + + ]:CBC 3010 : if (i1 >= ncolumns1 || i2 >= ncolumns2)
938 : : break; /* we'll deal with mismatch below loop */
939 : :
2429 andres@anarazel.de 940 : 3007 : att1 = TupleDescAttr(tupdesc1, i1);
941 : 3007 : att2 = TupleDescAttr(tupdesc2, i2);
942 : :
943 : : /*
944 : : * Have two matching columns, they must be same type
945 : : */
946 [ + + ]: 3007 : if (att1->atttypid != att2->atttypid)
5662 tgl@sss.pgh.pa.us 947 [ + - ]: 3 : 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 : : */
2429 andres@anarazel.de 958 : 3004 : collation = att1->attcollation;
959 [ - + ]: 3004 : if (collation != att2->attcollation)
4751 tgl@sss.pgh.pa.us 960 :UBC 0 : collation = InvalidOid;
961 : :
962 : : /*
963 : : * Lookup the comparison function if not done already
964 : : */
5662 tgl@sss.pgh.pa.us 965 :CBC 3004 : typentry = my_extra->columns[j].typentry;
966 [ + + ]: 3004 : if (typentry == NULL ||
2429 andres@anarazel.de 967 [ - + ]: 2655 : typentry->type_id != att1->atttypid)
968 : : {
969 : 349 : typentry = lookup_type_cache(att1->atttypid,
970 : : TYPECACHE_CMP_PROC_FINFO);
5662 tgl@sss.pgh.pa.us 971 [ + + ]: 349 : if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
972 [ + - ]: 3 : 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 : 346 : my_extra->columns[j].typentry = typentry;
977 : : }
978 : :
979 : : /*
980 : : * We consider two NULLs equal; NULL > not-NULL.
981 : : */
982 [ + + + + ]: 3001 : if (!nulls1[i1] || !nulls2[i2])
983 : : {
1905 andres@anarazel.de 984 : 2996 : LOCAL_FCINFO(locfcinfo, 2);
985 : : int32 cmpresult;
986 : :
5662 tgl@sss.pgh.pa.us 987 [ + + ]: 2996 : if (nulls1[i1])
988 : : {
989 : : /* arg1 is greater than arg2 */
990 : 12 : result = 1;
991 : 1848 : break;
992 : : }
993 [ - + ]: 2984 : if (nulls2[i2])
994 : : {
995 : : /* arg1 is less than arg2 */
5662 tgl@sss.pgh.pa.us 996 :UBC 0 : result = -1;
997 : 0 : break;
998 : : }
999 : :
1000 : : /* Compare the pair of elements */
1905 andres@anarazel.de 1001 :CBC 2984 : InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2,
1002 : : collation, NULL, NULL);
1003 : 2984 : locfcinfo->args[0].value = values1[i1];
1004 : 2984 : locfcinfo->args[0].isnull = false;
1005 : 2984 : locfcinfo->args[1].value = values2[i2];
1006 : 2984 : locfcinfo->args[1].isnull = false;
1007 : 2984 : cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo));
1008 : :
1009 : : /* We don't expect comparison support functions to return null */
1454 tgl@sss.pgh.pa.us 1010 [ - + ]: 2984 : Assert(!locfcinfo->isnull);
1011 : :
5662 1012 [ + + ]: 2984 : if (cmpresult < 0)
1013 : : {
1014 : : /* arg1 is less than arg2 */
1015 : 927 : result = -1;
1016 : 927 : break;
1017 : : }
1018 [ + + ]: 2057 : else if (cmpresult > 0)
1019 : : {
1020 : : /* arg1 is greater than arg2 */
1021 : 909 : result = 1;
1022 : 909 : break;
1023 : : }
1024 : : }
1025 : :
1026 : : /* equal, so continue to next column */
1027 : 1153 : i1++, i2++, j++;
1028 : : }
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 [ + + ]: 2248 : if (result == 0)
1036 : : {
1037 [ + + - + ]: 400 : if (i1 != ncolumns1 || i2 != ncolumns2)
1038 [ + - ]: 3 : ereport(ERROR,
1039 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
1040 : : errmsg("cannot compare record types with different numbers of columns")));
1041 : : }
1042 : :
1043 : 2245 : pfree(values1);
1044 : 2245 : pfree(nulls1);
1045 : 2245 : pfree(values2);
1046 : 2245 : pfree(nulls2);
1047 [ + - ]: 2245 : ReleaseTupleDesc(tupdesc1);
1048 [ + - ]: 2245 : ReleaseTupleDesc(tupdesc2);
1049 : :
1050 : : /* Avoid leaking memory when handed toasted input. */
1051 [ + + ]: 2245 : PG_FREE_IF_COPY(record1, 0);
1052 [ + + ]: 2245 : PG_FREE_IF_COPY(record2, 1);
1053 : :
1054 : 2245 : return result;
1055 : : }
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 : 1859 : record_eq(PG_FUNCTION_ARGS)
1068 : : {
1069 : 1859 : HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1070 : 1859 : HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1071 : 1859 : bool result = true;
1072 : : Oid tupType1;
1073 : : Oid tupType2;
1074 : : int32 tupTypmod1;
1075 : : int32 tupTypmod2;
1076 : : TupleDesc tupdesc1;
1077 : : TupleDesc tupdesc2;
1078 : : HeapTupleData tuple1;
1079 : : HeapTupleData tuple2;
1080 : : int ncolumns1;
1081 : : int ncolumns2;
1082 : : RecordCompareData *my_extra;
1083 : : int ncols;
1084 : : Datum *values1;
1085 : : Datum *values2;
1086 : : bool *nulls1;
1087 : : bool *nulls2;
1088 : : int i1;
1089 : : int i2;
1090 : : int j;
1091 : :
3114 noah@leadboat.com 1092 : 1859 : check_stack_depth(); /* recurses for record-type columns */
1093 : :
1094 : : /* Extract type info from the tuples */
5662 tgl@sss.pgh.pa.us 1095 : 1859 : tupType1 = HeapTupleHeaderGetTypeId(record1);
1096 : 1859 : tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1097 : 1859 : tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1098 : 1859 : ncolumns1 = tupdesc1->natts;
1099 : 1859 : tupType2 = HeapTupleHeaderGetTypeId(record2);
1100 : 1859 : tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1101 : 1859 : tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1102 : 1859 : ncolumns2 = tupdesc2->natts;
1103 : :
1104 : : /* Build temporary HeapTuple control structures */
1105 : 1859 : tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1106 : 1859 : ItemPointerSetInvalid(&(tuple1.t_self));
1107 : 1859 : tuple1.t_tableOid = InvalidOid;
1108 : 1859 : tuple1.t_data = record1;
1109 : 1859 : tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1110 : 1859 : ItemPointerSetInvalid(&(tuple2.t_self));
1111 : 1859 : tuple2.t_tableOid = InvalidOid;
1112 : 1859 : 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 : 1859 : ncols = Max(ncolumns1, ncolumns2);
1119 : 1859 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1120 [ + + ]: 1859 : if (my_extra == NULL ||
1121 [ - + ]: 1674 : my_extra->ncolumns < ncols)
1122 : : {
1123 : 370 : fcinfo->flinfo->fn_extra =
1124 : 185 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
3341 1125 : 185 : offsetof(RecordCompareData, columns) +
1126 : : ncols * sizeof(ColumnCompareData));
5662 1127 : 185 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1128 : 185 : my_extra->ncolumns = ncols;
1129 : 185 : my_extra->record1_type = InvalidOid;
1130 : 185 : my_extra->record1_typmod = 0;
1131 : 185 : my_extra->record2_type = InvalidOid;
1132 : 185 : my_extra->record2_typmod = 0;
1133 : : }
1134 : :
1135 [ + + ]: 1859 : if (my_extra->record1_type != tupType1 ||
1136 [ + - ]: 1674 : my_extra->record1_typmod != tupTypmod1 ||
1137 [ + - ]: 1674 : my_extra->record2_type != tupType2 ||
1138 [ - + ]: 1674 : my_extra->record2_typmod != tupTypmod2)
1139 : : {
1140 [ + - + - : 574 : MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
+ - + - +
+ ]
1141 : 185 : my_extra->record1_type = tupType1;
1142 : 185 : my_extra->record1_typmod = tupTypmod1;
1143 : 185 : my_extra->record2_type = tupType2;
1144 : 185 : my_extra->record2_typmod = tupTypmod2;
1145 : : }
1146 : :
1147 : : /* Break down the tuples into fields */
1148 : 1859 : values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1149 : 1859 : nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1150 : 1859 : heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1151 : 1859 : values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1152 : 1859 : nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1153 : 1859 : 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 : 1859 : i1 = i2 = j = 0;
1161 [ + + - + ]: 3048 : while (i1 < ncolumns1 || i2 < ncolumns2)
1162 : : {
1905 andres@anarazel.de 1163 : 2679 : LOCAL_FCINFO(locfcinfo, 2);
1164 : : Form_pg_attribute att1;
1165 : : Form_pg_attribute att2;
1166 : : TypeCacheEntry *typentry;
1167 : : Oid collation;
1168 : : bool oprresult;
1169 : :
1170 : : /*
1171 : : * Skip dropped columns
1172 : : */
2429 1173 [ + - - + ]: 2679 : if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1174 : : {
5662 tgl@sss.pgh.pa.us 1175 :UBC 0 : i1++;
1176 : 0 : continue;
1177 : : }
2429 andres@anarazel.de 1178 [ + + - + ]:CBC 2679 : if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1179 : : {
5662 tgl@sss.pgh.pa.us 1180 :UBC 0 : i2++;
1181 : 0 : continue;
1182 : : }
5662 tgl@sss.pgh.pa.us 1183 [ + - + + ]:CBC 2679 : if (i1 >= ncolumns1 || i2 >= ncolumns2)
1184 : : break; /* we'll deal with mismatch below loop */
1185 : :
2429 andres@anarazel.de 1186 : 2676 : att1 = TupleDescAttr(tupdesc1, i1);
1187 : 2676 : att2 = TupleDescAttr(tupdesc2, i2);
1188 : :
1189 : : /*
1190 : : * Have two matching columns, they must be same type
1191 : : */
1192 [ + + ]: 2676 : if (att1->atttypid != att2->atttypid)
5662 tgl@sss.pgh.pa.us 1193 [ + - ]: 6 : 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 : : */
2429 andres@anarazel.de 1204 : 2670 : collation = att1->attcollation;
1205 [ - + ]: 2670 : if (collation != att2->attcollation)
4751 tgl@sss.pgh.pa.us 1206 :UBC 0 : collation = InvalidOid;
1207 : :
1208 : : /*
1209 : : * Lookup the equality function if not done already
1210 : : */
5662 tgl@sss.pgh.pa.us 1211 :CBC 2670 : typentry = my_extra->columns[j].typentry;
1212 [ + + ]: 2670 : if (typentry == NULL ||
2429 andres@anarazel.de 1213 [ - + ]: 2302 : typentry->type_id != att1->atttypid)
1214 : : {
1215 : 368 : typentry = lookup_type_cache(att1->atttypid,
1216 : : TYPECACHE_EQ_OPR_FINFO);
5662 tgl@sss.pgh.pa.us 1217 [ + + ]: 368 : if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1218 [ + - ]: 3 : 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 : 365 : my_extra->columns[j].typentry = typentry;
1223 : : }
1224 : :
1225 : : /*
1226 : : * We consider two NULLs equal; NULL > not-NULL.
1227 : : */
1228 [ + + + + ]: 2667 : if (!nulls1[i1] || !nulls2[i2])
1229 : : {
1230 [ + + - + ]: 2542 : if (nulls1[i1] || nulls2[i2])
1231 : : {
1232 : 3 : result = false;
1233 : 3 : break;
1234 : : }
1235 : :
1236 : : /* Compare the pair of elements */
1905 andres@anarazel.de 1237 : 2539 : InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
1238 : : collation, NULL, NULL);
1239 : 2539 : locfcinfo->args[0].value = values1[i1];
1240 : 2539 : locfcinfo->args[0].isnull = false;
1241 : 2539 : locfcinfo->args[1].value = values2[i2];
1242 : 2539 : locfcinfo->args[1].isnull = false;
1243 : 2539 : oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
1454 tgl@sss.pgh.pa.us 1244 [ + - + + ]: 2539 : if (locfcinfo->isnull || !oprresult)
1245 : : {
5662 1246 : 1475 : result = false;
1247 : 1475 : break;
1248 : : }
1249 : : }
1250 : :
1251 : : /* equal, so continue to next column */
1252 : 1189 : i1++, i2++, j++;
1253 : : }
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 [ + + ]: 1850 : if (result)
1261 : : {
1262 [ + + - + ]: 372 : if (i1 != ncolumns1 || i2 != ncolumns2)
1263 [ + - ]: 3 : ereport(ERROR,
1264 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
1265 : : errmsg("cannot compare record types with different numbers of columns")));
1266 : : }
1267 : :
1268 : 1847 : pfree(values1);
1269 : 1847 : pfree(nulls1);
1270 : 1847 : pfree(values2);
1271 : 1847 : pfree(nulls2);
1272 [ + - ]: 1847 : ReleaseTupleDesc(tupdesc1);
1273 [ + - ]: 1847 : ReleaseTupleDesc(tupdesc2);
1274 : :
1275 : : /* Avoid leaking memory when handed toasted input. */
1276 [ + + ]: 1847 : PG_FREE_IF_COPY(record1, 0);
1277 [ + + ]: 1847 : PG_FREE_IF_COPY(record2, 1);
1278 : :
1279 : 1847 : PG_RETURN_BOOL(result);
1280 : : }
1281 : :
1282 : : Datum
1283 : 27 : record_ne(PG_FUNCTION_ARGS)
1284 : : {
1285 : 27 : PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
1286 : : }
1287 : :
1288 : : Datum
1289 : 18 : record_lt(PG_FUNCTION_ARGS)
1290 : : {
1291 : 18 : PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
1292 : : }
1293 : :
1294 : : Datum
1295 : 6 : record_gt(PG_FUNCTION_ARGS)
1296 : : {
1297 : 6 : PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
1298 : : }
1299 : :
1300 : : Datum
1301 : 6 : record_le(PG_FUNCTION_ARGS)
1302 : : {
1303 : 6 : PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
1304 : : }
1305 : :
1306 : : Datum
1307 : 21 : record_ge(PG_FUNCTION_ARGS)
1308 : : {
1309 : 21 : PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
1310 : : }
1311 : :
1312 : : Datum
1313 : 2203 : btrecordcmp(PG_FUNCTION_ARGS)
1314 : : {
1315 : 2203 : PG_RETURN_INT32(record_cmp(fcinfo));
1316 : : }
1317 : :
1318 : :
1319 : : /*
1320 : : * record_image_cmp :
1321 : : * Internal byte-oriented comparison function for records.
1322 : : *
1323 : : * Returns -1, 0 or 1
1324 : : *
1325 : : * Note: The normal concepts of "equality" do not apply here; different
1326 : : * representation of values considered to be equal are not considered to be
1327 : : * identical. As an example, for the citext type 'A' and 'a' are equal, but
1328 : : * they are not identical.
1329 : : */
1330 : : static int
3838 kgrittn@postgresql.o 1331 : 426 : record_image_cmp(FunctionCallInfo fcinfo)
1332 : : {
3840 1333 : 426 : HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1334 : 426 : HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
3838 1335 : 426 : int result = 0;
1336 : : Oid tupType1;
1337 : : Oid tupType2;
1338 : : int32 tupTypmod1;
1339 : : int32 tupTypmod2;
1340 : : TupleDesc tupdesc1;
1341 : : TupleDesc tupdesc2;
1342 : : HeapTupleData tuple1;
1343 : : HeapTupleData tuple2;
1344 : : int ncolumns1;
1345 : : int ncolumns2;
1346 : : RecordCompareData *my_extra;
1347 : : int ncols;
1348 : : Datum *values1;
1349 : : Datum *values2;
1350 : : bool *nulls1;
1351 : : bool *nulls2;
1352 : : int i1;
1353 : : int i2;
1354 : : int j;
1355 : :
1356 : : /* Extract type info from the tuples */
3840 1357 : 426 : tupType1 = HeapTupleHeaderGetTypeId(record1);
1358 : 426 : tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1359 : 426 : tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1360 : 426 : ncolumns1 = tupdesc1->natts;
1361 : 426 : tupType2 = HeapTupleHeaderGetTypeId(record2);
1362 : 426 : tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1363 : 426 : tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1364 : 426 : ncolumns2 = tupdesc2->natts;
1365 : :
1366 : : /* Build temporary HeapTuple control structures */
1367 : 426 : tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1368 : 426 : ItemPointerSetInvalid(&(tuple1.t_self));
1369 : 426 : tuple1.t_tableOid = InvalidOid;
1370 : 426 : tuple1.t_data = record1;
1371 : 426 : tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1372 : 426 : ItemPointerSetInvalid(&(tuple2.t_self));
1373 : 426 : tuple2.t_tableOid = InvalidOid;
1374 : 426 : tuple2.t_data = record2;
1375 : :
1376 : : /*
1377 : : * We arrange to look up the needed comparison info just once per series
1378 : : * of calls, assuming the record types don't change underneath us.
1379 : : */
1380 : 426 : ncols = Max(ncolumns1, ncolumns2);
1381 : 426 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1382 [ + + ]: 426 : if (my_extra == NULL ||
1383 [ - + ]: 303 : my_extra->ncolumns < ncols)
1384 : : {
1385 : 246 : fcinfo->flinfo->fn_extra =
1386 : 123 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
3341 tgl@sss.pgh.pa.us 1387 : 123 : offsetof(RecordCompareData, columns) +
1388 : : ncols * sizeof(ColumnCompareData));
3840 kgrittn@postgresql.o 1389 : 123 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1390 : 123 : my_extra->ncolumns = ncols;
1391 : 123 : my_extra->record1_type = InvalidOid;
1392 : 123 : my_extra->record1_typmod = 0;
1393 : 123 : my_extra->record2_type = InvalidOid;
1394 : 123 : my_extra->record2_typmod = 0;
1395 : : }
1396 : :
1397 [ + + ]: 426 : if (my_extra->record1_type != tupType1 ||
1398 [ + - ]: 303 : my_extra->record1_typmod != tupTypmod1 ||
1399 [ + - ]: 303 : my_extra->record2_type != tupType2 ||
1400 [ - + ]: 303 : my_extra->record2_typmod != tupTypmod2)
1401 : : {
1402 [ + - + - : 438 : MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
+ - + - +
+ ]
1403 : 123 : my_extra->record1_type = tupType1;
1404 : 123 : my_extra->record1_typmod = tupTypmod1;
1405 : 123 : my_extra->record2_type = tupType2;
1406 : 123 : my_extra->record2_typmod = tupTypmod2;
1407 : : }
1408 : :
1409 : : /* Break down the tuples into fields */
1410 : 426 : values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1411 : 426 : nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1412 : 426 : heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1413 : 426 : values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1414 : 426 : nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1415 : 426 : heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1416 : :
1417 : : /*
1418 : : * Scan corresponding columns, allowing for dropped columns in different
1419 : : * places in the two rows. i1 and i2 are physical column indexes, j is
1420 : : * the logical column index.
1421 : : */
1422 : 426 : i1 = i2 = j = 0;
1423 [ + + - + ]: 833 : while (i1 < ncolumns1 || i2 < ncolumns2)
1424 : : {
1425 : : Form_pg_attribute att1;
1426 : : Form_pg_attribute att2;
1427 : :
1428 : : /*
1429 : : * Skip dropped columns
1430 : : */
2429 andres@anarazel.de 1431 [ + - - + ]: 749 : if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1432 : : {
3840 kgrittn@postgresql.o 1433 :UBC 0 : i1++;
1434 : 0 : continue;
1435 : : }
2429 andres@anarazel.de 1436 [ + + - + ]:CBC 749 : if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1437 : : {
3840 kgrittn@postgresql.o 1438 :UBC 0 : i2++;
1439 : 0 : continue;
1440 : : }
3840 kgrittn@postgresql.o 1441 [ + - + + ]:CBC 749 : if (i1 >= ncolumns1 || i2 >= ncolumns2)
1442 : : break; /* we'll deal with mismatch below loop */
1443 : :
2429 andres@anarazel.de 1444 : 746 : att1 = TupleDescAttr(tupdesc1, i1);
1445 : 746 : att2 = TupleDescAttr(tupdesc2, i2);
1446 : :
1447 : : /*
1448 : : * Have two matching columns, they must be same type
1449 : : */
1450 [ + + ]: 746 : if (att1->atttypid != att2->atttypid)
3840 kgrittn@postgresql.o 1451 [ + - ]: 3 : ereport(ERROR,
1452 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
1453 : : errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1454 : : format_type_be(att1->atttypid),
1455 : : format_type_be(att2->atttypid),
1456 : : j + 1)));
1457 : :
1458 : : /*
1459 : : * The same type should have the same length (or both should be
1460 : : * variable).
1461 : : */
2429 andres@anarazel.de 1462 [ - + ]: 743 : Assert(att1->attlen == att2->attlen);
1463 : :
1464 : : /*
1465 : : * We consider two NULLs equal; NULL > not-NULL.
1466 : : */
3840 kgrittn@postgresql.o 1467 [ - + - - ]: 743 : if (!nulls1[i1] || !nulls2[i2])
1468 : : {
3827 tgl@sss.pgh.pa.us 1469 : 743 : int cmpresult = 0;
1470 : :
3840 kgrittn@postgresql.o 1471 [ - + ]: 743 : if (nulls1[i1])
1472 : : {
1473 : : /* arg1 is greater than arg2 */
3840 kgrittn@postgresql.o 1474 :UBC 0 : result = 1;
1475 : 0 : break;
1476 : : }
3840 kgrittn@postgresql.o 1477 [ - + ]:CBC 743 : if (nulls2[i2])
1478 : : {
1479 : : /* arg1 is less than arg2 */
3840 kgrittn@postgresql.o 1480 :UBC 0 : result = -1;
1481 : 0 : break;
1482 : : }
1483 : :
1484 : : /* Compare the pair of elements */
1623 peter@eisentraut.org 1485 [ + + ]:CBC 743 : if (att1->attbyval)
1486 : : {
1487 [ + + ]: 489 : if (values1[i1] != values2[i2])
1488 [ + + ]: 256 : cmpresult = (values1[i1] < values2[i2]) ? -1 : 1;
1489 : : }
1490 [ + + ]: 254 : else if (att1->attlen > 0)
1491 : : {
1492 : 18 : cmpresult = memcmp(DatumGetPointer(values1[i1]),
1493 : 18 : DatumGetPointer(values2[i2]),
1494 : 18 : att1->attlen);
1495 : : }
1496 [ + - ]: 236 : else if (att1->attlen == -1)
1497 : : {
1498 : : Size len1,
1499 : : len2;
1500 : : struct varlena *arg1val;
1501 : : struct varlena *arg2val;
1502 : :
3840 kgrittn@postgresql.o 1503 : 236 : len1 = toast_raw_datum_size(values1[i1]);
1504 : 236 : len2 = toast_raw_datum_size(values2[i2]);
1505 : 236 : arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
1506 : 236 : arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
1507 : :
1508 [ + - ]: 236 : cmpresult = memcmp(VARDATA_ANY(arg1val),
1509 : 236 : VARDATA_ANY(arg2val),
3838 1510 [ + - ]: 236 : Min(len1, len2) - VARHDRSZ);
3840 1511 [ + + + + ]: 236 : if ((cmpresult == 0) && (len1 != len2))
1512 [ + - ]: 3 : cmpresult = (len1 < len2) ? -1 : 1;
1513 : :
1514 [ - + ]: 236 : if ((Pointer) arg1val != (Pointer) values1[i1])
3840 kgrittn@postgresql.o 1515 :UBC 0 : pfree(arg1val);
3840 kgrittn@postgresql.o 1516 [ - + ]:CBC 236 : if ((Pointer) arg2val != (Pointer) values2[i2])
3840 kgrittn@postgresql.o 1517 :UBC 0 : pfree(arg2val);
1518 : : }
1519 : : else
1623 peter@eisentraut.org 1520 [ # # ]: 0 : elog(ERROR, "unexpected attlen: %d", att1->attlen);
1521 : :
3840 kgrittn@postgresql.o 1522 [ + + ]:CBC 743 : if (cmpresult < 0)
1523 : : {
1524 : : /* arg1 is less than arg2 */
1525 : 184 : result = -1;
1526 : 184 : break;
1527 : : }
1528 [ + + ]: 559 : else if (cmpresult > 0)
1529 : : {
1530 : : /* arg1 is greater than arg2 */
1531 : 152 : result = 1;
1532 : 152 : break;
1533 : : }
1534 : : }
1535 : :
1536 : : /* equal, so continue to next column */
1537 : 407 : i1++, i2++, j++;
1538 : : }
1539 : :
1540 : : /*
1541 : : * If we didn't break out of the loop early, check for column count
1542 : : * mismatch. (We do not report such mismatch if we found unequal column
1543 : : * values; is that a feature or a bug?)
1544 : : */
1545 [ + + ]: 423 : if (result == 0)
1546 : : {
1547 [ + + - + ]: 87 : if (i1 != ncolumns1 || i2 != ncolumns2)
1548 [ + - ]: 3 : ereport(ERROR,
1549 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
1550 : : errmsg("cannot compare record types with different numbers of columns")));
1551 : : }
1552 : :
1553 : 420 : pfree(values1);
1554 : 420 : pfree(nulls1);
1555 : 420 : pfree(values2);
1556 : 420 : pfree(nulls2);
1557 [ + - ]: 420 : ReleaseTupleDesc(tupdesc1);
1558 [ + - ]: 420 : ReleaseTupleDesc(tupdesc2);
1559 : :
1560 : : /* Avoid leaking memory when handed toasted input. */
1561 [ + + ]: 420 : PG_FREE_IF_COPY(record1, 0);
1562 [ + + ]: 420 : PG_FREE_IF_COPY(record2, 1);
1563 : :
1564 : 420 : return result;
1565 : : }
1566 : :
1567 : : /*
1568 : : * record_image_eq :
1569 : : * compares two records for identical contents, based on byte images
1570 : : * result :
1571 : : * returns true if the records are identical, false otherwise.
1572 : : *
1573 : : * Note: we do not use record_image_cmp here, since we can avoid
1574 : : * de-toasting for unequal lengths this way.
1575 : : */
1576 : : Datum
1577 : 130 : record_image_eq(PG_FUNCTION_ARGS)
1578 : : {
1579 : 130 : HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1580 : 130 : HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1581 : 130 : bool result = true;
1582 : : Oid tupType1;
1583 : : Oid tupType2;
1584 : : int32 tupTypmod1;
1585 : : int32 tupTypmod2;
1586 : : TupleDesc tupdesc1;
1587 : : TupleDesc tupdesc2;
1588 : : HeapTupleData tuple1;
1589 : : HeapTupleData tuple2;
1590 : : int ncolumns1;
1591 : : int ncolumns2;
1592 : : RecordCompareData *my_extra;
1593 : : int ncols;
1594 : : Datum *values1;
1595 : : Datum *values2;
1596 : : bool *nulls1;
1597 : : bool *nulls2;
1598 : : int i1;
1599 : : int i2;
1600 : : int j;
1601 : :
1602 : : /* Extract type info from the tuples */
1603 : 130 : tupType1 = HeapTupleHeaderGetTypeId(record1);
1604 : 130 : tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1605 : 130 : tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1606 : 130 : ncolumns1 = tupdesc1->natts;
1607 : 130 : tupType2 = HeapTupleHeaderGetTypeId(record2);
1608 : 130 : tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1609 : 130 : tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1610 : 130 : ncolumns2 = tupdesc2->natts;
1611 : :
1612 : : /* Build temporary HeapTuple control structures */
1613 : 130 : tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1614 : 130 : ItemPointerSetInvalid(&(tuple1.t_self));
1615 : 130 : tuple1.t_tableOid = InvalidOid;
1616 : 130 : tuple1.t_data = record1;
1617 : 130 : tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1618 : 130 : ItemPointerSetInvalid(&(tuple2.t_self));
1619 : 130 : tuple2.t_tableOid = InvalidOid;
1620 : 130 : tuple2.t_data = record2;
1621 : :
1622 : : /*
1623 : : * We arrange to look up the needed comparison info just once per series
1624 : : * of calls, assuming the record types don't change underneath us.
1625 : : */
1626 : 130 : ncols = Max(ncolumns1, ncolumns2);
1627 : 130 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1628 [ + + ]: 130 : if (my_extra == NULL ||
1629 [ - + ]: 65 : my_extra->ncolumns < ncols)
1630 : : {
1631 : 130 : fcinfo->flinfo->fn_extra =
1632 : 65 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
3341 tgl@sss.pgh.pa.us 1633 : 65 : offsetof(RecordCompareData, columns) +
1634 : : ncols * sizeof(ColumnCompareData));
3840 kgrittn@postgresql.o 1635 : 65 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1636 : 65 : my_extra->ncolumns = ncols;
1637 : 65 : my_extra->record1_type = InvalidOid;
1638 : 65 : my_extra->record1_typmod = 0;
1639 : 65 : my_extra->record2_type = InvalidOid;
1640 : 65 : my_extra->record2_typmod = 0;
1641 : : }
1642 : :
1643 [ + + ]: 130 : if (my_extra->record1_type != tupType1 ||
1644 [ + - ]: 65 : my_extra->record1_typmod != tupTypmod1 ||
1645 [ + - ]: 65 : my_extra->record2_type != tupType2 ||
1646 [ - + ]: 65 : my_extra->record2_typmod != tupTypmod2)
1647 : : {
1648 [ + - + - : 212 : MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
+ - + - +
+ ]
1649 : 65 : my_extra->record1_type = tupType1;
1650 : 65 : my_extra->record1_typmod = tupTypmod1;
1651 : 65 : my_extra->record2_type = tupType2;
1652 : 65 : my_extra->record2_typmod = tupTypmod2;
1653 : : }
1654 : :
1655 : : /* Break down the tuples into fields */
1656 : 130 : values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1657 : 130 : nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1658 : 130 : heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1659 : 130 : values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1660 : 130 : nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1661 : 130 : heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1662 : :
1663 : : /*
1664 : : * Scan corresponding columns, allowing for dropped columns in different
1665 : : * places in the two rows. i1 and i2 are physical column indexes, j is
1666 : : * the logical column index.
1667 : : */
1668 : 130 : i1 = i2 = j = 0;
1669 [ + + - + ]: 494 : while (i1 < ncolumns1 || i2 < ncolumns2)
1670 : : {
1671 : : Form_pg_attribute att1;
1672 : : Form_pg_attribute att2;
1673 : :
1674 : : /*
1675 : : * Skip dropped columns
1676 : : */
2429 andres@anarazel.de 1677 [ + - - + ]: 399 : if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
1678 : : {
3840 kgrittn@postgresql.o 1679 :UBC 0 : i1++;
1680 : 0 : continue;
1681 : : }
2429 andres@anarazel.de 1682 [ + + - + ]:CBC 399 : if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
1683 : : {
3840 kgrittn@postgresql.o 1684 :UBC 0 : i2++;
1685 : 0 : continue;
1686 : : }
3840 kgrittn@postgresql.o 1687 [ + - + + ]:CBC 399 : if (i1 >= ncolumns1 || i2 >= ncolumns2)
1688 : : break; /* we'll deal with mismatch below loop */
1689 : :
2429 andres@anarazel.de 1690 : 396 : att1 = TupleDescAttr(tupdesc1, i1);
1691 : 396 : att2 = TupleDescAttr(tupdesc2, i2);
1692 : :
1693 : : /*
1694 : : * Have two matching columns, they must be same type
1695 : : */
1696 [ + + ]: 396 : if (att1->atttypid != att2->atttypid)
3840 kgrittn@postgresql.o 1697 [ + - ]: 3 : ereport(ERROR,
1698 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
1699 : : errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1700 : : format_type_be(att1->atttypid),
1701 : : format_type_be(att2->atttypid),
1702 : : j + 1)));
1703 : :
1704 : : /*
1705 : : * We consider two NULLs equal; NULL > not-NULL.
1706 : : */
1707 [ + + - + ]: 393 : if (!nulls1[i1] || !nulls2[i2])
1708 : : {
1709 [ + - - + ]: 387 : if (nulls1[i1] || nulls2[i2])
1710 : : {
3840 kgrittn@postgresql.o 1711 :UBC 0 : result = false;
1712 : 0 : break;
1713 : : }
1714 : :
1715 : : /* Compare the pair of elements */
1854 peter@eisentraut.org 1716 :CBC 387 : result = datum_image_eq(values1[i1], values2[i2], att1->attbyval, att2->attlen);
3840 kgrittn@postgresql.o 1717 [ + + ]: 387 : if (!result)
1718 : 29 : break;
1719 : : }
1720 : :
1721 : : /* equal, so continue to next column */
1722 : 364 : i1++, i2++, j++;
1723 : : }
1724 : :
1725 : : /*
1726 : : * If we didn't break out of the loop early, check for column count
1727 : : * mismatch. (We do not report such mismatch if we found unequal column
1728 : : * values; is that a feature or a bug?)
1729 : : */
1730 [ + + ]: 127 : if (result)
1731 : : {
1732 [ + + - + ]: 98 : if (i1 != ncolumns1 || i2 != ncolumns2)
1733 [ + - ]: 3 : ereport(ERROR,
1734 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
1735 : : errmsg("cannot compare record types with different numbers of columns")));
1736 : : }
1737 : :
1738 : 124 : pfree(values1);
1739 : 124 : pfree(nulls1);
1740 : 124 : pfree(values2);
1741 : 124 : pfree(nulls2);
1742 [ + - ]: 124 : ReleaseTupleDesc(tupdesc1);
1743 [ + - ]: 124 : ReleaseTupleDesc(tupdesc2);
1744 : :
1745 : : /* Avoid leaking memory when handed toasted input. */
1746 [ + + ]: 124 : PG_FREE_IF_COPY(record1, 0);
1747 [ + + ]: 124 : PG_FREE_IF_COPY(record2, 1);
1748 : :
1749 : 124 : PG_RETURN_BOOL(result);
1750 : : }
1751 : :
1752 : : Datum
1753 : 24 : record_image_ne(PG_FUNCTION_ARGS)
1754 : : {
1755 : 24 : PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo)));
1756 : : }
1757 : :
1758 : : Datum
1759 : 36 : record_image_lt(PG_FUNCTION_ARGS)
1760 : : {
1761 : 36 : PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0);
1762 : : }
1763 : :
1764 : : Datum
1765 : 9 : record_image_gt(PG_FUNCTION_ARGS)
1766 : : {
1767 : 9 : PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0);
1768 : : }
1769 : :
1770 : : Datum
1771 : 6 : record_image_le(PG_FUNCTION_ARGS)
1772 : : {
1773 : 6 : PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0);
1774 : : }
1775 : :
1776 : : Datum
1777 : 9 : record_image_ge(PG_FUNCTION_ARGS)
1778 : : {
1779 : 9 : PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0);
1780 : : }
1781 : :
1782 : : Datum
1783 : 366 : btrecordimagecmp(PG_FUNCTION_ARGS)
1784 : : {
1785 : 366 : PG_RETURN_INT32(record_image_cmp(fcinfo));
1786 : : }
1787 : :
1788 : :
1789 : : /*
1790 : : * Row type hash functions
1791 : : */
1792 : :
1793 : : Datum
1242 peter@eisentraut.org 1794 : 450 : hash_record(PG_FUNCTION_ARGS)
1795 : : {
1796 : 450 : HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0);
1797 : 450 : uint32 result = 0;
1798 : : Oid tupType;
1799 : : int32 tupTypmod;
1800 : : TupleDesc tupdesc;
1801 : : HeapTupleData tuple;
1802 : : int ncolumns;
1803 : : RecordCompareData *my_extra;
1804 : : Datum *values;
1805 : : bool *nulls;
1806 : :
1807 : 450 : check_stack_depth(); /* recurses for record-type columns */
1808 : :
1809 : : /* Extract type info from tuple */
1810 : 450 : tupType = HeapTupleHeaderGetTypeId(record);
1811 : 450 : tupTypmod = HeapTupleHeaderGetTypMod(record);
1812 : 450 : tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
1813 : 450 : ncolumns = tupdesc->natts;
1814 : :
1815 : : /* Build temporary HeapTuple control structure */
1816 : 450 : tuple.t_len = HeapTupleHeaderGetDatumLength(record);
1817 : 450 : ItemPointerSetInvalid(&(tuple.t_self));
1818 : 450 : tuple.t_tableOid = InvalidOid;
1819 : 450 : tuple.t_data = record;
1820 : :
1821 : : /*
1822 : : * We arrange to look up the needed hashing info just once per series of
1823 : : * calls, assuming the record type doesn't change underneath us.
1824 : : */
1825 : 450 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1826 [ + + ]: 450 : if (my_extra == NULL ||
1827 [ - + ]: 426 : my_extra->ncolumns < ncolumns)
1828 : : {
1829 : 48 : fcinfo->flinfo->fn_extra =
1830 : 24 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1831 : 24 : offsetof(RecordCompareData, columns) +
1832 : : ncolumns * sizeof(ColumnCompareData));
1833 : 24 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1834 : 24 : my_extra->ncolumns = ncolumns;
1835 : 24 : my_extra->record1_type = InvalidOid;
1836 : 24 : my_extra->record1_typmod = 0;
1837 : : }
1838 : :
1839 [ + + ]: 450 : if (my_extra->record1_type != tupType ||
1840 [ - + ]: 426 : my_extra->record1_typmod != tupTypmod)
1841 : : {
1842 [ + - + - : 75 : MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData));
+ - + - +
+ ]
1843 : 24 : my_extra->record1_type = tupType;
1844 : 24 : my_extra->record1_typmod = tupTypmod;
1845 : : }
1846 : :
1847 : : /* Break down the tuple into fields */
1848 : 450 : values = (Datum *) palloc(ncolumns * sizeof(Datum));
1849 : 450 : nulls = (bool *) palloc(ncolumns * sizeof(bool));
1850 : 450 : heap_deform_tuple(&tuple, tupdesc, values, nulls);
1851 : :
1852 [ + + ]: 1365 : for (int i = 0; i < ncolumns; i++)
1853 : : {
1854 : : Form_pg_attribute att;
1855 : : TypeCacheEntry *typentry;
1856 : : uint32 element_hash;
1857 : :
1858 : 918 : att = TupleDescAttr(tupdesc, i);
1859 : :
1860 [ - + ]: 918 : if (att->attisdropped)
1242 peter@eisentraut.org 1861 :UBC 0 : continue;
1862 : :
1863 : : /*
1864 : : * Lookup the hash function if not done already
1865 : : */
1242 peter@eisentraut.org 1866 :CBC 918 : typentry = my_extra->columns[i].typentry;
1867 [ + + ]: 918 : if (typentry == NULL ||
1868 [ - + ]: 870 : typentry->type_id != att->atttypid)
1869 : : {
1870 : 48 : typentry = lookup_type_cache(att->atttypid,
1871 : : TYPECACHE_HASH_PROC_FINFO);
1872 [ + + ]: 48 : if (!OidIsValid(typentry->hash_proc_finfo.fn_oid))
1873 [ + - ]: 3 : ereport(ERROR,
1874 : : (errcode(ERRCODE_UNDEFINED_FUNCTION),
1875 : : errmsg("could not identify a hash function for type %s",
1876 : : format_type_be(typentry->type_id))));
1877 : 45 : my_extra->columns[i].typentry = typentry;
1878 : : }
1879 : :
1880 : : /* Compute hash of element */
1881 [ - + ]: 915 : if (nulls[i])
1882 : : {
1242 peter@eisentraut.org 1883 :UBC 0 : element_hash = 0;
1884 : : }
1885 : : else
1886 : : {
1242 peter@eisentraut.org 1887 :CBC 915 : LOCAL_FCINFO(locfcinfo, 1);
1888 : :
1889 : 915 : InitFunctionCallInfoData(*locfcinfo, &typentry->hash_proc_finfo, 1,
1890 : : att->attcollation, NULL, NULL);
1891 : 915 : locfcinfo->args[0].value = values[i];
1892 : 915 : locfcinfo->args[0].isnull = false;
1893 : 915 : element_hash = DatumGetUInt32(FunctionCallInvoke(locfcinfo));
1894 : :
1895 : : /* We don't expect hash support functions to return null */
1896 [ - + ]: 915 : Assert(!locfcinfo->isnull);
1897 : : }
1898 : :
1899 : : /* see hash_array() */
1900 : 915 : result = (result << 5) - result + element_hash;
1901 : : }
1902 : :
1903 : 447 : pfree(values);
1904 : 447 : pfree(nulls);
1905 [ + - ]: 447 : ReleaseTupleDesc(tupdesc);
1906 : :
1907 : : /* Avoid leaking memory when handed toasted input. */
1908 [ - + ]: 447 : PG_FREE_IF_COPY(record, 0);
1909 : :
1910 : 447 : PG_RETURN_UINT32(result);
1911 : : }
1912 : :
1913 : : Datum
1914 : 15 : hash_record_extended(PG_FUNCTION_ARGS)
1915 : : {
1916 : 15 : HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0);
1917 : 15 : uint64 seed = PG_GETARG_INT64(1);
1918 : 15 : uint64 result = 0;
1919 : : Oid tupType;
1920 : : int32 tupTypmod;
1921 : : TupleDesc tupdesc;
1922 : : HeapTupleData tuple;
1923 : : int ncolumns;
1924 : : RecordCompareData *my_extra;
1925 : : Datum *values;
1926 : : bool *nulls;
1927 : :
1928 : 15 : check_stack_depth(); /* recurses for record-type columns */
1929 : :
1930 : : /* Extract type info from tuple */
1931 : 15 : tupType = HeapTupleHeaderGetTypeId(record);
1932 : 15 : tupTypmod = HeapTupleHeaderGetTypMod(record);
1933 : 15 : tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
1934 : 15 : ncolumns = tupdesc->natts;
1935 : :
1936 : : /* Build temporary HeapTuple control structure */
1937 : 15 : tuple.t_len = HeapTupleHeaderGetDatumLength(record);
1938 : 15 : ItemPointerSetInvalid(&(tuple.t_self));
1939 : 15 : tuple.t_tableOid = InvalidOid;
1940 : 15 : tuple.t_data = record;
1941 : :
1942 : : /*
1943 : : * We arrange to look up the needed hashing info just once per series of
1944 : : * calls, assuming the record type doesn't change underneath us.
1945 : : */
1946 : 15 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1947 [ - + ]: 15 : if (my_extra == NULL ||
1242 peter@eisentraut.org 1948 [ # # ]:UBC 0 : my_extra->ncolumns < ncolumns)
1949 : : {
1242 peter@eisentraut.org 1950 :CBC 30 : fcinfo->flinfo->fn_extra =
1951 : 15 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1952 : 15 : offsetof(RecordCompareData, columns) +
1953 : : ncolumns * sizeof(ColumnCompareData));
1954 : 15 : my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1955 : 15 : my_extra->ncolumns = ncolumns;
1956 : 15 : my_extra->record1_type = InvalidOid;
1957 : 15 : my_extra->record1_typmod = 0;
1958 : : }
1959 : :
1960 [ - + ]: 15 : if (my_extra->record1_type != tupType ||
1242 peter@eisentraut.org 1961 [ # # ]:UBC 0 : my_extra->record1_typmod != tupTypmod)
1962 : : {
1242 peter@eisentraut.org 1963 [ + - + - :CBC 45 : MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData));
+ - + - +
+ ]
1964 : 15 : my_extra->record1_type = tupType;
1965 : 15 : my_extra->record1_typmod = tupTypmod;
1966 : : }
1967 : :
1968 : : /* Break down the tuple into fields */
1969 : 15 : values = (Datum *) palloc(ncolumns * sizeof(Datum));
1970 : 15 : nulls = (bool *) palloc(ncolumns * sizeof(bool));
1971 : 15 : heap_deform_tuple(&tuple, tupdesc, values, nulls);
1972 : :
1973 [ + + ]: 39 : for (int i = 0; i < ncolumns; i++)
1974 : : {
1975 : : Form_pg_attribute att;
1976 : : TypeCacheEntry *typentry;
1977 : : uint64 element_hash;
1978 : :
1979 : 27 : att = TupleDescAttr(tupdesc, i);
1980 : :
1981 [ - + ]: 27 : if (att->attisdropped)
1242 peter@eisentraut.org 1982 :UBC 0 : continue;
1983 : :
1984 : : /*
1985 : : * Lookup the hash function if not done already
1986 : : */
1242 peter@eisentraut.org 1987 :CBC 27 : typentry = my_extra->columns[i].typentry;
1988 [ - + ]: 27 : if (typentry == NULL ||
1242 peter@eisentraut.org 1989 [ # # ]:UBC 0 : typentry->type_id != att->atttypid)
1990 : : {
1242 peter@eisentraut.org 1991 :CBC 27 : typentry = lookup_type_cache(att->atttypid,
1992 : : TYPECACHE_HASH_EXTENDED_PROC_FINFO);
1993 [ + + ]: 27 : if (!OidIsValid(typentry->hash_extended_proc_finfo.fn_oid))
1994 [ + - ]: 3 : ereport(ERROR,
1995 : : (errcode(ERRCODE_UNDEFINED_FUNCTION),
1996 : : errmsg("could not identify an extended hash function for type %s",
1997 : : format_type_be(typentry->type_id))));
1998 : 24 : my_extra->columns[i].typentry = typentry;
1999 : : }
2000 : :
2001 : : /* Compute hash of element */
2002 [ - + ]: 24 : if (nulls[i])
2003 : : {
1242 peter@eisentraut.org 2004 :UBC 0 : element_hash = 0;
2005 : : }
2006 : : else
2007 : : {
1242 peter@eisentraut.org 2008 :CBC 24 : LOCAL_FCINFO(locfcinfo, 2);
2009 : :
2010 : 24 : InitFunctionCallInfoData(*locfcinfo, &typentry->hash_extended_proc_finfo, 2,
2011 : : att->attcollation, NULL, NULL);
2012 : 24 : locfcinfo->args[0].value = values[i];
2013 : 24 : locfcinfo->args[0].isnull = false;
2014 : 24 : locfcinfo->args[1].value = Int64GetDatum(seed);
2015 : 24 : locfcinfo->args[0].isnull = false;
2016 : 24 : element_hash = DatumGetUInt64(FunctionCallInvoke(locfcinfo));
2017 : :
2018 : : /* We don't expect hash support functions to return null */
2019 [ - + ]: 24 : Assert(!locfcinfo->isnull);
2020 : : }
2021 : :
2022 : : /* see hash_array_extended() */
2023 : 24 : result = (result << 5) - result + element_hash;
2024 : : }
2025 : :
2026 : 12 : pfree(values);
2027 : 12 : pfree(nulls);
2028 [ + - ]: 12 : ReleaseTupleDesc(tupdesc);
2029 : :
2030 : : /* Avoid leaking memory when handed toasted input. */
2031 [ - + ]: 12 : PG_FREE_IF_COPY(record, 0);
2032 : :
2033 : 12 : PG_RETURN_UINT64(result);
2034 : : }
|