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