LCOV - code coverage report
Current view: top level - src/backend/utils/adt - rowtypes.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13devel Lines: 545 713 76.4 %
Date: 2019-09-19 02: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-2019, 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        1426 : record_in(PG_FUNCTION_ARGS)
      75             : {
      76        1426 :     char       *string = PG_GETARG_CSTRING(0);
      77        1426 :     Oid         tupType = PG_GETARG_OID(1);
      78        1426 :     int32       tupTypmod = PG_GETARG_INT32(2);
      79             :     HeapTupleHeader result;
      80             :     TupleDesc   tupdesc;
      81             :     HeapTuple   tuple;
      82             :     RecordIOData *my_extra;
      83        1426 :     bool        needComma = false;
      84             :     int         ncolumns;
      85             :     int         i;
      86             :     char       *ptr;
      87             :     Datum      *values;
      88             :     bool       *nulls;
      89             :     StringInfoData buf;
      90             : 
      91        1426 :     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        1426 :     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        1426 :     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
     112        1426 :     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        1426 :     my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
     119        2334 :     if (my_extra == NULL ||
     120         908 :         my_extra->ncolumns != ncolumns)
     121             :     {
     122        1036 :         fcinfo->flinfo->fn_extra =
     123         518 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
     124             :                                offsetof(RecordIOData, columns) +
     125         518 :                                ncolumns * sizeof(ColumnIOData));
     126         518 :         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
     127         518 :         my_extra->record_type = InvalidOid;
     128         518 :         my_extra->record_typmod = 0;
     129             :     }
     130             : 
     131        2334 :     if (my_extra->record_type != tupType ||
     132         908 :         my_extra->record_typmod != tupTypmod)
     133             :     {
     134         518 :         MemSet(my_extra, 0,
     135             :                offsetof(RecordIOData, columns) +
     136             :                ncolumns * sizeof(ColumnIOData));
     137         518 :         my_extra->record_type = tupType;
     138         518 :         my_extra->record_typmod = tupTypmod;
     139         518 :         my_extra->ncolumns = ncolumns;
     140             :     }
     141             : 
     142        1426 :     values = (Datum *) palloc(ncolumns * sizeof(Datum));
     143        1426 :     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        1426 :     ptr = string;
     150             :     /* Allow leading whitespace */
     151        2856 :     while (*ptr && isspace((unsigned char) *ptr))
     152           4 :         ptr++;
     153        1426 :     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        1418 :     initStringInfo(&buf);
     160             : 
     161        6668 :     for (i = 0; i < ncolumns; i++)
     162             :     {
     163        5256 :         Form_pg_attribute att = TupleDescAttr(tupdesc, i);
     164        5256 :         ColumnIOData *column_info = &my_extra->columns[i];
     165        5256 :         Oid         column_type = att->atttypid;
     166             :         char       *column_data;
     167             : 
     168             :         /* Ignore dropped columns in datatype, but fill with nulls */
     169        5256 :         if (att->attisdropped)
     170             :         {
     171         254 :             values[i] = (Datum) 0;
     172         254 :             nulls[i] = true;
     173         254 :             continue;
     174             :         }
     175             : 
     176        5002 :         if (needComma)
     177             :         {
     178             :             /* Skip comma that separates prior field from this one */
     179        3584 :             if (*ptr == ',')
     180        3580 :                 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        4998 :         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        4836 :             bool        inquote = false;
     199             : 
     200        4836 :             resetStringInfo(&buf);
     201       32896 :             while (inquote || !(*ptr == ',' || *ptr == ')'))
     202             :             {
     203       23224 :                 char        ch = *ptr++;
     204             : 
     205       23224 :                 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       23224 :                 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       23220 :                 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       21648 :                     appendStringInfoChar(&buf, ch);
     235             :             }
     236             : 
     237        4836 :             column_data = buf.data;
     238        4836 :             nulls[i] = false;
     239             :         }
     240             : 
     241             :         /*
     242             :          * Convert the column value
     243             :          */
     244        4998 :         if (column_info->column_type != column_type)
     245             :         {
     246        1404 :             getTypeInputInfo(column_type,
     247             :                              &column_info->typiofunc,
     248             :                              &column_info->typioparam);
     249        1404 :             fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
     250        1404 :                           fcinfo->flinfo->fn_mcxt);
     251        1404 :             column_info->column_type = column_type;
     252             :         }
     253             : 
     254        4998 :         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        4996 :         needComma = true;
     263             :     }
     264             : 
     265        1412 :     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        2828 :     while (*ptr && isspace((unsigned char) *ptr))
     272          12 :         ptr++;
     273        1408 :     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        1404 :     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        1404 :     result = (HeapTupleHeader) palloc(tuple->t_len);
     287        1404 :     memcpy(result, tuple->t_data, tuple->t_len);
     288             : 
     289        1404 :     heap_freetuple(tuple);
     290        1404 :     pfree(buf.data);
     291        1404 :     pfree(values);
     292        1404 :     pfree(nulls);
     293        1404 :     ReleaseTupleDesc(tupdesc);
     294             : 
     295        1404 :     PG_RETURN_HEAPTUPLEHEADER(result);
     296             : }
     297             : 
     298             : /*
     299             :  * record_out       - output routine for any composite type.
     300             :  */
     301             : Datum
     302       24718 : record_out(PG_FUNCTION_ARGS)
     303             : {
     304       24718 :     HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
     305             :     Oid         tupType;
     306             :     int32       tupTypmod;
     307             :     TupleDesc   tupdesc;
     308             :     HeapTupleData tuple;
     309             :     RecordIOData *my_extra;
     310       24718 :     bool        needComma = false;
     311             :     int         ncolumns;
     312             :     int         i;
     313             :     Datum      *values;
     314             :     bool       *nulls;
     315             :     StringInfoData buf;
     316             : 
     317       24718 :     check_stack_depth();        /* recurses for record-type columns */
     318             : 
     319             :     /* Extract type info from the tuple itself */
     320       24718 :     tupType = HeapTupleHeaderGetTypeId(rec);
     321       24718 :     tupTypmod = HeapTupleHeaderGetTypMod(rec);
     322       24718 :     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
     323       24718 :     ncolumns = tupdesc->natts;
     324             : 
     325             :     /* Build a temporary HeapTuple control structure */
     326       24718 :     tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
     327       24718 :     ItemPointerSetInvalid(&(tuple.t_self));
     328       24718 :     tuple.t_tableOid = InvalidOid;
     329       24718 :     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       24718 :     my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
     336       47178 :     if (my_extra == NULL ||
     337       22460 :         my_extra->ncolumns != ncolumns)
     338             :     {
     339        4548 :         fcinfo->flinfo->fn_extra =
     340        2274 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
     341             :                                offsetof(RecordIOData, columns) +
     342        2274 :                                ncolumns * sizeof(ColumnIOData));
     343        2274 :         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
     344        2274 :         my_extra->record_type = InvalidOid;
     345        2274 :         my_extra->record_typmod = 0;
     346             :     }
     347             : 
     348       47162 :     if (my_extra->record_type != tupType ||
     349       22444 :         my_extra->record_typmod != tupTypmod)
     350             :     {
     351        2298 :         MemSet(my_extra, 0,
     352             :                offsetof(RecordIOData, columns) +
     353             :                ncolumns * sizeof(ColumnIOData));
     354        2298 :         my_extra->record_type = tupType;
     355        2298 :         my_extra->record_typmod = tupTypmod;
     356        2298 :         my_extra->ncolumns = ncolumns;
     357             :     }
     358             : 
     359       24718 :     values = (Datum *) palloc(ncolumns * sizeof(Datum));
     360       24718 :     nulls = (bool *) palloc(ncolumns * sizeof(bool));
     361             : 
     362             :     /* Break down the tuple into fields */
     363       24718 :     heap_deform_tuple(&tuple, tupdesc, values, nulls);
     364             : 
     365             :     /* And build the result string */
     366       24718 :     initStringInfo(&buf);
     367             : 
     368       24718 :     appendStringInfoChar(&buf, '(');
     369             : 
     370      178440 :     for (i = 0; i < ncolumns; i++)
     371             :     {
     372      153722 :         Form_pg_attribute att = TupleDescAttr(tupdesc, i);
     373      153722 :         ColumnIOData *column_info = &my_extra->columns[i];
     374      153722 :         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      153722 :         if (att->attisdropped)
     382         430 :             continue;
     383             : 
     384      153292 :         if (needComma)
     385      128578 :             appendStringInfoChar(&buf, ',');
     386      153292 :         needComma = true;
     387             : 
     388      153292 :         if (nulls[i])
     389             :         {
     390             :             /* emit nothing... */
     391        1520 :             continue;
     392             :         }
     393             : 
     394             :         /*
     395             :          * Convert the column value to text
     396             :          */
     397      151772 :         if (column_info->column_type != column_type)
     398             :         {
     399        5640 :             getTypeOutputInfo(column_type,
     400             :                               &column_info->typiofunc,
     401             :                               &column_info->typisvarlena);
     402        5640 :             fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
     403        5640 :                           fcinfo->flinfo->fn_mcxt);
     404        5640 :             column_info->column_type = column_type;
     405             :         }
     406             : 
     407      151772 :         attr = values[i];
     408      151772 :         value = OutputFunctionCall(&column_info->proc, attr);
     409             : 
     410             :         /* Detect whether we need double quotes for this value */
     411      151772 :         nq = (value[0] == '\0');    /* force quotes for empty string */
     412    72353050 :         for (tmp = value; *tmp; tmp++)
     413             :         {
     414    72251116 :             char        ch = *tmp;
     415             : 
     416    72251116 :             if (ch == '"' || ch == '\\' ||
     417   144501310 :                 ch == '(' || ch == ')' || ch == ',' ||
     418    72250454 :                 isspace((unsigned char) ch))
     419             :             {
     420       49838 :                 nq = true;
     421       49838 :                 break;
     422             :             }
     423             :         }
     424             : 
     425             :         /* And emit the string */
     426      151772 :         if (nq)
     427       49838 :             appendStringInfoCharMacro(&buf, '"');
     428    72852904 :         for (tmp = value; *tmp; tmp++)
     429             :         {
     430    72701132 :             char        ch = *tmp;
     431             : 
     432    72701132 :             if (ch == '"' || ch == '\\')
     433         328 :                 appendStringInfoCharMacro(&buf, ch);
     434    72701132 :             appendStringInfoCharMacro(&buf, ch);
     435             :         }
     436      151772 :         if (nq)
     437       49838 :             appendStringInfoCharMacro(&buf, '"');
     438             :     }
     439             : 
     440       24718 :     appendStringInfoChar(&buf, ')');
     441             : 
     442       24718 :     pfree(values);
     443       24718 :     pfree(nulls);
     444       24718 :     ReleaseTupleDesc(tupdesc);
     445             : 
     446       24718 :     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        1490 : record_cmp(FunctionCallInfo fcinfo)
     786             : {
     787        1490 :     HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
     788        1490 :     HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
     789        1490 :     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        1490 :     check_stack_depth();        /* recurses for record-type columns */
     811             : 
     812             :     /* Extract type info from the tuples */
     813        1490 :     tupType1 = HeapTupleHeaderGetTypeId(record1);
     814        1490 :     tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
     815        1490 :     tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
     816        1490 :     ncolumns1 = tupdesc1->natts;
     817        1490 :     tupType2 = HeapTupleHeaderGetTypeId(record2);
     818        1490 :     tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
     819        1490 :     tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
     820        1490 :     ncolumns2 = tupdesc2->natts;
     821             : 
     822             :     /* Build temporary HeapTuple control structures */
     823        1490 :     tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
     824        1490 :     ItemPointerSetInvalid(&(tuple1.t_self));
     825        1490 :     tuple1.t_tableOid = InvalidOid;
     826        1490 :     tuple1.t_data = record1;
     827        1490 :     tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
     828        1490 :     ItemPointerSetInvalid(&(tuple2.t_self));
     829        1490 :     tuple2.t_tableOid = InvalidOid;
     830        1490 :     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        1490 :     ncols = Max(ncolumns1, ncolumns2);
     837        1490 :     my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
     838        2748 :     if (my_extra == NULL ||
     839        1258 :         my_extra->ncolumns < ncols)
     840             :     {
     841         464 :         fcinfo->flinfo->fn_extra =
     842         232 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
     843             :                                offsetof(RecordCompareData, columns) +
     844             :                                ncols * sizeof(ColumnCompareData));
     845         232 :         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
     846         232 :         my_extra->ncolumns = ncols;
     847         232 :         my_extra->record1_type = InvalidOid;
     848         232 :         my_extra->record1_typmod = 0;
     849         232 :         my_extra->record2_type = InvalidOid;
     850         232 :         my_extra->record2_typmod = 0;
     851             :     }
     852             : 
     853        2748 :     if (my_extra->record1_type != tupType1 ||
     854        2516 :         my_extra->record1_typmod != tupTypmod1 ||
     855        2516 :         my_extra->record2_type != tupType2 ||
     856        1258 :         my_extra->record2_typmod != tupTypmod2)
     857             :     {
     858         232 :         MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
     859         232 :         my_extra->record1_type = tupType1;
     860         232 :         my_extra->record1_typmod = tupTypmod1;
     861         232 :         my_extra->record2_type = tupType2;
     862         232 :         my_extra->record2_typmod = tupTypmod2;
     863             :     }
     864             : 
     865             :     /* Break down the tuples into fields */
     866        1490 :     values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
     867        1490 :     nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
     868        1490 :     heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
     869        1490 :     values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
     870        1490 :     nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
     871        1490 :     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        1490 :     i1 = i2 = j = 0;
     879        4214 :     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        2238 :         if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
     890             :         {
     891           0 :             i1++;
     892           0 :             continue;
     893             :         }
     894        2238 :         if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
     895             :         {
     896           0 :             i2++;
     897           0 :             continue;
     898             :         }
     899        2238 :         if (i1 >= ncolumns1 || i2 >= ncolumns2)
     900             :             break;              /* we'll deal with mismatch below loop */
     901             : 
     902        2234 :         att1 = TupleDescAttr(tupdesc1, i1);
     903        2234 :         att2 = TupleDescAttr(tupdesc2, i2);
     904             : 
     905             :         /*
     906             :          * Have two matching columns, they must be same type
     907             :          */
     908        2234 :         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        2230 :         collation = att1->attcollation;
     921        2230 :         if (collation != att2->attcollation)
     922           0 :             collation = InvalidOid;
     923             : 
     924             :         /*
     925             :          * Lookup the comparison function if not done already
     926             :          */
     927        2230 :         typentry = my_extra->columns[j].typentry;
     928        4068 :         if (typentry == NULL ||
     929        1838 :             typentry->type_id != att1->atttypid)
     930             :         {
     931         392 :             typentry = lookup_type_cache(att1->atttypid,
     932             :                                          TYPECACHE_CMP_PROC_FINFO);
     933         392 :             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         388 :             my_extra->columns[j].typentry = typentry;
     939             :         }
     940             : 
     941             :         /*
     942             :          * We consider two NULLs equal; NULL > not-NULL.
     943             :          */
     944        2226 :         if (!nulls1[i1] || !nulls2[i2])
     945             :         {
     946        2218 :             LOCAL_FCINFO(locfcinfo, 2);
     947             :             int32       cmpresult;
     948             : 
     949        2218 :             if (nulls1[i1])
     950             :             {
     951             :                 /* arg1 is greater than arg2 */
     952           8 :                 result = 1;
     953        1000 :                 break;
     954             :             }
     955        2210 :             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        2210 :             InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2,
     964             :                                      collation, NULL, NULL);
     965        2210 :             locfcinfo->args[0].value = values1[i1];
     966        2210 :             locfcinfo->args[0].isnull = false;
     967        2210 :             locfcinfo->args[1].value = values2[i2];
     968        2210 :             locfcinfo->args[1].isnull = false;
     969        2210 :             locfcinfo->isnull = false;
     970        2210 :             cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo));
     971             : 
     972        2210 :             if (cmpresult < 0)
     973             :             {
     974             :                 /* arg1 is less than arg2 */
     975         552 :                 result = -1;
     976         552 :                 break;
     977             :             }
     978        1658 :             else if (cmpresult > 0)
     979             :             {
     980             :                 /* arg1 is greater than arg2 */
     981         432 :                 result = 1;
     982         432 :                 break;
     983             :             }
     984             :         }
     985             : 
     986             :         /* equal, so continue to next column */
     987        1234 :         i1++, i2++, j++;
     988             :     }
     989             : 
     990             :     /*
     991             :      * If we didn't break out of the loop early, check for column count
     992             :      * mismatch.  (We do not report such mismatch if we found unequal column
     993             :      * values; is that a feature or a bug?)
     994             :      */
     995        1482 :     if (result == 0)
     996             :     {
     997         490 :         if (i1 != ncolumns1 || i2 != ncolumns2)
     998           4 :             ereport(ERROR,
     999             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
    1000             :                      errmsg("cannot compare record types with different numbers of columns")));
    1001             :     }
    1002             : 
    1003        1478 :     pfree(values1);
    1004        1478 :     pfree(nulls1);
    1005        1478 :     pfree(values2);
    1006        1478 :     pfree(nulls2);
    1007        1478 :     ReleaseTupleDesc(tupdesc1);
    1008        1478 :     ReleaseTupleDesc(tupdesc2);
    1009             : 
    1010             :     /* Avoid leaking memory when handed toasted input. */
    1011        1478 :     PG_FREE_IF_COPY(record1, 0);
    1012        1478 :     PG_FREE_IF_COPY(record2, 1);
    1013             : 
    1014        1478 :     return result;
    1015             : }
    1016             : 
    1017             : /*
    1018             :  * record_eq :
    1019             :  *        compares two records for equality
    1020             :  * result :
    1021             :  *        returns true if the records are equal, false otherwise.
    1022             :  *
    1023             :  * Note: we do not use record_cmp here, since equality may be meaningful in
    1024             :  * datatypes that don't have a total ordering (and hence no btree support).
    1025             :  */
    1026             : Datum
    1027         830 : record_eq(PG_FUNCTION_ARGS)
    1028             : {
    1029         830 :     HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
    1030         830 :     HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
    1031         830 :     bool        result = true;
    1032             :     Oid         tupType1;
    1033             :     Oid         tupType2;
    1034             :     int32       tupTypmod1;
    1035             :     int32       tupTypmod2;
    1036             :     TupleDesc   tupdesc1;
    1037             :     TupleDesc   tupdesc2;
    1038             :     HeapTupleData tuple1;
    1039             :     HeapTupleData tuple2;
    1040             :     int         ncolumns1;
    1041             :     int         ncolumns2;
    1042             :     RecordCompareData *my_extra;
    1043             :     int         ncols;
    1044             :     Datum      *values1;
    1045             :     Datum      *values2;
    1046             :     bool       *nulls1;
    1047             :     bool       *nulls2;
    1048             :     int         i1;
    1049             :     int         i2;
    1050             :     int         j;
    1051             : 
    1052         830 :     check_stack_depth();        /* recurses for record-type columns */
    1053             : 
    1054             :     /* Extract type info from the tuples */
    1055         830 :     tupType1 = HeapTupleHeaderGetTypeId(record1);
    1056         830 :     tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
    1057         830 :     tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
    1058         830 :     ncolumns1 = tupdesc1->natts;
    1059         830 :     tupType2 = HeapTupleHeaderGetTypeId(record2);
    1060         830 :     tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
    1061         830 :     tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
    1062         830 :     ncolumns2 = tupdesc2->natts;
    1063             : 
    1064             :     /* Build temporary HeapTuple control structures */
    1065         830 :     tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
    1066         830 :     ItemPointerSetInvalid(&(tuple1.t_self));
    1067         830 :     tuple1.t_tableOid = InvalidOid;
    1068         830 :     tuple1.t_data = record1;
    1069         830 :     tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
    1070         830 :     ItemPointerSetInvalid(&(tuple2.t_self));
    1071         830 :     tuple2.t_tableOid = InvalidOid;
    1072         830 :     tuple2.t_data = record2;
    1073             : 
    1074             :     /*
    1075             :      * We arrange to look up the needed comparison info just once per series
    1076             :      * of calls, assuming the record types don't change underneath us.
    1077             :      */
    1078         830 :     ncols = Max(ncolumns1, ncolumns2);
    1079         830 :     my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1080        1480 :     if (my_extra == NULL ||
    1081         650 :         my_extra->ncolumns < ncols)
    1082             :     {
    1083         360 :         fcinfo->flinfo->fn_extra =
    1084         180 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
    1085             :                                offsetof(RecordCompareData, columns) +
    1086             :                                ncols * sizeof(ColumnCompareData));
    1087         180 :         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1088         180 :         my_extra->ncolumns = ncols;
    1089         180 :         my_extra->record1_type = InvalidOid;
    1090         180 :         my_extra->record1_typmod = 0;
    1091         180 :         my_extra->record2_type = InvalidOid;
    1092         180 :         my_extra->record2_typmod = 0;
    1093             :     }
    1094             : 
    1095        1480 :     if (my_extra->record1_type != tupType1 ||
    1096        1300 :         my_extra->record1_typmod != tupTypmod1 ||
    1097        1300 :         my_extra->record2_type != tupType2 ||
    1098         650 :         my_extra->record2_typmod != tupTypmod2)
    1099             :     {
    1100         180 :         MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
    1101         180 :         my_extra->record1_type = tupType1;
    1102         180 :         my_extra->record1_typmod = tupTypmod1;
    1103         180 :         my_extra->record2_type = tupType2;
    1104         180 :         my_extra->record2_typmod = tupTypmod2;
    1105             :     }
    1106             : 
    1107             :     /* Break down the tuples into fields */
    1108         830 :     values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
    1109         830 :     nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
    1110         830 :     heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
    1111         830 :     values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
    1112         830 :     nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
    1113         830 :     heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
    1114             : 
    1115             :     /*
    1116             :      * Scan corresponding columns, allowing for dropped columns in different
    1117             :      * places in the two rows.  i1 and i2 are physical column indexes, j is
    1118             :      * the logical column index.
    1119             :      */
    1120         830 :     i1 = i2 = j = 0;
    1121        2982 :     while (i1 < ncolumns1 || i2 < ncolumns2)
    1122             :     {
    1123        1754 :         LOCAL_FCINFO(locfcinfo, 2);
    1124             :         Form_pg_attribute att1;
    1125             :         Form_pg_attribute att2;
    1126             :         TypeCacheEntry *typentry;
    1127             :         Oid         collation;
    1128             :         bool        oprresult;
    1129             : 
    1130             :         /*
    1131             :          * Skip dropped columns
    1132             :          */
    1133        1754 :         if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
    1134             :         {
    1135           0 :             i1++;
    1136           0 :             continue;
    1137             :         }
    1138        1754 :         if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
    1139             :         {
    1140           0 :             i2++;
    1141           0 :             continue;
    1142             :         }
    1143        1754 :         if (i1 >= ncolumns1 || i2 >= ncolumns2)
    1144             :             break;              /* we'll deal with mismatch below loop */
    1145             : 
    1146        1750 :         att1 = TupleDescAttr(tupdesc1, i1);
    1147        1750 :         att2 = TupleDescAttr(tupdesc2, i2);
    1148             : 
    1149             :         /*
    1150             :          * Have two matching columns, they must be same type
    1151             :          */
    1152        1750 :         if (att1->atttypid != att2->atttypid)
    1153           8 :             ereport(ERROR,
    1154             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
    1155             :                      errmsg("cannot compare dissimilar column types %s and %s at record column %d",
    1156             :                             format_type_be(att1->atttypid),
    1157             :                             format_type_be(att2->atttypid),
    1158             :                             j + 1)));
    1159             : 
    1160             :         /*
    1161             :          * If they're not same collation, we don't complain here, but the
    1162             :          * equality function might.
    1163             :          */
    1164        1742 :         collation = att1->attcollation;
    1165        1742 :         if (collation != att2->attcollation)
    1166           0 :             collation = InvalidOid;
    1167             : 
    1168             :         /*
    1169             :          * Lookup the equality function if not done already
    1170             :          */
    1171        1742 :         typentry = my_extra->columns[j].typentry;
    1172        3080 :         if (typentry == NULL ||
    1173        1338 :             typentry->type_id != att1->atttypid)
    1174             :         {
    1175         404 :             typentry = lookup_type_cache(att1->atttypid,
    1176             :                                          TYPECACHE_EQ_OPR_FINFO);
    1177         404 :             if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
    1178           4 :                 ereport(ERROR,
    1179             :                         (errcode(ERRCODE_UNDEFINED_FUNCTION),
    1180             :                          errmsg("could not identify an equality operator for type %s",
    1181             :                                 format_type_be(typentry->type_id))));
    1182         400 :             my_extra->columns[j].typentry = typentry;
    1183             :         }
    1184             : 
    1185             :         /*
    1186             :          * We consider two NULLs equal; NULL > not-NULL.
    1187             :          */
    1188        1738 :         if (!nulls1[i1] || !nulls2[i2])
    1189             :         {
    1190        1578 :             if (nulls1[i1] || nulls2[i2])
    1191             :             {
    1192           4 :                 result = false;
    1193           4 :                 break;
    1194             :             }
    1195             : 
    1196             :             /* Compare the pair of elements */
    1197        1574 :             InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
    1198             :                                      collation, NULL, NULL);
    1199        1574 :             locfcinfo->args[0].value = values1[i1];
    1200        1574 :             locfcinfo->args[0].isnull = false;
    1201        1574 :             locfcinfo->args[1].value = values2[i2];
    1202        1574 :             locfcinfo->args[1].isnull = false;
    1203        1574 :             locfcinfo->isnull = false;
    1204        1574 :             oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
    1205        1574 :             if (!oprresult)
    1206             :             {
    1207         412 :                 result = false;
    1208         412 :                 break;
    1209             :             }
    1210             :         }
    1211             : 
    1212             :         /* equal, so continue to next column */
    1213        1322 :         i1++, i2++, j++;
    1214             :     }
    1215             : 
    1216             :     /*
    1217             :      * If we didn't break out of the loop early, check for column count
    1218             :      * mismatch.  (We do not report such mismatch if we found unequal column
    1219             :      * values; is that a feature or a bug?)
    1220             :      */
    1221         818 :     if (result)
    1222             :     {
    1223         402 :         if (i1 != ncolumns1 || i2 != ncolumns2)
    1224           4 :             ereport(ERROR,
    1225             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
    1226             :                      errmsg("cannot compare record types with different numbers of columns")));
    1227             :     }
    1228             : 
    1229         814 :     pfree(values1);
    1230         814 :     pfree(nulls1);
    1231         814 :     pfree(values2);
    1232         814 :     pfree(nulls2);
    1233         814 :     ReleaseTupleDesc(tupdesc1);
    1234         814 :     ReleaseTupleDesc(tupdesc2);
    1235             : 
    1236             :     /* Avoid leaking memory when handed toasted input. */
    1237         814 :     PG_FREE_IF_COPY(record1, 0);
    1238         814 :     PG_FREE_IF_COPY(record2, 1);
    1239             : 
    1240         814 :     PG_RETURN_BOOL(result);
    1241             : }
    1242             : 
    1243             : Datum
    1244          36 : record_ne(PG_FUNCTION_ARGS)
    1245             : {
    1246          36 :     PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
    1247             : }
    1248             : 
    1249             : Datum
    1250          24 : record_lt(PG_FUNCTION_ARGS)
    1251             : {
    1252          24 :     PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
    1253             : }
    1254             : 
    1255             : Datum
    1256           8 : record_gt(PG_FUNCTION_ARGS)
    1257             : {
    1258           8 :     PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
    1259             : }
    1260             : 
    1261             : Datum
    1262           8 : record_le(PG_FUNCTION_ARGS)
    1263             : {
    1264           8 :     PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
    1265             : }
    1266             : 
    1267             : Datum
    1268          28 : record_ge(PG_FUNCTION_ARGS)
    1269             : {
    1270          28 :     PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
    1271             : }
    1272             : 
    1273             : Datum
    1274        1422 : btrecordcmp(PG_FUNCTION_ARGS)
    1275             : {
    1276        1422 :     PG_RETURN_INT32(record_cmp(fcinfo));
    1277             : }
    1278             : 
    1279             : 
    1280             : /*
    1281             :  * record_image_cmp :
    1282             :  * Internal byte-oriented comparison function for records.
    1283             :  *
    1284             :  * Returns -1, 0 or 1
    1285             :  *
    1286             :  * Note: The normal concepts of "equality" do not apply here; different
    1287             :  * representation of values considered to be equal are not considered to be
    1288             :  * identical.  As an example, for the citext type 'A' and 'a' are equal, but
    1289             :  * they are not identical.
    1290             :  */
    1291             : static int
    1292         438 : record_image_cmp(FunctionCallInfo fcinfo)
    1293             : {
    1294         438 :     HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
    1295         438 :     HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
    1296         438 :     int         result = 0;
    1297             :     Oid         tupType1;
    1298             :     Oid         tupType2;
    1299             :     int32       tupTypmod1;
    1300             :     int32       tupTypmod2;
    1301             :     TupleDesc   tupdesc1;
    1302             :     TupleDesc   tupdesc2;
    1303             :     HeapTupleData tuple1;
    1304             :     HeapTupleData tuple2;
    1305             :     int         ncolumns1;
    1306             :     int         ncolumns2;
    1307             :     RecordCompareData *my_extra;
    1308             :     int         ncols;
    1309             :     Datum      *values1;
    1310             :     Datum      *values2;
    1311             :     bool       *nulls1;
    1312             :     bool       *nulls2;
    1313             :     int         i1;
    1314             :     int         i2;
    1315             :     int         j;
    1316             : 
    1317             :     /* Extract type info from the tuples */
    1318         438 :     tupType1 = HeapTupleHeaderGetTypeId(record1);
    1319         438 :     tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
    1320         438 :     tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
    1321         438 :     ncolumns1 = tupdesc1->natts;
    1322         438 :     tupType2 = HeapTupleHeaderGetTypeId(record2);
    1323         438 :     tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
    1324         438 :     tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
    1325         438 :     ncolumns2 = tupdesc2->natts;
    1326             : 
    1327             :     /* Build temporary HeapTuple control structures */
    1328         438 :     tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
    1329         438 :     ItemPointerSetInvalid(&(tuple1.t_self));
    1330         438 :     tuple1.t_tableOid = InvalidOid;
    1331         438 :     tuple1.t_data = record1;
    1332         438 :     tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
    1333         438 :     ItemPointerSetInvalid(&(tuple2.t_self));
    1334         438 :     tuple2.t_tableOid = InvalidOid;
    1335         438 :     tuple2.t_data = record2;
    1336             : 
    1337             :     /*
    1338             :      * We arrange to look up the needed comparison info just once per series
    1339             :      * of calls, assuming the record types don't change underneath us.
    1340             :      */
    1341         438 :     ncols = Max(ncolumns1, ncolumns2);
    1342         438 :     my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1343         742 :     if (my_extra == NULL ||
    1344         304 :         my_extra->ncolumns < ncols)
    1345             :     {
    1346         268 :         fcinfo->flinfo->fn_extra =
    1347         134 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
    1348             :                                offsetof(RecordCompareData, columns) +
    1349             :                                ncols * sizeof(ColumnCompareData));
    1350         134 :         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1351         134 :         my_extra->ncolumns = ncols;
    1352         134 :         my_extra->record1_type = InvalidOid;
    1353         134 :         my_extra->record1_typmod = 0;
    1354         134 :         my_extra->record2_type = InvalidOid;
    1355         134 :         my_extra->record2_typmod = 0;
    1356             :     }
    1357             : 
    1358         742 :     if (my_extra->record1_type != tupType1 ||
    1359         608 :         my_extra->record1_typmod != tupTypmod1 ||
    1360         608 :         my_extra->record2_type != tupType2 ||
    1361         304 :         my_extra->record2_typmod != tupTypmod2)
    1362             :     {
    1363         134 :         MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
    1364         134 :         my_extra->record1_type = tupType1;
    1365         134 :         my_extra->record1_typmod = tupTypmod1;
    1366         134 :         my_extra->record2_type = tupType2;
    1367         134 :         my_extra->record2_typmod = tupTypmod2;
    1368             :     }
    1369             : 
    1370             :     /* Break down the tuples into fields */
    1371         438 :     values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
    1372         438 :     nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
    1373         438 :     heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
    1374         438 :     values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
    1375         438 :     nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
    1376         438 :     heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
    1377             : 
    1378             :     /*
    1379             :      * Scan corresponding columns, allowing for dropped columns in different
    1380             :      * places in the two rows.  i1 and i2 are physical column indexes, j is
    1381             :      * the logical column index.
    1382             :      */
    1383         438 :     i1 = i2 = j = 0;
    1384        1124 :     while (i1 < ncolumns1 || i2 < ncolumns2)
    1385             :     {
    1386             :         Form_pg_attribute att1;
    1387             :         Form_pg_attribute att2;
    1388             : 
    1389             :         /*
    1390             :          * Skip dropped columns
    1391             :          */
    1392         604 :         if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
    1393             :         {
    1394           0 :             i1++;
    1395           0 :             continue;
    1396             :         }
    1397         604 :         if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
    1398             :         {
    1399           0 :             i2++;
    1400           0 :             continue;
    1401             :         }
    1402         604 :         if (i1 >= ncolumns1 || i2 >= ncolumns2)
    1403             :             break;              /* we'll deal with mismatch below loop */
    1404             : 
    1405         600 :         att1 = TupleDescAttr(tupdesc1, i1);
    1406         600 :         att2 = TupleDescAttr(tupdesc2, i2);
    1407             : 
    1408             :         /*
    1409             :          * Have two matching columns, they must be same type
    1410             :          */
    1411         600 :         if (att1->atttypid != att2->atttypid)
    1412           4 :             ereport(ERROR,
    1413             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
    1414             :                      errmsg("cannot compare dissimilar column types %s and %s at record column %d",
    1415             :                             format_type_be(att1->atttypid),
    1416             :                             format_type_be(att2->atttypid),
    1417             :                             j + 1)));
    1418             : 
    1419             :         /*
    1420             :          * The same type should have the same length (or both should be
    1421             :          * variable).
    1422             :          */
    1423             :         Assert(att1->attlen == att2->attlen);
    1424             : 
    1425             :         /*
    1426             :          * We consider two NULLs equal; NULL > not-NULL.
    1427             :          */
    1428         596 :         if (!nulls1[i1] || !nulls2[i2])
    1429             :         {
    1430         596 :             int         cmpresult = 0;
    1431             : 
    1432         596 :             if (nulls1[i1])
    1433             :             {
    1434             :                 /* arg1 is greater than arg2 */
    1435           0 :                 result = 1;
    1436           0 :                 break;
    1437             :             }
    1438         596 :             if (nulls2[i2])
    1439             :             {
    1440             :                 /* arg1 is less than arg2 */
    1441           0 :                 result = -1;
    1442           0 :                 break;
    1443             :             }
    1444             : 
    1445             :             /* Compare the pair of elements */
    1446         596 :             if (att1->attlen == -1)
    1447             :             {
    1448             :                 Size        len1,
    1449             :                             len2;
    1450             :                 struct varlena *arg1val;
    1451             :                 struct varlena *arg2val;
    1452             : 
    1453         122 :                 len1 = toast_raw_datum_size(values1[i1]);
    1454         122 :                 len2 = toast_raw_datum_size(values2[i2]);
    1455         122 :                 arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
    1456         122 :                 arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
    1457             : 
    1458         244 :                 cmpresult = memcmp(VARDATA_ANY(arg1val),
    1459         122 :                                    VARDATA_ANY(arg2val),
    1460         122 :                                    Min(len1, len2) - VARHDRSZ);
    1461         122 :                 if ((cmpresult == 0) && (len1 != len2))
    1462           4 :                     cmpresult = (len1 < len2) ? -1 : 1;
    1463             : 
    1464         122 :                 if ((Pointer) arg1val != (Pointer) values1[i1])
    1465           0 :                     pfree(arg1val);
    1466         122 :                 if ((Pointer) arg2val != (Pointer) values2[i2])
    1467           0 :                     pfree(arg2val);
    1468             :             }
    1469         474 :             else if (att1->attbyval)
    1470             :             {
    1471         450 :                 if (values1[i1] != values2[i2])
    1472         284 :                     cmpresult = (values1[i1] < values2[i2]) ? -1 : 1;
    1473             :             }
    1474             :             else
    1475             :             {
    1476          48 :                 cmpresult = memcmp(DatumGetPointer(values1[i1]),
    1477          24 :                                    DatumGetPointer(values2[i2]),
    1478          24 :                                    att1->attlen);
    1479             :             }
    1480             : 
    1481         596 :             if (cmpresult < 0)
    1482             :             {
    1483             :                 /* arg1 is less than arg2 */
    1484         228 :                 result = -1;
    1485         228 :                 break;
    1486             :             }
    1487         368 :             else if (cmpresult > 0)
    1488             :             {
    1489             :                 /* arg1 is greater than arg2 */
    1490         120 :                 result = 1;
    1491         120 :                 break;
    1492             :             }
    1493             :         }
    1494             : 
    1495             :         /* equal, so continue to next column */
    1496         248 :         i1++, i2++, j++;
    1497             :     }
    1498             : 
    1499             :     /*
    1500             :      * If we didn't break out of the loop early, check for column count
    1501             :      * mismatch.  (We do not report such mismatch if we found unequal column
    1502             :      * values; is that a feature or a bug?)
    1503             :      */
    1504         434 :     if (result == 0)
    1505             :     {
    1506          86 :         if (i1 != ncolumns1 || i2 != ncolumns2)
    1507           4 :             ereport(ERROR,
    1508             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
    1509             :                      errmsg("cannot compare record types with different numbers of columns")));
    1510             :     }
    1511             : 
    1512         430 :     pfree(values1);
    1513         430 :     pfree(nulls1);
    1514         430 :     pfree(values2);
    1515         430 :     pfree(nulls2);
    1516         430 :     ReleaseTupleDesc(tupdesc1);
    1517         430 :     ReleaseTupleDesc(tupdesc2);
    1518             : 
    1519             :     /* Avoid leaking memory when handed toasted input. */
    1520         430 :     PG_FREE_IF_COPY(record1, 0);
    1521         430 :     PG_FREE_IF_COPY(record2, 1);
    1522             : 
    1523         430 :     return result;
    1524             : }
    1525             : 
    1526             : /*
    1527             :  * record_image_eq :
    1528             :  *        compares two records for identical contents, based on byte images
    1529             :  * result :
    1530             :  *        returns true if the records are identical, false otherwise.
    1531             :  *
    1532             :  * Note: we do not use record_image_cmp here, since we can avoid
    1533             :  * de-toasting for unequal lengths this way.
    1534             :  */
    1535             : Datum
    1536         154 : record_image_eq(PG_FUNCTION_ARGS)
    1537             : {
    1538         154 :     HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
    1539         154 :     HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
    1540         154 :     bool        result = true;
    1541             :     Oid         tupType1;
    1542             :     Oid         tupType2;
    1543             :     int32       tupTypmod1;
    1544             :     int32       tupTypmod2;
    1545             :     TupleDesc   tupdesc1;
    1546             :     TupleDesc   tupdesc2;
    1547             :     HeapTupleData tuple1;
    1548             :     HeapTupleData tuple2;
    1549             :     int         ncolumns1;
    1550             :     int         ncolumns2;
    1551             :     RecordCompareData *my_extra;
    1552             :     int         ncols;
    1553             :     Datum      *values1;
    1554             :     Datum      *values2;
    1555             :     bool       *nulls1;
    1556             :     bool       *nulls2;
    1557             :     int         i1;
    1558             :     int         i2;
    1559             :     int         j;
    1560             : 
    1561             :     /* Extract type info from the tuples */
    1562         154 :     tupType1 = HeapTupleHeaderGetTypeId(record1);
    1563         154 :     tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
    1564         154 :     tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
    1565         154 :     ncolumns1 = tupdesc1->natts;
    1566         154 :     tupType2 = HeapTupleHeaderGetTypeId(record2);
    1567         154 :     tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
    1568         154 :     tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
    1569         154 :     ncolumns2 = tupdesc2->natts;
    1570             : 
    1571             :     /* Build temporary HeapTuple control structures */
    1572         154 :     tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
    1573         154 :     ItemPointerSetInvalid(&(tuple1.t_self));
    1574         154 :     tuple1.t_tableOid = InvalidOid;
    1575         154 :     tuple1.t_data = record1;
    1576         154 :     tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
    1577         154 :     ItemPointerSetInvalid(&(tuple2.t_self));
    1578         154 :     tuple2.t_tableOid = InvalidOid;
    1579         154 :     tuple2.t_data = record2;
    1580             : 
    1581             :     /*
    1582             :      * We arrange to look up the needed comparison info just once per series
    1583             :      * of calls, assuming the record types don't change underneath us.
    1584             :      */
    1585         154 :     ncols = Max(ncolumns1, ncolumns2);
    1586         154 :     my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1587         238 :     if (my_extra == NULL ||
    1588          84 :         my_extra->ncolumns < ncols)
    1589             :     {
    1590         140 :         fcinfo->flinfo->fn_extra =
    1591          70 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
    1592             :                                offsetof(RecordCompareData, columns) +
    1593             :                                ncols * sizeof(ColumnCompareData));
    1594          70 :         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1595          70 :         my_extra->ncolumns = ncols;
    1596          70 :         my_extra->record1_type = InvalidOid;
    1597          70 :         my_extra->record1_typmod = 0;
    1598          70 :         my_extra->record2_type = InvalidOid;
    1599          70 :         my_extra->record2_typmod = 0;
    1600             :     }
    1601             : 
    1602         238 :     if (my_extra->record1_type != tupType1 ||
    1603         168 :         my_extra->record1_typmod != tupTypmod1 ||
    1604         168 :         my_extra->record2_type != tupType2 ||
    1605          84 :         my_extra->record2_typmod != tupTypmod2)
    1606             :     {
    1607          70 :         MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
    1608          70 :         my_extra->record1_type = tupType1;
    1609          70 :         my_extra->record1_typmod = tupTypmod1;
    1610          70 :         my_extra->record2_type = tupType2;
    1611          70 :         my_extra->record2_typmod = tupTypmod2;
    1612             :     }
    1613             : 
    1614             :     /* Break down the tuples into fields */
    1615         154 :     values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
    1616         154 :     nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
    1617         154 :     heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
    1618         154 :     values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
    1619         154 :     nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
    1620         154 :     heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
    1621             : 
    1622             :     /*
    1623             :      * Scan corresponding columns, allowing for dropped columns in different
    1624             :      * places in the two rows.  i1 and i2 are physical column indexes, j is
    1625             :      * the logical column index.
    1626             :      */
    1627         154 :     i1 = i2 = j = 0;
    1628         580 :     while (i1 < ncolumns1 || i2 < ncolumns2)
    1629             :     {
    1630             :         Form_pg_attribute att1;
    1631             :         Form_pg_attribute att2;
    1632             : 
    1633             :         /*
    1634             :          * Skip dropped columns
    1635             :          */
    1636         320 :         if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
    1637             :         {
    1638           0 :             i1++;
    1639           0 :             continue;
    1640             :         }
    1641         320 :         if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
    1642             :         {
    1643           0 :             i2++;
    1644           0 :             continue;
    1645             :         }
    1646         320 :         if (i1 >= ncolumns1 || i2 >= ncolumns2)
    1647             :             break;              /* we'll deal with mismatch below loop */
    1648             : 
    1649         316 :         att1 = TupleDescAttr(tupdesc1, i1);
    1650         316 :         att2 = TupleDescAttr(tupdesc2, i2);
    1651             : 
    1652             :         /*
    1653             :          * Have two matching columns, they must be same type
    1654             :          */
    1655         316 :         if (att1->atttypid != att2->atttypid)
    1656           4 :             ereport(ERROR,
    1657             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
    1658             :                      errmsg("cannot compare dissimilar column types %s and %s at record column %d",
    1659             :                             format_type_be(att1->atttypid),
    1660             :                             format_type_be(att2->atttypid),
    1661             :                             j + 1)));
    1662             : 
    1663             :         /*
    1664             :          * We consider two NULLs equal; NULL > not-NULL.
    1665             :          */
    1666         312 :         if (!nulls1[i1] || !nulls2[i2])
    1667             :         {
    1668         300 :             if (nulls1[i1] || nulls2[i2])
    1669             :             {
    1670           0 :                 result = false;
    1671           0 :                 break;
    1672             :             }
    1673             : 
    1674             :             /* Compare the pair of elements */
    1675         300 :             result = datum_image_eq(values1[i1], values2[i2], att1->attbyval, att2->attlen);
    1676         300 :             if (!result)
    1677          40 :                 break;
    1678             :         }
    1679             : 
    1680             :         /* equal, so continue to next column */
    1681         272 :         i1++, i2++, j++;
    1682             :     }
    1683             : 
    1684             :     /*
    1685             :      * If we didn't break out of the loop early, check for column count
    1686             :      * mismatch.  (We do not report such mismatch if we found unequal column
    1687             :      * values; is that a feature or a bug?)
    1688             :      */
    1689         150 :     if (result)
    1690             :     {
    1691         110 :         if (i1 != ncolumns1 || i2 != ncolumns2)
    1692           4 :             ereport(ERROR,
    1693             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
    1694             :                      errmsg("cannot compare record types with different numbers of columns")));
    1695             :     }
    1696             : 
    1697         146 :     pfree(values1);
    1698         146 :     pfree(nulls1);
    1699         146 :     pfree(values2);
    1700         146 :     pfree(nulls2);
    1701         146 :     ReleaseTupleDesc(tupdesc1);
    1702         146 :     ReleaseTupleDesc(tupdesc2);
    1703             : 
    1704             :     /* Avoid leaking memory when handed toasted input. */
    1705         146 :     PG_FREE_IF_COPY(record1, 0);
    1706         146 :     PG_FREE_IF_COPY(record2, 1);
    1707             : 
    1708         146 :     PG_RETURN_BOOL(result);
    1709             : }
    1710             : 
    1711             : Datum
    1712          32 : record_image_ne(PG_FUNCTION_ARGS)
    1713             : {
    1714          32 :     PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo)));
    1715             : }
    1716             : 
    1717             : Datum
    1718          48 : record_image_lt(PG_FUNCTION_ARGS)
    1719             : {
    1720          48 :     PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0);
    1721             : }
    1722             : 
    1723             : Datum
    1724          12 : record_image_gt(PG_FUNCTION_ARGS)
    1725             : {
    1726          12 :     PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0);
    1727             : }
    1728             : 
    1729             : Datum
    1730           8 : record_image_le(PG_FUNCTION_ARGS)
    1731             : {
    1732           8 :     PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0);
    1733             : }
    1734             : 
    1735             : Datum
    1736          12 : record_image_ge(PG_FUNCTION_ARGS)
    1737             : {
    1738          12 :     PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0);
    1739             : }
    1740             : 
    1741             : Datum
    1742         358 : btrecordimagecmp(PG_FUNCTION_ARGS)
    1743             : {
    1744         358 :     PG_RETURN_INT32(record_image_cmp(fcinfo));
    1745             : }

Generated by: LCOV version 1.13