LCOV - code coverage report
Current view: top level - src/backend/utils/adt - rowtypes.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13beta1 Lines: 544 713 76.3 %
Date: 2020-06-03 11:07:14 Functions: 18 20 90.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13