LCOV - code coverage report
Current view: top level - src/backend/utils/adt - rowtypes.c (source / functions) Hit Total Coverage
Test: PostgreSQL 16beta1 Lines: 660 841 78.5 %
Date: 2023-05-31 04:12:22 Functions: 20 22 90.9 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14