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

Generated by: LCOV version 2.0-1