LCOV - code coverage report
Current view: top level - src/backend/utils/adt - rowtypes.c (source / functions) Hit Total Coverage
Test: PostgreSQL Lines: 529 755 70.1 %
Date: 2017-09-29 13:40:31 Functions: 9 20 45.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.11