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