LCOV - code coverage report
Current view: top level - src/backend/utils/adt - rowtypes.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 660 835 79.0 %
Date: 2024-05-08 04:10:59 Functions: 20 22 90.9 %
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        1702 : record_in(PG_FUNCTION_ARGS)
      75             : {
      76        1702 :     char       *string = PG_GETARG_CSTRING(0);
      77        1702 :     Oid         tupType = PG_GETARG_OID(1);
      78        1702 :     int32       tupTypmod = PG_GETARG_INT32(2);
      79        1702 :     Node       *escontext = fcinfo->context;
      80             :     HeapTupleHeader result;
      81             :     TupleDesc   tupdesc;
      82             :     HeapTuple   tuple;
      83             :     RecordIOData *my_extra;
      84        1702 :     bool        needComma = false;
      85             :     int         ncolumns;
      86             :     int         i;
      87             :     char       *ptr;
      88             :     Datum      *values;
      89             :     bool       *nulls;
      90             :     StringInfoData buf;
      91             : 
      92        1702 :     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        1702 :     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        1702 :     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
     113        1702 :     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        1702 :     my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
     120        1702 :     if (my_extra == NULL ||
     121         986 :         my_extra->ncolumns != ncolumns)
     122             :     {
     123        1432 :         fcinfo->flinfo->fn_extra =
     124         716 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
     125             :                                offsetof(RecordIOData, columns) +
     126         716 :                                ncolumns * sizeof(ColumnIOData));
     127         716 :         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
     128         716 :         my_extra->record_type = InvalidOid;
     129         716 :         my_extra->record_typmod = 0;
     130             :     }
     131             : 
     132        1702 :     if (my_extra->record_type != tupType ||
     133         986 :         my_extra->record_typmod != tupTypmod)
     134             :     {
     135       17140 :         MemSet(my_extra, 0,
     136             :                offsetof(RecordIOData, columns) +
     137             :                ncolumns * sizeof(ColumnIOData));
     138         716 :         my_extra->record_type = tupType;
     139         716 :         my_extra->record_typmod = tupTypmod;
     140         716 :         my_extra->ncolumns = ncolumns;
     141             :     }
     142             : 
     143        1702 :     values = (Datum *) palloc(ncolumns * sizeof(Datum));
     144        1702 :     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        1702 :     ptr = string;
     151             :     /* Allow leading whitespace */
     152        1708 :     while (*ptr && isspace((unsigned char) *ptr))
     153           6 :         ptr++;
     154        1702 :     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        1692 :     initStringInfo(&buf);
     164             : 
     165        7986 :     for (i = 0; i < ncolumns; i++)
     166             :     {
     167        6332 :         Form_pg_attribute att = TupleDescAttr(tupdesc, i);
     168        6332 :         ColumnIOData *column_info = &my_extra->columns[i];
     169        6332 :         Oid         column_type = att->atttypid;
     170             :         char       *column_data;
     171             : 
     172             :         /* Ignore dropped columns in datatype, but fill with nulls */
     173        6332 :         if (att->attisdropped)
     174             :         {
     175         330 :             values[i] = (Datum) 0;
     176         330 :             nulls[i] = true;
     177         330 :             continue;
     178             :         }
     179             : 
     180        6002 :         if (needComma)
     181             :         {
     182             :             /* Skip comma that separates prior field from this one */
     183        4310 :             if (*ptr == ',')
     184        4304 :                 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        5996 :         if (*ptr == ',' || *ptr == ')')
     198             :         {
     199         470 :             column_data = NULL;
     200         470 :             nulls[i] = true;
     201             :         }
     202             :         else
     203             :         {
     204             :             /* Extract string for this column */
     205        5526 :             bool        inquote = false;
     206             : 
     207        5526 :             resetStringInfo(&buf);
     208       32114 :             while (inquote || !(*ptr == ',' || *ptr == ')'))
     209             :             {
     210       26594 :                 char        ch = *ptr++;
     211             : 
     212       26594 :                 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       26588 :                 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       26582 :                 else if (ch == '"')
     235             :                 {
     236        1766 :                     if (!inquote)
     237         852 :                         inquote = true;
     238         914 :                     else if (*ptr == '"')
     239             :                     {
     240             :                         /* doubled quote within quote sequence */
     241          62 :                         appendStringInfoChar(&buf, *ptr++);
     242             :                     }
     243             :                     else
     244         852 :                         inquote = false;
     245             :                 }
     246             :                 else
     247       24816 :                     appendStringInfoChar(&buf, ch);
     248             :             }
     249             : 
     250        5520 :             column_data = buf.data;
     251        5520 :             nulls[i] = false;
     252             :         }
     253             : 
     254             :         /*
     255             :          * Convert the column value
     256             :          */
     257        5990 :         if (column_info->column_type != column_type)
     258             :         {
     259        1808 :             getTypeInputInfo(column_type,
     260             :                              &column_info->typiofunc,
     261             :                              &column_info->typioparam);
     262        1808 :             fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
     263        1808 :                           fcinfo->flinfo->fn_mcxt);
     264        1808 :             column_info->column_type = column_type;
     265             :         }
     266             : 
     267        5982 :         if (!InputFunctionCallSafe(&column_info->proc,
     268             :                                    column_data,
     269             :                                    column_info->typioparam,
     270             :                                    att->atttypmod,
     271             :                                    escontext,
     272        5990 :                                    &values[i]))
     273          18 :             goto fail;
     274             : 
     275             :         /*
     276             :          * Prep for next column
     277             :          */
     278        5964 :         needComma = true;
     279             :     }
     280             : 
     281        1654 :     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        1666 :     while (*ptr && isspace((unsigned char) *ptr))
     291          18 :         ptr++;
     292        1648 :     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        1642 :     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        1642 :     result = (HeapTupleHeader) palloc(tuple->t_len);
     309        1642 :     memcpy(result, tuple->t_data, tuple->t_len);
     310             : 
     311        1642 :     heap_freetuple(tuple);
     312        1642 :     pfree(buf.data);
     313        1642 :     pfree(values);
     314        1642 :     pfree(nulls);
     315        1642 :     ReleaseTupleDesc(tupdesc);
     316             : 
     317        1642 :     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       34830 : record_out(PG_FUNCTION_ARGS)
     330             : {
     331       34830 :     HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
     332             :     Oid         tupType;
     333             :     int32       tupTypmod;
     334             :     TupleDesc   tupdesc;
     335             :     HeapTupleData tuple;
     336             :     RecordIOData *my_extra;
     337       34830 :     bool        needComma = false;
     338             :     int         ncolumns;
     339             :     int         i;
     340             :     Datum      *values;
     341             :     bool       *nulls;
     342             :     StringInfoData buf;
     343             : 
     344       34830 :     check_stack_depth();        /* recurses for record-type columns */
     345             : 
     346             :     /* Extract type info from the tuple itself */
     347       34830 :     tupType = HeapTupleHeaderGetTypeId(rec);
     348       34830 :     tupTypmod = HeapTupleHeaderGetTypMod(rec);
     349       34830 :     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
     350       34830 :     ncolumns = tupdesc->natts;
     351             : 
     352             :     /* Build a temporary HeapTuple control structure */
     353       34830 :     tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
     354       34830 :     ItemPointerSetInvalid(&(tuple.t_self));
     355       34830 :     tuple.t_tableOid = InvalidOid;
     356       34830 :     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       34830 :     my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
     363       34830 :     if (my_extra == NULL ||
     364       30116 :         my_extra->ncolumns != ncolumns)
     365             :     {
     366        9476 :         fcinfo->flinfo->fn_extra =
     367        4738 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
     368             :                                offsetof(RecordIOData, columns) +
     369        4738 :                                ncolumns * sizeof(ColumnIOData));
     370        4738 :         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
     371        4738 :         my_extra->record_type = InvalidOid;
     372        4738 :         my_extra->record_typmod = 0;
     373             :     }
     374             : 
     375       34830 :     if (my_extra->record_type != tupType ||
     376       30092 :         my_extra->record_typmod != tupTypmod)
     377             :     {
     378      106174 :         MemSet(my_extra, 0,
     379             :                offsetof(RecordIOData, columns) +
     380             :                ncolumns * sizeof(ColumnIOData));
     381        4778 :         my_extra->record_type = tupType;
     382        4778 :         my_extra->record_typmod = tupTypmod;
     383        4778 :         my_extra->ncolumns = ncolumns;
     384             :     }
     385             : 
     386       34830 :     values = (Datum *) palloc(ncolumns * sizeof(Datum));
     387       34830 :     nulls = (bool *) palloc(ncolumns * sizeof(bool));
     388             : 
     389             :     /* Break down the tuple into fields */
     390       34830 :     heap_deform_tuple(&tuple, tupdesc, values, nulls);
     391             : 
     392             :     /* And build the result string */
     393       34830 :     initStringInfo(&buf);
     394             : 
     395       34830 :     appendStringInfoChar(&buf, '(');
     396             : 
     397      211458 :     for (i = 0; i < ncolumns; i++)
     398             :     {
     399      176628 :         Form_pg_attribute att = TupleDescAttr(tupdesc, i);
     400      176628 :         ColumnIOData *column_info = &my_extra->columns[i];
     401      176628 :         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      176628 :         if (att->attisdropped)
     409         492 :             continue;
     410             : 
     411      176136 :         if (needComma)
     412      141312 :             appendStringInfoChar(&buf, ',');
     413      176136 :         needComma = true;
     414             : 
     415      176136 :         if (nulls[i])
     416             :         {
     417             :             /* emit nothing... */
     418        4098 :             continue;
     419             :         }
     420             : 
     421             :         /*
     422             :          * Convert the column value to text
     423             :          */
     424      172038 :         if (column_info->column_type != column_type)
     425             :         {
     426       10842 :             getTypeOutputInfo(column_type,
     427             :                               &column_info->typiofunc,
     428             :                               &column_info->typisvarlena);
     429       10842 :             fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
     430       10842 :                           fcinfo->flinfo->fn_mcxt);
     431       10842 :             column_info->column_type = column_type;
     432             :         }
     433             : 
     434      172038 :         attr = values[i];
     435      172038 :         value = OutputFunctionCall(&column_info->proc, attr);
     436             : 
     437             :         /* Detect whether we need double quotes for this value */
     438      172038 :         nq = (value[0] == '\0');    /* force quotes for empty string */
     439   108257812 :         for (tmp = value; *tmp; tmp++)
     440             :         {
     441   108137488 :             char        ch = *tmp;
     442             : 
     443   108137488 :             if (ch == '"' || ch == '\\' ||
     444   108136594 :                 ch == '(' || ch == ')' || ch == ',' ||
     445   108135636 :                 isspace((unsigned char) ch))
     446             :             {
     447       51714 :                 nq = true;
     448       51714 :                 break;
     449             :             }
     450             :         }
     451             : 
     452             :         /* And emit the string */
     453      172038 :         if (nq)
     454       51718 :             appendStringInfoCharMacro(&buf, '"');
     455   108780092 :         for (tmp = value; *tmp; tmp++)
     456             :         {
     457   108608054 :             char        ch = *tmp;
     458             : 
     459   108608054 :             if (ch == '"' || ch == '\\')
     460         996 :                 appendStringInfoCharMacro(&buf, ch);
     461   108608054 :             appendStringInfoCharMacro(&buf, ch);
     462             :         }
     463      172038 :         if (nq)
     464       51718 :             appendStringInfoCharMacro(&buf, '"');
     465             :     }
     466             : 
     467       34830 :     appendStringInfoChar(&buf, ')');
     468             : 
     469       34830 :     pfree(values);
     470       34830 :     pfree(nulls);
     471       34830 :     ReleaseTupleDesc(tupdesc);
     472             : 
     473       34830 :     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        4376 : record_cmp(FunctionCallInfo fcinfo)
     824             : {
     825        4376 :     HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
     826        4376 :     HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
     827        4376 :     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        4376 :     check_stack_depth();        /* recurses for record-type columns */
     849             : 
     850             :     /* Extract type info from the tuples */
     851        4376 :     tupType1 = HeapTupleHeaderGetTypeId(record1);
     852        4376 :     tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
     853        4376 :     tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
     854        4376 :     ncolumns1 = tupdesc1->natts;
     855        4376 :     tupType2 = HeapTupleHeaderGetTypeId(record2);
     856        4376 :     tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
     857        4376 :     tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
     858        4376 :     ncolumns2 = tupdesc2->natts;
     859             : 
     860             :     /* Build temporary HeapTuple control structures */
     861        4376 :     tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
     862        4376 :     ItemPointerSetInvalid(&(tuple1.t_self));
     863        4376 :     tuple1.t_tableOid = InvalidOid;
     864        4376 :     tuple1.t_data = record1;
     865        4376 :     tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
     866        4376 :     ItemPointerSetInvalid(&(tuple2.t_self));
     867        4376 :     tuple2.t_tableOid = InvalidOid;
     868        4376 :     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        4376 :     ncols = Max(ncolumns1, ncolumns2);
     875        4376 :     my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
     876        4376 :     if (my_extra == NULL ||
     877        3952 :         my_extra->ncolumns < ncols)
     878             :     {
     879         848 :         fcinfo->flinfo->fn_extra =
     880         424 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
     881         424 :                                offsetof(RecordCompareData, columns) +
     882             :                                ncols * sizeof(ColumnCompareData));
     883         424 :         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
     884         424 :         my_extra->ncolumns = ncols;
     885         424 :         my_extra->record1_type = InvalidOid;
     886         424 :         my_extra->record1_typmod = 0;
     887         424 :         my_extra->record2_type = InvalidOid;
     888         424 :         my_extra->record2_typmod = 0;
     889             :     }
     890             : 
     891        4376 :     if (my_extra->record1_type != tupType1 ||
     892        3952 :         my_extra->record1_typmod != tupTypmod1 ||
     893        3946 :         my_extra->record2_type != tupType2 ||
     894        3946 :         my_extra->record2_typmod != tupTypmod2)
     895             :     {
     896        1580 :         MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
     897         430 :         my_extra->record1_type = tupType1;
     898         430 :         my_extra->record1_typmod = tupTypmod1;
     899         430 :         my_extra->record2_type = tupType2;
     900         430 :         my_extra->record2_typmod = tupTypmod2;
     901             :     }
     902             : 
     903             :     /* Break down the tuples into fields */
     904        4376 :     values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
     905        4376 :     nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
     906        4376 :     heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
     907        4376 :     values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
     908        4376 :     nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
     909        4376 :     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        4376 :     i1 = i2 = j = 0;
     917        6706 :     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        5918 :         if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
     928             :         {
     929           0 :             i1++;
     930           0 :             continue;
     931             :         }
     932        5918 :         if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
     933             :         {
     934           0 :             i2++;
     935           0 :             continue;
     936             :         }
     937        5918 :         if (i1 >= ncolumns1 || i2 >= ncolumns2)
     938             :             break;              /* we'll deal with mismatch below loop */
     939             : 
     940        5912 :         att1 = TupleDescAttr(tupdesc1, i1);
     941        5912 :         att2 = TupleDescAttr(tupdesc2, i2);
     942             : 
     943             :         /*
     944             :          * Have two matching columns, they must be same type
     945             :          */
     946        5912 :         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        5906 :         collation = att1->attcollation;
     959        5906 :         if (collation != att2->attcollation)
     960           0 :             collation = InvalidOid;
     961             : 
     962             :         /*
     963             :          * Lookup the comparison function if not done already
     964             :          */
     965        5906 :         typentry = my_extra->columns[j].typentry;
     966        5906 :         if (typentry == NULL ||
     967        5148 :             typentry->type_id != att1->atttypid)
     968             :         {
     969         758 :             typentry = lookup_type_cache(att1->atttypid,
     970             :                                          TYPECACHE_CMP_PROC_FINFO);
     971         758 :             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         752 :             my_extra->columns[j].typentry = typentry;
     977             :         }
     978             : 
     979             :         /*
     980             :          * We consider two NULLs equal; NULL > not-NULL.
     981             :          */
     982        5900 :         if (!nulls1[i1] || !nulls2[i2])
     983             :         {
     984        5890 :             LOCAL_FCINFO(locfcinfo, 2);
     985             :             int32       cmpresult;
     986             : 
     987        5890 :             if (nulls1[i1])
     988             :             {
     989             :                 /* arg1 is greater than arg2 */
     990          12 :                 result = 1;
     991        3570 :                 break;
     992             :             }
     993        5878 :             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        5878 :             InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2,
    1002             :                                      collation, NULL, NULL);
    1003        5878 :             locfcinfo->args[0].value = values1[i1];
    1004        5878 :             locfcinfo->args[0].isnull = false;
    1005        5878 :             locfcinfo->args[1].value = values2[i2];
    1006        5878 :             locfcinfo->args[1].isnull = false;
    1007        5878 :             cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo));
    1008             : 
    1009             :             /* We don't expect comparison support functions to return null */
    1010             :             Assert(!locfcinfo->isnull);
    1011             : 
    1012        5878 :             if (cmpresult < 0)
    1013             :             {
    1014             :                 /* arg1 is less than arg2 */
    1015        1856 :                 result = -1;
    1016        1856 :                 break;
    1017             :             }
    1018        4022 :             else if (cmpresult > 0)
    1019             :             {
    1020             :                 /* arg1 is greater than arg2 */
    1021        1702 :                 result = 1;
    1022        1702 :                 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        4364 :     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        4358 :     pfree(values1);
    1044        4358 :     pfree(nulls1);
    1045        4358 :     pfree(values2);
    1046        4358 :     pfree(nulls2);
    1047        4358 :     ReleaseTupleDesc(tupdesc1);
    1048        4358 :     ReleaseTupleDesc(tupdesc2);
    1049             : 
    1050             :     /* Avoid leaking memory when handed toasted input. */
    1051        4358 :     PG_FREE_IF_COPY(record1, 0);
    1052        4358 :     PG_FREE_IF_COPY(record2, 1);
    1053             : 
    1054        4358 :     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        4274 : btrecordcmp(PG_FUNCTION_ARGS)
    1314             : {
    1315        4274 :     PG_RETURN_INT32(record_cmp(fcinfo));
    1316             : }
    1317             : 
    1318             : 
    1319             : /*
    1320             :  * record_image_cmp :
    1321             :  * Internal byte-oriented comparison function for records.
    1322             :  *
    1323             :  * Returns -1, 0 or 1
    1324             :  *
    1325             :  * Note: The normal concepts of "equality" do not apply here; different
    1326             :  * representation of values considered to be equal are not considered to be
    1327             :  * identical.  As an example, for the citext type 'A' and 'a' are equal, but
    1328             :  * they are not identical.
    1329             :  */
    1330             : static int
    1331         852 : record_image_cmp(FunctionCallInfo fcinfo)
    1332             : {
    1333         852 :     HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
    1334         852 :     HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
    1335         852 :     int         result = 0;
    1336             :     Oid         tupType1;
    1337             :     Oid         tupType2;
    1338             :     int32       tupTypmod1;
    1339             :     int32       tupTypmod2;
    1340             :     TupleDesc   tupdesc1;
    1341             :     TupleDesc   tupdesc2;
    1342             :     HeapTupleData tuple1;
    1343             :     HeapTupleData tuple2;
    1344             :     int         ncolumns1;
    1345             :     int         ncolumns2;
    1346             :     RecordCompareData *my_extra;
    1347             :     int         ncols;
    1348             :     Datum      *values1;
    1349             :     Datum      *values2;
    1350             :     bool       *nulls1;
    1351             :     bool       *nulls2;
    1352             :     int         i1;
    1353             :     int         i2;
    1354             :     int         j;
    1355             : 
    1356             :     /* Extract type info from the tuples */
    1357         852 :     tupType1 = HeapTupleHeaderGetTypeId(record1);
    1358         852 :     tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
    1359         852 :     tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
    1360         852 :     ncolumns1 = tupdesc1->natts;
    1361         852 :     tupType2 = HeapTupleHeaderGetTypeId(record2);
    1362         852 :     tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
    1363         852 :     tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
    1364         852 :     ncolumns2 = tupdesc2->natts;
    1365             : 
    1366             :     /* Build temporary HeapTuple control structures */
    1367         852 :     tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
    1368         852 :     ItemPointerSetInvalid(&(tuple1.t_self));
    1369         852 :     tuple1.t_tableOid = InvalidOid;
    1370         852 :     tuple1.t_data = record1;
    1371         852 :     tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
    1372         852 :     ItemPointerSetInvalid(&(tuple2.t_self));
    1373         852 :     tuple2.t_tableOid = InvalidOid;
    1374         852 :     tuple2.t_data = record2;
    1375             : 
    1376             :     /*
    1377             :      * We arrange to look up the needed comparison info just once per series
    1378             :      * of calls, assuming the record types don't change underneath us.
    1379             :      */
    1380         852 :     ncols = Max(ncolumns1, ncolumns2);
    1381         852 :     my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1382         852 :     if (my_extra == NULL ||
    1383         606 :         my_extra->ncolumns < ncols)
    1384             :     {
    1385         492 :         fcinfo->flinfo->fn_extra =
    1386         246 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
    1387         246 :                                offsetof(RecordCompareData, columns) +
    1388             :                                ncols * sizeof(ColumnCompareData));
    1389         246 :         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1390         246 :         my_extra->ncolumns = ncols;
    1391         246 :         my_extra->record1_type = InvalidOid;
    1392         246 :         my_extra->record1_typmod = 0;
    1393         246 :         my_extra->record2_type = InvalidOid;
    1394         246 :         my_extra->record2_typmod = 0;
    1395             :     }
    1396             : 
    1397         852 :     if (my_extra->record1_type != tupType1 ||
    1398         606 :         my_extra->record1_typmod != tupTypmod1 ||
    1399         606 :         my_extra->record2_type != tupType2 ||
    1400         606 :         my_extra->record2_typmod != tupTypmod2)
    1401             :     {
    1402         876 :         MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
    1403         246 :         my_extra->record1_type = tupType1;
    1404         246 :         my_extra->record1_typmod = tupTypmod1;
    1405         246 :         my_extra->record2_type = tupType2;
    1406         246 :         my_extra->record2_typmod = tupTypmod2;
    1407             :     }
    1408             : 
    1409             :     /* Break down the tuples into fields */
    1410         852 :     values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
    1411         852 :     nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
    1412         852 :     heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
    1413         852 :     values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
    1414         852 :     nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
    1415         852 :     heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
    1416             : 
    1417             :     /*
    1418             :      * Scan corresponding columns, allowing for dropped columns in different
    1419             :      * places in the two rows.  i1 and i2 are physical column indexes, j is
    1420             :      * the logical column index.
    1421             :      */
    1422         852 :     i1 = i2 = j = 0;
    1423        1666 :     while (i1 < ncolumns1 || i2 < ncolumns2)
    1424             :     {
    1425             :         Form_pg_attribute att1;
    1426             :         Form_pg_attribute att2;
    1427             : 
    1428             :         /*
    1429             :          * Skip dropped columns
    1430             :          */
    1431        1498 :         if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
    1432             :         {
    1433           0 :             i1++;
    1434           0 :             continue;
    1435             :         }
    1436        1498 :         if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
    1437             :         {
    1438           0 :             i2++;
    1439           0 :             continue;
    1440             :         }
    1441        1498 :         if (i1 >= ncolumns1 || i2 >= ncolumns2)
    1442             :             break;              /* we'll deal with mismatch below loop */
    1443             : 
    1444        1492 :         att1 = TupleDescAttr(tupdesc1, i1);
    1445        1492 :         att2 = TupleDescAttr(tupdesc2, i2);
    1446             : 
    1447             :         /*
    1448             :          * Have two matching columns, they must be same type
    1449             :          */
    1450        1492 :         if (att1->atttypid != att2->atttypid)
    1451           6 :             ereport(ERROR,
    1452             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
    1453             :                      errmsg("cannot compare dissimilar column types %s and %s at record column %d",
    1454             :                             format_type_be(att1->atttypid),
    1455             :                             format_type_be(att2->atttypid),
    1456             :                             j + 1)));
    1457             : 
    1458             :         /*
    1459             :          * The same type should have the same length (or both should be
    1460             :          * variable).
    1461             :          */
    1462             :         Assert(att1->attlen == att2->attlen);
    1463             : 
    1464             :         /*
    1465             :          * We consider two NULLs equal; NULL > not-NULL.
    1466             :          */
    1467        1486 :         if (!nulls1[i1] || !nulls2[i2])
    1468             :         {
    1469        1486 :             int         cmpresult = 0;
    1470             : 
    1471        1486 :             if (nulls1[i1])
    1472             :             {
    1473             :                 /* arg1 is greater than arg2 */
    1474           0 :                 result = 1;
    1475           0 :                 break;
    1476             :             }
    1477        1486 :             if (nulls2[i2])
    1478             :             {
    1479             :                 /* arg1 is less than arg2 */
    1480           0 :                 result = -1;
    1481           0 :                 break;
    1482             :             }
    1483             : 
    1484             :             /* Compare the pair of elements */
    1485        1486 :             if (att1->attbyval)
    1486             :             {
    1487         978 :                 if (values1[i1] != values2[i2])
    1488         512 :                     cmpresult = (values1[i1] < values2[i2]) ? -1 : 1;
    1489             :             }
    1490         508 :             else if (att1->attlen > 0)
    1491             :             {
    1492          36 :                 cmpresult = memcmp(DatumGetPointer(values1[i1]),
    1493          36 :                                    DatumGetPointer(values2[i2]),
    1494          36 :                                    att1->attlen);
    1495             :             }
    1496         472 :             else if (att1->attlen == -1)
    1497             :             {
    1498             :                 Size        len1,
    1499             :                             len2;
    1500             :                 struct varlena *arg1val;
    1501             :                 struct varlena *arg2val;
    1502             : 
    1503         472 :                 len1 = toast_raw_datum_size(values1[i1]);
    1504         472 :                 len2 = toast_raw_datum_size(values2[i2]);
    1505         472 :                 arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
    1506         472 :                 arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
    1507             : 
    1508         472 :                 cmpresult = memcmp(VARDATA_ANY(arg1val),
    1509         472 :                                    VARDATA_ANY(arg2val),
    1510         472 :                                    Min(len1, len2) - VARHDRSZ);
    1511         472 :                 if ((cmpresult == 0) && (len1 != len2))
    1512           6 :                     cmpresult = (len1 < len2) ? -1 : 1;
    1513             : 
    1514         472 :                 if ((Pointer) arg1val != (Pointer) values1[i1])
    1515           0 :                     pfree(arg1val);
    1516         472 :                 if ((Pointer) arg2val != (Pointer) values2[i2])
    1517           0 :                     pfree(arg2val);
    1518             :             }
    1519             :             else
    1520           0 :                 elog(ERROR, "unexpected attlen: %d", att1->attlen);
    1521             : 
    1522        1486 :             if (cmpresult < 0)
    1523             :             {
    1524             :                 /* arg1 is less than arg2 */
    1525         368 :                 result = -1;
    1526         368 :                 break;
    1527             :             }
    1528        1118 :             else if (cmpresult > 0)
    1529             :             {
    1530             :                 /* arg1 is greater than arg2 */
    1531         304 :                 result = 1;
    1532         304 :                 break;
    1533             :             }
    1534             :         }
    1535             : 
    1536             :         /* equal, so continue to next column */
    1537         814 :         i1++, i2++, j++;
    1538             :     }
    1539             : 
    1540             :     /*
    1541             :      * If we didn't break out of the loop early, check for column count
    1542             :      * mismatch.  (We do not report such mismatch if we found unequal column
    1543             :      * values; is that a feature or a bug?)
    1544             :      */
    1545         846 :     if (result == 0)
    1546             :     {
    1547         174 :         if (i1 != ncolumns1 || i2 != ncolumns2)
    1548           6 :             ereport(ERROR,
    1549             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
    1550             :                      errmsg("cannot compare record types with different numbers of columns")));
    1551             :     }
    1552             : 
    1553         840 :     pfree(values1);
    1554         840 :     pfree(nulls1);
    1555         840 :     pfree(values2);
    1556         840 :     pfree(nulls2);
    1557         840 :     ReleaseTupleDesc(tupdesc1);
    1558         840 :     ReleaseTupleDesc(tupdesc2);
    1559             : 
    1560             :     /* Avoid leaking memory when handed toasted input. */
    1561         840 :     PG_FREE_IF_COPY(record1, 0);
    1562         840 :     PG_FREE_IF_COPY(record2, 1);
    1563             : 
    1564         840 :     return result;
    1565             : }
    1566             : 
    1567             : /*
    1568             :  * record_image_eq :
    1569             :  *        compares two records for identical contents, based on byte images
    1570             :  * result :
    1571             :  *        returns true if the records are identical, false otherwise.
    1572             :  *
    1573             :  * Note: we do not use record_image_cmp here, since we can avoid
    1574             :  * de-toasting for unequal lengths this way.
    1575             :  */
    1576             : Datum
    1577         260 : record_image_eq(PG_FUNCTION_ARGS)
    1578             : {
    1579         260 :     HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
    1580         260 :     HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
    1581         260 :     bool        result = true;
    1582             :     Oid         tupType1;
    1583             :     Oid         tupType2;
    1584             :     int32       tupTypmod1;
    1585             :     int32       tupTypmod2;
    1586             :     TupleDesc   tupdesc1;
    1587             :     TupleDesc   tupdesc2;
    1588             :     HeapTupleData tuple1;
    1589             :     HeapTupleData tuple2;
    1590             :     int         ncolumns1;
    1591             :     int         ncolumns2;
    1592             :     RecordCompareData *my_extra;
    1593             :     int         ncols;
    1594             :     Datum      *values1;
    1595             :     Datum      *values2;
    1596             :     bool       *nulls1;
    1597             :     bool       *nulls2;
    1598             :     int         i1;
    1599             :     int         i2;
    1600             :     int         j;
    1601             : 
    1602             :     /* Extract type info from the tuples */
    1603         260 :     tupType1 = HeapTupleHeaderGetTypeId(record1);
    1604         260 :     tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
    1605         260 :     tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
    1606         260 :     ncolumns1 = tupdesc1->natts;
    1607         260 :     tupType2 = HeapTupleHeaderGetTypeId(record2);
    1608         260 :     tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
    1609         260 :     tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
    1610         260 :     ncolumns2 = tupdesc2->natts;
    1611             : 
    1612             :     /* Build temporary HeapTuple control structures */
    1613         260 :     tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
    1614         260 :     ItemPointerSetInvalid(&(tuple1.t_self));
    1615         260 :     tuple1.t_tableOid = InvalidOid;
    1616         260 :     tuple1.t_data = record1;
    1617         260 :     tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
    1618         260 :     ItemPointerSetInvalid(&(tuple2.t_self));
    1619         260 :     tuple2.t_tableOid = InvalidOid;
    1620         260 :     tuple2.t_data = record2;
    1621             : 
    1622             :     /*
    1623             :      * We arrange to look up the needed comparison info just once per series
    1624             :      * of calls, assuming the record types don't change underneath us.
    1625             :      */
    1626         260 :     ncols = Max(ncolumns1, ncolumns2);
    1627         260 :     my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1628         260 :     if (my_extra == NULL ||
    1629         130 :         my_extra->ncolumns < ncols)
    1630             :     {
    1631         260 :         fcinfo->flinfo->fn_extra =
    1632         130 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
    1633         130 :                                offsetof(RecordCompareData, columns) +
    1634             :                                ncols * sizeof(ColumnCompareData));
    1635         130 :         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1636         130 :         my_extra->ncolumns = ncols;
    1637         130 :         my_extra->record1_type = InvalidOid;
    1638         130 :         my_extra->record1_typmod = 0;
    1639         130 :         my_extra->record2_type = InvalidOid;
    1640         130 :         my_extra->record2_typmod = 0;
    1641             :     }
    1642             : 
    1643         260 :     if (my_extra->record1_type != tupType1 ||
    1644         130 :         my_extra->record1_typmod != tupTypmod1 ||
    1645         130 :         my_extra->record2_type != tupType2 ||
    1646         130 :         my_extra->record2_typmod != tupTypmod2)
    1647             :     {
    1648         424 :         MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
    1649         130 :         my_extra->record1_type = tupType1;
    1650         130 :         my_extra->record1_typmod = tupTypmod1;
    1651         130 :         my_extra->record2_type = tupType2;
    1652         130 :         my_extra->record2_typmod = tupTypmod2;
    1653             :     }
    1654             : 
    1655             :     /* Break down the tuples into fields */
    1656         260 :     values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
    1657         260 :     nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
    1658         260 :     heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
    1659         260 :     values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
    1660         260 :     nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
    1661         260 :     heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
    1662             : 
    1663             :     /*
    1664             :      * Scan corresponding columns, allowing for dropped columns in different
    1665             :      * places in the two rows.  i1 and i2 are physical column indexes, j is
    1666             :      * the logical column index.
    1667             :      */
    1668         260 :     i1 = i2 = j = 0;
    1669         988 :     while (i1 < ncolumns1 || i2 < ncolumns2)
    1670             :     {
    1671             :         Form_pg_attribute att1;
    1672             :         Form_pg_attribute att2;
    1673             : 
    1674             :         /*
    1675             :          * Skip dropped columns
    1676             :          */
    1677         798 :         if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
    1678             :         {
    1679           0 :             i1++;
    1680           0 :             continue;
    1681             :         }
    1682         798 :         if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
    1683             :         {
    1684           0 :             i2++;
    1685           0 :             continue;
    1686             :         }
    1687         798 :         if (i1 >= ncolumns1 || i2 >= ncolumns2)
    1688             :             break;              /* we'll deal with mismatch below loop */
    1689             : 
    1690         792 :         att1 = TupleDescAttr(tupdesc1, i1);
    1691         792 :         att2 = TupleDescAttr(tupdesc2, i2);
    1692             : 
    1693             :         /*
    1694             :          * Have two matching columns, they must be same type
    1695             :          */
    1696         792 :         if (att1->atttypid != att2->atttypid)
    1697           6 :             ereport(ERROR,
    1698             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
    1699             :                      errmsg("cannot compare dissimilar column types %s and %s at record column %d",
    1700             :                             format_type_be(att1->atttypid),
    1701             :                             format_type_be(att2->atttypid),
    1702             :                             j + 1)));
    1703             : 
    1704             :         /*
    1705             :          * We consider two NULLs equal; NULL > not-NULL.
    1706             :          */
    1707         786 :         if (!nulls1[i1] || !nulls2[i2])
    1708             :         {
    1709         774 :             if (nulls1[i1] || nulls2[i2])
    1710             :             {
    1711           0 :                 result = false;
    1712           0 :                 break;
    1713             :             }
    1714             : 
    1715             :             /* Compare the pair of elements */
    1716         774 :             result = datum_image_eq(values1[i1], values2[i2], att1->attbyval, att2->attlen);
    1717         774 :             if (!result)
    1718          58 :                 break;
    1719             :         }
    1720             : 
    1721             :         /* equal, so continue to next column */
    1722         728 :         i1++, i2++, j++;
    1723             :     }
    1724             : 
    1725             :     /*
    1726             :      * If we didn't break out of the loop early, check for column count
    1727             :      * mismatch.  (We do not report such mismatch if we found unequal column
    1728             :      * values; is that a feature or a bug?)
    1729             :      */
    1730         254 :     if (result)
    1731             :     {
    1732         196 :         if (i1 != ncolumns1 || i2 != ncolumns2)
    1733           6 :             ereport(ERROR,
    1734             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
    1735             :                      errmsg("cannot compare record types with different numbers of columns")));
    1736             :     }
    1737             : 
    1738         248 :     pfree(values1);
    1739         248 :     pfree(nulls1);
    1740         248 :     pfree(values2);
    1741         248 :     pfree(nulls2);
    1742         248 :     ReleaseTupleDesc(tupdesc1);
    1743         248 :     ReleaseTupleDesc(tupdesc2);
    1744             : 
    1745             :     /* Avoid leaking memory when handed toasted input. */
    1746         248 :     PG_FREE_IF_COPY(record1, 0);
    1747         248 :     PG_FREE_IF_COPY(record2, 1);
    1748             : 
    1749         248 :     PG_RETURN_BOOL(result);
    1750             : }
    1751             : 
    1752             : Datum
    1753          48 : record_image_ne(PG_FUNCTION_ARGS)
    1754             : {
    1755          48 :     PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo)));
    1756             : }
    1757             : 
    1758             : Datum
    1759          72 : record_image_lt(PG_FUNCTION_ARGS)
    1760             : {
    1761          72 :     PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0);
    1762             : }
    1763             : 
    1764             : Datum
    1765          18 : record_image_gt(PG_FUNCTION_ARGS)
    1766             : {
    1767          18 :     PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0);
    1768             : }
    1769             : 
    1770             : Datum
    1771          12 : record_image_le(PG_FUNCTION_ARGS)
    1772             : {
    1773          12 :     PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0);
    1774             : }
    1775             : 
    1776             : Datum
    1777          18 : record_image_ge(PG_FUNCTION_ARGS)
    1778             : {
    1779          18 :     PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0);
    1780             : }
    1781             : 
    1782             : Datum
    1783         732 : btrecordimagecmp(PG_FUNCTION_ARGS)
    1784             : {
    1785         732 :     PG_RETURN_INT32(record_image_cmp(fcinfo));
    1786             : }
    1787             : 
    1788             : 
    1789             : /*
    1790             :  * Row type hash functions
    1791             :  */
    1792             : 
    1793             : Datum
    1794         900 : hash_record(PG_FUNCTION_ARGS)
    1795             : {
    1796         900 :     HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0);
    1797         900 :     uint32      result = 0;
    1798             :     Oid         tupType;
    1799             :     int32       tupTypmod;
    1800             :     TupleDesc   tupdesc;
    1801             :     HeapTupleData tuple;
    1802             :     int         ncolumns;
    1803             :     RecordCompareData *my_extra;
    1804             :     Datum      *values;
    1805             :     bool       *nulls;
    1806             : 
    1807         900 :     check_stack_depth();        /* recurses for record-type columns */
    1808             : 
    1809             :     /* Extract type info from tuple */
    1810         900 :     tupType = HeapTupleHeaderGetTypeId(record);
    1811         900 :     tupTypmod = HeapTupleHeaderGetTypMod(record);
    1812         900 :     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
    1813         900 :     ncolumns = tupdesc->natts;
    1814             : 
    1815             :     /* Build temporary HeapTuple control structure */
    1816         900 :     tuple.t_len = HeapTupleHeaderGetDatumLength(record);
    1817         900 :     ItemPointerSetInvalid(&(tuple.t_self));
    1818         900 :     tuple.t_tableOid = InvalidOid;
    1819         900 :     tuple.t_data = record;
    1820             : 
    1821             :     /*
    1822             :      * We arrange to look up the needed hashing info just once per series of
    1823             :      * calls, assuming the record type doesn't change underneath us.
    1824             :      */
    1825         900 :     my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1826         900 :     if (my_extra == NULL ||
    1827         852 :         my_extra->ncolumns < ncolumns)
    1828             :     {
    1829          96 :         fcinfo->flinfo->fn_extra =
    1830          48 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
    1831          48 :                                offsetof(RecordCompareData, columns) +
    1832             :                                ncolumns * sizeof(ColumnCompareData));
    1833          48 :         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1834          48 :         my_extra->ncolumns = ncolumns;
    1835          48 :         my_extra->record1_type = InvalidOid;
    1836          48 :         my_extra->record1_typmod = 0;
    1837             :     }
    1838             : 
    1839         900 :     if (my_extra->record1_type != tupType ||
    1840         852 :         my_extra->record1_typmod != tupTypmod)
    1841             :     {
    1842         150 :         MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData));
    1843          48 :         my_extra->record1_type = tupType;
    1844          48 :         my_extra->record1_typmod = tupTypmod;
    1845             :     }
    1846             : 
    1847             :     /* Break down the tuple into fields */
    1848         900 :     values = (Datum *) palloc(ncolumns * sizeof(Datum));
    1849         900 :     nulls = (bool *) palloc(ncolumns * sizeof(bool));
    1850         900 :     heap_deform_tuple(&tuple, tupdesc, values, nulls);
    1851             : 
    1852        2730 :     for (int i = 0; i < ncolumns; i++)
    1853             :     {
    1854             :         Form_pg_attribute att;
    1855             :         TypeCacheEntry *typentry;
    1856             :         uint32      element_hash;
    1857             : 
    1858        1836 :         att = TupleDescAttr(tupdesc, i);
    1859             : 
    1860        1836 :         if (att->attisdropped)
    1861           0 :             continue;
    1862             : 
    1863             :         /*
    1864             :          * Lookup the hash function if not done already
    1865             :          */
    1866        1836 :         typentry = my_extra->columns[i].typentry;
    1867        1836 :         if (typentry == NULL ||
    1868        1740 :             typentry->type_id != att->atttypid)
    1869             :         {
    1870          96 :             typentry = lookup_type_cache(att->atttypid,
    1871             :                                          TYPECACHE_HASH_PROC_FINFO);
    1872          96 :             if (!OidIsValid(typentry->hash_proc_finfo.fn_oid))
    1873           6 :                 ereport(ERROR,
    1874             :                         (errcode(ERRCODE_UNDEFINED_FUNCTION),
    1875             :                          errmsg("could not identify a hash function for type %s",
    1876             :                                 format_type_be(typentry->type_id))));
    1877          90 :             my_extra->columns[i].typentry = typentry;
    1878             :         }
    1879             : 
    1880             :         /* Compute hash of element */
    1881        1830 :         if (nulls[i])
    1882             :         {
    1883           0 :             element_hash = 0;
    1884             :         }
    1885             :         else
    1886             :         {
    1887        1830 :             LOCAL_FCINFO(locfcinfo, 1);
    1888             : 
    1889        1830 :             InitFunctionCallInfoData(*locfcinfo, &typentry->hash_proc_finfo, 1,
    1890             :                                      att->attcollation, NULL, NULL);
    1891        1830 :             locfcinfo->args[0].value = values[i];
    1892        1830 :             locfcinfo->args[0].isnull = false;
    1893        1830 :             element_hash = DatumGetUInt32(FunctionCallInvoke(locfcinfo));
    1894             : 
    1895             :             /* We don't expect hash support functions to return null */
    1896             :             Assert(!locfcinfo->isnull);
    1897             :         }
    1898             : 
    1899             :         /* see hash_array() */
    1900        1830 :         result = (result << 5) - result + element_hash;
    1901             :     }
    1902             : 
    1903         894 :     pfree(values);
    1904         894 :     pfree(nulls);
    1905         894 :     ReleaseTupleDesc(tupdesc);
    1906             : 
    1907             :     /* Avoid leaking memory when handed toasted input. */
    1908         894 :     PG_FREE_IF_COPY(record, 0);
    1909             : 
    1910         894 :     PG_RETURN_UINT32(result);
    1911             : }
    1912             : 
    1913             : Datum
    1914          30 : hash_record_extended(PG_FUNCTION_ARGS)
    1915             : {
    1916          30 :     HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0);
    1917          30 :     uint64      seed = PG_GETARG_INT64(1);
    1918          30 :     uint64      result = 0;
    1919             :     Oid         tupType;
    1920             :     int32       tupTypmod;
    1921             :     TupleDesc   tupdesc;
    1922             :     HeapTupleData tuple;
    1923             :     int         ncolumns;
    1924             :     RecordCompareData *my_extra;
    1925             :     Datum      *values;
    1926             :     bool       *nulls;
    1927             : 
    1928          30 :     check_stack_depth();        /* recurses for record-type columns */
    1929             : 
    1930             :     /* Extract type info from tuple */
    1931          30 :     tupType = HeapTupleHeaderGetTypeId(record);
    1932          30 :     tupTypmod = HeapTupleHeaderGetTypMod(record);
    1933          30 :     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
    1934          30 :     ncolumns = tupdesc->natts;
    1935             : 
    1936             :     /* Build temporary HeapTuple control structure */
    1937          30 :     tuple.t_len = HeapTupleHeaderGetDatumLength(record);
    1938          30 :     ItemPointerSetInvalid(&(tuple.t_self));
    1939          30 :     tuple.t_tableOid = InvalidOid;
    1940          30 :     tuple.t_data = record;
    1941             : 
    1942             :     /*
    1943             :      * We arrange to look up the needed hashing info just once per series of
    1944             :      * calls, assuming the record type doesn't change underneath us.
    1945             :      */
    1946          30 :     my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1947          30 :     if (my_extra == NULL ||
    1948           0 :         my_extra->ncolumns < ncolumns)
    1949             :     {
    1950          60 :         fcinfo->flinfo->fn_extra =
    1951          30 :             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
    1952          30 :                                offsetof(RecordCompareData, columns) +
    1953             :                                ncolumns * sizeof(ColumnCompareData));
    1954          30 :         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
    1955          30 :         my_extra->ncolumns = ncolumns;
    1956          30 :         my_extra->record1_type = InvalidOid;
    1957          30 :         my_extra->record1_typmod = 0;
    1958             :     }
    1959             : 
    1960          30 :     if (my_extra->record1_type != tupType ||
    1961           0 :         my_extra->record1_typmod != tupTypmod)
    1962             :     {
    1963          90 :         MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData));
    1964          30 :         my_extra->record1_type = tupType;
    1965          30 :         my_extra->record1_typmod = tupTypmod;
    1966             :     }
    1967             : 
    1968             :     /* Break down the tuple into fields */
    1969          30 :     values = (Datum *) palloc(ncolumns * sizeof(Datum));
    1970          30 :     nulls = (bool *) palloc(ncolumns * sizeof(bool));
    1971          30 :     heap_deform_tuple(&tuple, tupdesc, values, nulls);
    1972             : 
    1973          78 :     for (int i = 0; i < ncolumns; i++)
    1974             :     {
    1975             :         Form_pg_attribute att;
    1976             :         TypeCacheEntry *typentry;
    1977             :         uint64      element_hash;
    1978             : 
    1979          54 :         att = TupleDescAttr(tupdesc, i);
    1980             : 
    1981          54 :         if (att->attisdropped)
    1982           0 :             continue;
    1983             : 
    1984             :         /*
    1985             :          * Lookup the hash function if not done already
    1986             :          */
    1987          54 :         typentry = my_extra->columns[i].typentry;
    1988          54 :         if (typentry == NULL ||
    1989           0 :             typentry->type_id != att->atttypid)
    1990             :         {
    1991          54 :             typentry = lookup_type_cache(att->atttypid,
    1992             :                                          TYPECACHE_HASH_EXTENDED_PROC_FINFO);
    1993          54 :             if (!OidIsValid(typentry->hash_extended_proc_finfo.fn_oid))
    1994           6 :                 ereport(ERROR,
    1995             :                         (errcode(ERRCODE_UNDEFINED_FUNCTION),
    1996             :                          errmsg("could not identify an extended hash function for type %s",
    1997             :                                 format_type_be(typentry->type_id))));
    1998          48 :             my_extra->columns[i].typentry = typentry;
    1999             :         }
    2000             : 
    2001             :         /* Compute hash of element */
    2002          48 :         if (nulls[i])
    2003             :         {
    2004           0 :             element_hash = 0;
    2005             :         }
    2006             :         else
    2007             :         {
    2008          48 :             LOCAL_FCINFO(locfcinfo, 2);
    2009             : 
    2010          48 :             InitFunctionCallInfoData(*locfcinfo, &typentry->hash_extended_proc_finfo, 2,
    2011             :                                      att->attcollation, NULL, NULL);
    2012          48 :             locfcinfo->args[0].value = values[i];
    2013          48 :             locfcinfo->args[0].isnull = false;
    2014          48 :             locfcinfo->args[1].value = Int64GetDatum(seed);
    2015          48 :             locfcinfo->args[0].isnull = false;
    2016          48 :             element_hash = DatumGetUInt64(FunctionCallInvoke(locfcinfo));
    2017             : 
    2018             :             /* We don't expect hash support functions to return null */
    2019             :             Assert(!locfcinfo->isnull);
    2020             :         }
    2021             : 
    2022             :         /* see hash_array_extended() */
    2023          48 :         result = (result << 5) - result + element_hash;
    2024             :     }
    2025             : 
    2026          24 :     pfree(values);
    2027          24 :     pfree(nulls);
    2028          24 :     ReleaseTupleDesc(tupdesc);
    2029             : 
    2030             :     /* Avoid leaking memory when handed toasted input. */
    2031          24 :     PG_FREE_IF_COPY(record, 0);
    2032             : 
    2033          24 :     PG_RETURN_UINT64(result);
    2034             : }

Generated by: LCOV version 1.14