LCOV - code coverage report
Current view: top level - src/backend/utils/adt - rowtypes.c (source / functions) Hit Total Coverage
Test: PostgreSQL 14devel Lines: 646 823 78.5 %
Date: 2021-01-26 03:06:49 Functions: 20 22 90.9 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13