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

Generated by: LCOV version 1.14