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

Generated by: LCOV version 1.14