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