Header And Logo

PostgreSQL
| The world's most advanced open source database.

rowtypes.c

Go to the documentation of this file.
00001 /*-------------------------------------------------------------------------
00002  *
00003  * rowtypes.c
00004  *    I/O and comparison functions for generic composite types.
00005  *
00006  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
00007  * Portions Copyright (c) 1994, Regents of the University of California
00008  *
00009  *
00010  * IDENTIFICATION
00011  *    src/backend/utils/adt/rowtypes.c
00012  *
00013  *-------------------------------------------------------------------------
00014  */
00015 #include "postgres.h"
00016 
00017 #include <ctype.h>
00018 
00019 #include "access/htup_details.h"
00020 #include "catalog/pg_type.h"
00021 #include "libpq/pqformat.h"
00022 #include "utils/builtins.h"
00023 #include "utils/lsyscache.h"
00024 #include "utils/typcache.h"
00025 
00026 
00027 /*
00028  * structure to cache metadata needed for record I/O
00029  */
00030 typedef struct ColumnIOData
00031 {
00032     Oid         column_type;
00033     Oid         typiofunc;
00034     Oid         typioparam;
00035     bool        typisvarlena;
00036     FmgrInfo    proc;
00037 } ColumnIOData;
00038 
00039 typedef struct RecordIOData
00040 {
00041     Oid         record_type;
00042     int32       record_typmod;
00043     int         ncolumns;
00044     ColumnIOData columns[1];    /* VARIABLE LENGTH ARRAY */
00045 } RecordIOData;
00046 
00047 /*
00048  * structure to cache metadata needed for record comparison
00049  */
00050 typedef struct ColumnCompareData
00051 {
00052     TypeCacheEntry *typentry;   /* has everything we need, actually */
00053 } ColumnCompareData;
00054 
00055 typedef struct RecordCompareData
00056 {
00057     int         ncolumns;       /* allocated length of columns[] */
00058     Oid         record1_type;
00059     int32       record1_typmod;
00060     Oid         record2_type;
00061     int32       record2_typmod;
00062     ColumnCompareData columns[1];       /* VARIABLE LENGTH ARRAY */
00063 } RecordCompareData;
00064 
00065 
00066 /*
00067  * record_in        - input routine for any composite type.
00068  */
00069 Datum
00070 record_in(PG_FUNCTION_ARGS)
00071 {
00072     char       *string = PG_GETARG_CSTRING(0);
00073     Oid         tupType = PG_GETARG_OID(1);
00074 
00075 #ifdef NOT_USED
00076     int32       typmod = PG_GETARG_INT32(2);
00077 #endif
00078     HeapTupleHeader result;
00079     int32       tupTypmod;
00080     TupleDesc   tupdesc;
00081     HeapTuple   tuple;
00082     RecordIOData *my_extra;
00083     bool        needComma = false;
00084     int         ncolumns;
00085     int         i;
00086     char       *ptr;
00087     Datum      *values;
00088     bool       *nulls;
00089     StringInfoData buf;
00090 
00091     /*
00092      * Use the passed type unless it's RECORD; we can't support input of
00093      * anonymous types, mainly because there's no good way to figure out which
00094      * anonymous type is wanted.  Note that for RECORD, what we'll probably
00095      * actually get is RECORD's typelem, ie, zero.
00096      */
00097     if (tupType == InvalidOid || tupType == RECORDOID)
00098         ereport(ERROR,
00099                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00100            errmsg("input of anonymous composite types is not implemented")));
00101     tupTypmod = -1;             /* for all non-anonymous types */
00102 
00103     /*
00104      * This comes from the composite type's pg_type.oid and stores system oids
00105      * in user tables, specifically DatumTupleFields. This oid must be
00106      * preserved by binary upgrades.
00107      */
00108     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
00109     ncolumns = tupdesc->natts;
00110 
00111     /*
00112      * We arrange to look up the needed I/O info just once per series of
00113      * calls, assuming the record type doesn't change underneath us.
00114      */
00115     my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
00116     if (my_extra == NULL ||
00117         my_extra->ncolumns != ncolumns)
00118     {
00119         fcinfo->flinfo->fn_extra =
00120             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
00121                                sizeof(RecordIOData) - sizeof(ColumnIOData)
00122                                + ncolumns * sizeof(ColumnIOData));
00123         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
00124         my_extra->record_type = InvalidOid;
00125         my_extra->record_typmod = 0;
00126     }
00127 
00128     if (my_extra->record_type != tupType ||
00129         my_extra->record_typmod != tupTypmod)
00130     {
00131         MemSet(my_extra, 0,
00132                sizeof(RecordIOData) - sizeof(ColumnIOData)
00133                + ncolumns * sizeof(ColumnIOData));
00134         my_extra->record_type = tupType;
00135         my_extra->record_typmod = tupTypmod;
00136         my_extra->ncolumns = ncolumns;
00137     }
00138 
00139     values = (Datum *) palloc(ncolumns * sizeof(Datum));
00140     nulls = (bool *) palloc(ncolumns * sizeof(bool));
00141 
00142     /*
00143      * Scan the string.  We use "buf" to accumulate the de-quoted data for
00144      * each column, which is then fed to the appropriate input converter.
00145      */
00146     ptr = string;
00147     /* Allow leading whitespace */
00148     while (*ptr && isspace((unsigned char) *ptr))
00149         ptr++;
00150     if (*ptr++ != '(')
00151         ereport(ERROR,
00152                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00153                  errmsg("malformed record literal: \"%s\"", string),
00154                  errdetail("Missing left parenthesis.")));
00155 
00156     initStringInfo(&buf);
00157 
00158     for (i = 0; i < ncolumns; i++)
00159     {
00160         ColumnIOData *column_info = &my_extra->columns[i];
00161         Oid         column_type = tupdesc->attrs[i]->atttypid;
00162         char       *column_data;
00163 
00164         /* Ignore dropped columns in datatype, but fill with nulls */
00165         if (tupdesc->attrs[i]->attisdropped)
00166         {
00167             values[i] = (Datum) 0;
00168             nulls[i] = true;
00169             continue;
00170         }
00171 
00172         if (needComma)
00173         {
00174             /* Skip comma that separates prior field from this one */
00175             if (*ptr == ',')
00176                 ptr++;
00177             else
00178                 /* *ptr must be ')' */
00179                 ereport(ERROR,
00180                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00181                          errmsg("malformed record literal: \"%s\"", string),
00182                          errdetail("Too few columns.")));
00183         }
00184 
00185         /* Check for null: completely empty input means null */
00186         if (*ptr == ',' || *ptr == ')')
00187         {
00188             column_data = NULL;
00189             nulls[i] = true;
00190         }
00191         else
00192         {
00193             /* Extract string for this column */
00194             bool        inquote = false;
00195 
00196             resetStringInfo(&buf);
00197             while (inquote || !(*ptr == ',' || *ptr == ')'))
00198             {
00199                 char        ch = *ptr++;
00200 
00201                 if (ch == '\0')
00202                     ereport(ERROR,
00203                             (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00204                              errmsg("malformed record literal: \"%s\"",
00205                                     string),
00206                              errdetail("Unexpected end of input.")));
00207                 if (ch == '\\')
00208                 {
00209                     if (*ptr == '\0')
00210                         ereport(ERROR,
00211                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00212                                  errmsg("malformed record literal: \"%s\"",
00213                                         string),
00214                                  errdetail("Unexpected end of input.")));
00215                     appendStringInfoChar(&buf, *ptr++);
00216                 }
00217                 else if (ch == '\"')
00218                 {
00219                     if (!inquote)
00220                         inquote = true;
00221                     else if (*ptr == '\"')
00222                     {
00223                         /* doubled quote within quote sequence */
00224                         appendStringInfoChar(&buf, *ptr++);
00225                     }
00226                     else
00227                         inquote = false;
00228                 }
00229                 else
00230                     appendStringInfoChar(&buf, ch);
00231             }
00232 
00233             column_data = buf.data;
00234             nulls[i] = false;
00235         }
00236 
00237         /*
00238          * Convert the column value
00239          */
00240         if (column_info->column_type != column_type)
00241         {
00242             getTypeInputInfo(column_type,
00243                              &column_info->typiofunc,
00244                              &column_info->typioparam);
00245             fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
00246                           fcinfo->flinfo->fn_mcxt);
00247             column_info->column_type = column_type;
00248         }
00249 
00250         values[i] = InputFunctionCall(&column_info->proc,
00251                                       column_data,
00252                                       column_info->typioparam,
00253                                       tupdesc->attrs[i]->atttypmod);
00254 
00255         /*
00256          * Prep for next column
00257          */
00258         needComma = true;
00259     }
00260 
00261     if (*ptr++ != ')')
00262         ereport(ERROR,
00263                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00264                  errmsg("malformed record literal: \"%s\"", string),
00265                  errdetail("Too many columns.")));
00266     /* Allow trailing whitespace */
00267     while (*ptr && isspace((unsigned char) *ptr))
00268         ptr++;
00269     if (*ptr)
00270         ereport(ERROR,
00271                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00272                  errmsg("malformed record literal: \"%s\"", string),
00273                  errdetail("Junk after right parenthesis.")));
00274 
00275     tuple = heap_form_tuple(tupdesc, values, nulls);
00276 
00277     /*
00278      * We cannot return tuple->t_data because heap_form_tuple allocates it as
00279      * part of a larger chunk, and our caller may expect to be able to pfree
00280      * our result.  So must copy the info into a new palloc chunk.
00281      */
00282     result = (HeapTupleHeader) palloc(tuple->t_len);
00283     memcpy(result, tuple->t_data, tuple->t_len);
00284 
00285     heap_freetuple(tuple);
00286     pfree(buf.data);
00287     pfree(values);
00288     pfree(nulls);
00289     ReleaseTupleDesc(tupdesc);
00290 
00291     PG_RETURN_HEAPTUPLEHEADER(result);
00292 }
00293 
00294 /*
00295  * record_out       - output routine for any composite type.
00296  */
00297 Datum
00298 record_out(PG_FUNCTION_ARGS)
00299 {
00300     HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
00301     Oid         tupType;
00302     int32       tupTypmod;
00303     TupleDesc   tupdesc;
00304     HeapTupleData tuple;
00305     RecordIOData *my_extra;
00306     bool        needComma = false;
00307     int         ncolumns;
00308     int         i;
00309     Datum      *values;
00310     bool       *nulls;
00311     StringInfoData buf;
00312 
00313     /* Extract type info from the tuple itself */
00314     tupType = HeapTupleHeaderGetTypeId(rec);
00315     tupTypmod = HeapTupleHeaderGetTypMod(rec);
00316     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
00317     ncolumns = tupdesc->natts;
00318 
00319     /* Build a temporary HeapTuple control structure */
00320     tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
00321     ItemPointerSetInvalid(&(tuple.t_self));
00322     tuple.t_tableOid = InvalidOid;
00323     tuple.t_data = rec;
00324 
00325     /*
00326      * We arrange to look up the needed I/O info just once per series of
00327      * calls, assuming the record type doesn't change underneath us.
00328      */
00329     my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
00330     if (my_extra == NULL ||
00331         my_extra->ncolumns != ncolumns)
00332     {
00333         fcinfo->flinfo->fn_extra =
00334             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
00335                                sizeof(RecordIOData) - sizeof(ColumnIOData)
00336                                + ncolumns * sizeof(ColumnIOData));
00337         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
00338         my_extra->record_type = InvalidOid;
00339         my_extra->record_typmod = 0;
00340     }
00341 
00342     if (my_extra->record_type != tupType ||
00343         my_extra->record_typmod != tupTypmod)
00344     {
00345         MemSet(my_extra, 0,
00346                sizeof(RecordIOData) - sizeof(ColumnIOData)
00347                + ncolumns * sizeof(ColumnIOData));
00348         my_extra->record_type = tupType;
00349         my_extra->record_typmod = tupTypmod;
00350         my_extra->ncolumns = ncolumns;
00351     }
00352 
00353     values = (Datum *) palloc(ncolumns * sizeof(Datum));
00354     nulls = (bool *) palloc(ncolumns * sizeof(bool));
00355 
00356     /* Break down the tuple into fields */
00357     heap_deform_tuple(&tuple, tupdesc, values, nulls);
00358 
00359     /* And build the result string */
00360     initStringInfo(&buf);
00361 
00362     appendStringInfoChar(&buf, '(');
00363 
00364     for (i = 0; i < ncolumns; i++)
00365     {
00366         ColumnIOData *column_info = &my_extra->columns[i];
00367         Oid         column_type = tupdesc->attrs[i]->atttypid;
00368         Datum       attr;
00369         char       *value;
00370         char       *tmp;
00371         bool        nq;
00372 
00373         /* Ignore dropped columns in datatype */
00374         if (tupdesc->attrs[i]->attisdropped)
00375             continue;
00376 
00377         if (needComma)
00378             appendStringInfoChar(&buf, ',');
00379         needComma = true;
00380 
00381         if (nulls[i])
00382         {
00383             /* emit nothing... */
00384             continue;
00385         }
00386 
00387         /*
00388          * Convert the column value to text
00389          */
00390         if (column_info->column_type != column_type)
00391         {
00392             getTypeOutputInfo(column_type,
00393                               &column_info->typiofunc,
00394                               &column_info->typisvarlena);
00395             fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
00396                           fcinfo->flinfo->fn_mcxt);
00397             column_info->column_type = column_type;
00398         }
00399 
00400         /*
00401          * If we have a toasted datum, forcibly detoast it here to avoid
00402          * memory leakage inside the type's output routine.
00403          */
00404         if (column_info->typisvarlena)
00405             attr = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
00406         else
00407             attr = values[i];
00408 
00409         value = OutputFunctionCall(&column_info->proc, attr);
00410 
00411         /* Detect whether we need double quotes for this value */
00412         nq = (value[0] == '\0');    /* force quotes for empty string */
00413         for (tmp = value; *tmp; tmp++)
00414         {
00415             char        ch = *tmp;
00416 
00417             if (ch == '"' || ch == '\\' ||
00418                 ch == '(' || ch == ')' || ch == ',' ||
00419                 isspace((unsigned char) ch))
00420             {
00421                 nq = true;
00422                 break;
00423             }
00424         }
00425 
00426         /* And emit the string */
00427         if (nq)
00428             appendStringInfoCharMacro(&buf, '"');
00429         for (tmp = value; *tmp; tmp++)
00430         {
00431             char        ch = *tmp;
00432 
00433             if (ch == '"' || ch == '\\')
00434                 appendStringInfoCharMacro(&buf, ch);
00435             appendStringInfoCharMacro(&buf, ch);
00436         }
00437         if (nq)
00438             appendStringInfoCharMacro(&buf, '"');
00439 
00440         pfree(value);
00441 
00442         /* Clean up detoasted copy, if any */
00443         if (DatumGetPointer(attr) != DatumGetPointer(values[i]))
00444             pfree(DatumGetPointer(attr));
00445     }
00446 
00447     appendStringInfoChar(&buf, ')');
00448 
00449     pfree(values);
00450     pfree(nulls);
00451     ReleaseTupleDesc(tupdesc);
00452 
00453     PG_RETURN_CSTRING(buf.data);
00454 }
00455 
00456 /*
00457  * record_recv      - binary input routine for any composite type.
00458  */
00459 Datum
00460 record_recv(PG_FUNCTION_ARGS)
00461 {
00462     StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
00463     Oid         tupType = PG_GETARG_OID(1);
00464 
00465 #ifdef NOT_USED
00466     int32       typmod = PG_GETARG_INT32(2);
00467 #endif
00468     HeapTupleHeader result;
00469     int32       tupTypmod;
00470     TupleDesc   tupdesc;
00471     HeapTuple   tuple;
00472     RecordIOData *my_extra;
00473     int         ncolumns;
00474     int         usercols;
00475     int         validcols;
00476     int         i;
00477     Datum      *values;
00478     bool       *nulls;
00479 
00480     /*
00481      * Use the passed type unless it's RECORD; we can't support input of
00482      * anonymous types, mainly because there's no good way to figure out which
00483      * anonymous type is wanted.  Note that for RECORD, what we'll probably
00484      * actually get is RECORD's typelem, ie, zero.
00485      */
00486     if (tupType == InvalidOid || tupType == RECORDOID)
00487         ereport(ERROR,
00488                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00489            errmsg("input of anonymous composite types is not implemented")));
00490     tupTypmod = -1;             /* for all non-anonymous types */
00491     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
00492     ncolumns = tupdesc->natts;
00493 
00494     /*
00495      * We arrange to look up the needed I/O info just once per series of
00496      * calls, assuming the record type doesn't change underneath us.
00497      */
00498     my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
00499     if (my_extra == NULL ||
00500         my_extra->ncolumns != ncolumns)
00501     {
00502         fcinfo->flinfo->fn_extra =
00503             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
00504                                sizeof(RecordIOData) - sizeof(ColumnIOData)
00505                                + ncolumns * sizeof(ColumnIOData));
00506         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
00507         my_extra->record_type = InvalidOid;
00508         my_extra->record_typmod = 0;
00509     }
00510 
00511     if (my_extra->record_type != tupType ||
00512         my_extra->record_typmod != tupTypmod)
00513     {
00514         MemSet(my_extra, 0,
00515                sizeof(RecordIOData) - sizeof(ColumnIOData)
00516                + ncolumns * sizeof(ColumnIOData));
00517         my_extra->record_type = tupType;
00518         my_extra->record_typmod = tupTypmod;
00519         my_extra->ncolumns = ncolumns;
00520     }
00521 
00522     values = (Datum *) palloc(ncolumns * sizeof(Datum));
00523     nulls = (bool *) palloc(ncolumns * sizeof(bool));
00524 
00525     /* Fetch number of columns user thinks it has */
00526     usercols = pq_getmsgint(buf, 4);
00527 
00528     /* Need to scan to count nondeleted columns */
00529     validcols = 0;
00530     for (i = 0; i < ncolumns; i++)
00531     {
00532         if (!tupdesc->attrs[i]->attisdropped)
00533             validcols++;
00534     }
00535     if (usercols != validcols)
00536         ereport(ERROR,
00537                 (errcode(ERRCODE_DATATYPE_MISMATCH),
00538                  errmsg("wrong number of columns: %d, expected %d",
00539                         usercols, validcols)));
00540 
00541     /* Process each column */
00542     for (i = 0; i < ncolumns; i++)
00543     {
00544         ColumnIOData *column_info = &my_extra->columns[i];
00545         Oid         column_type = tupdesc->attrs[i]->atttypid;
00546         Oid         coltypoid;
00547         int         itemlen;
00548         StringInfoData item_buf;
00549         StringInfo  bufptr;
00550         char        csave;
00551 
00552         /* Ignore dropped columns in datatype, but fill with nulls */
00553         if (tupdesc->attrs[i]->attisdropped)
00554         {
00555             values[i] = (Datum) 0;
00556             nulls[i] = true;
00557             continue;
00558         }
00559 
00560         /* Verify column datatype */
00561         coltypoid = pq_getmsgint(buf, sizeof(Oid));
00562         if (coltypoid != column_type)
00563             ereport(ERROR,
00564                     (errcode(ERRCODE_DATATYPE_MISMATCH),
00565                      errmsg("wrong data type: %u, expected %u",
00566                             coltypoid, column_type)));
00567 
00568         /* Get and check the item length */
00569         itemlen = pq_getmsgint(buf, 4);
00570         if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
00571             ereport(ERROR,
00572                     (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
00573                      errmsg("insufficient data left in message")));
00574 
00575         if (itemlen == -1)
00576         {
00577             /* -1 length means NULL */
00578             bufptr = NULL;
00579             nulls[i] = true;
00580             csave = 0;          /* keep compiler quiet */
00581         }
00582         else
00583         {
00584             /*
00585              * Rather than copying data around, we just set up a phony
00586              * StringInfo pointing to the correct portion of the input buffer.
00587              * We assume we can scribble on the input buffer so as to maintain
00588              * the convention that StringInfos have a trailing null.
00589              */
00590             item_buf.data = &buf->data[buf->cursor];
00591             item_buf.maxlen = itemlen + 1;
00592             item_buf.len = itemlen;
00593             item_buf.cursor = 0;
00594 
00595             buf->cursor += itemlen;
00596 
00597             csave = buf->data[buf->cursor];
00598             buf->data[buf->cursor] = '\0';
00599 
00600             bufptr = &item_buf;
00601             nulls[i] = false;
00602         }
00603 
00604         /* Now call the column's receiveproc */
00605         if (column_info->column_type != column_type)
00606         {
00607             getTypeBinaryInputInfo(column_type,
00608                                    &column_info->typiofunc,
00609                                    &column_info->typioparam);
00610             fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
00611                           fcinfo->flinfo->fn_mcxt);
00612             column_info->column_type = column_type;
00613         }
00614 
00615         values[i] = ReceiveFunctionCall(&column_info->proc,
00616                                         bufptr,
00617                                         column_info->typioparam,
00618                                         tupdesc->attrs[i]->atttypmod);
00619 
00620         if (bufptr)
00621         {
00622             /* Trouble if it didn't eat the whole buffer */
00623             if (item_buf.cursor != itemlen)
00624                 ereport(ERROR,
00625                         (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
00626                          errmsg("improper binary format in record column %d",
00627                                 i + 1)));
00628 
00629             buf->data[buf->cursor] = csave;
00630         }
00631     }
00632 
00633     tuple = heap_form_tuple(tupdesc, values, nulls);
00634 
00635     /*
00636      * We cannot return tuple->t_data because heap_form_tuple allocates it as
00637      * part of a larger chunk, and our caller may expect to be able to pfree
00638      * our result.  So must copy the info into a new palloc chunk.
00639      */
00640     result = (HeapTupleHeader) palloc(tuple->t_len);
00641     memcpy(result, tuple->t_data, tuple->t_len);
00642 
00643     heap_freetuple(tuple);
00644     pfree(values);
00645     pfree(nulls);
00646     ReleaseTupleDesc(tupdesc);
00647 
00648     PG_RETURN_HEAPTUPLEHEADER(result);
00649 }
00650 
00651 /*
00652  * record_send      - binary output routine for any composite type.
00653  */
00654 Datum
00655 record_send(PG_FUNCTION_ARGS)
00656 {
00657     HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
00658     Oid         tupType;
00659     int32       tupTypmod;
00660     TupleDesc   tupdesc;
00661     HeapTupleData tuple;
00662     RecordIOData *my_extra;
00663     int         ncolumns;
00664     int         validcols;
00665     int         i;
00666     Datum      *values;
00667     bool       *nulls;
00668     StringInfoData buf;
00669 
00670     /* Extract type info from the tuple itself */
00671     tupType = HeapTupleHeaderGetTypeId(rec);
00672     tupTypmod = HeapTupleHeaderGetTypMod(rec);
00673     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
00674     ncolumns = tupdesc->natts;
00675 
00676     /* Build a temporary HeapTuple control structure */
00677     tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
00678     ItemPointerSetInvalid(&(tuple.t_self));
00679     tuple.t_tableOid = InvalidOid;
00680     tuple.t_data = rec;
00681 
00682     /*
00683      * We arrange to look up the needed I/O info just once per series of
00684      * calls, assuming the record type doesn't change underneath us.
00685      */
00686     my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
00687     if (my_extra == NULL ||
00688         my_extra->ncolumns != ncolumns)
00689     {
00690         fcinfo->flinfo->fn_extra =
00691             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
00692                                sizeof(RecordIOData) - sizeof(ColumnIOData)
00693                                + ncolumns * sizeof(ColumnIOData));
00694         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
00695         my_extra->record_type = InvalidOid;
00696         my_extra->record_typmod = 0;
00697     }
00698 
00699     if (my_extra->record_type != tupType ||
00700         my_extra->record_typmod != tupTypmod)
00701     {
00702         MemSet(my_extra, 0,
00703                sizeof(RecordIOData) - sizeof(ColumnIOData)
00704                + ncolumns * sizeof(ColumnIOData));
00705         my_extra->record_type = tupType;
00706         my_extra->record_typmod = tupTypmod;
00707         my_extra->ncolumns = ncolumns;
00708     }
00709 
00710     values = (Datum *) palloc(ncolumns * sizeof(Datum));
00711     nulls = (bool *) palloc(ncolumns * sizeof(bool));
00712 
00713     /* Break down the tuple into fields */
00714     heap_deform_tuple(&tuple, tupdesc, values, nulls);
00715 
00716     /* And build the result string */
00717     pq_begintypsend(&buf);
00718 
00719     /* Need to scan to count nondeleted columns */
00720     validcols = 0;
00721     for (i = 0; i < ncolumns; i++)
00722     {
00723         if (!tupdesc->attrs[i]->attisdropped)
00724             validcols++;
00725     }
00726     pq_sendint(&buf, validcols, 4);
00727 
00728     for (i = 0; i < ncolumns; i++)
00729     {
00730         ColumnIOData *column_info = &my_extra->columns[i];
00731         Oid         column_type = tupdesc->attrs[i]->atttypid;
00732         Datum       attr;
00733         bytea      *outputbytes;
00734 
00735         /* Ignore dropped columns in datatype */
00736         if (tupdesc->attrs[i]->attisdropped)
00737             continue;
00738 
00739         pq_sendint(&buf, column_type, sizeof(Oid));
00740 
00741         if (nulls[i])
00742         {
00743             /* emit -1 data length to signify a NULL */
00744             pq_sendint(&buf, -1, 4);
00745             continue;
00746         }
00747 
00748         /*
00749          * Convert the column value to binary
00750          */
00751         if (column_info->column_type != column_type)
00752         {
00753             getTypeBinaryOutputInfo(column_type,
00754                                     &column_info->typiofunc,
00755                                     &column_info->typisvarlena);
00756             fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
00757                           fcinfo->flinfo->fn_mcxt);
00758             column_info->column_type = column_type;
00759         }
00760 
00761         /*
00762          * If we have a toasted datum, forcibly detoast it here to avoid
00763          * memory leakage inside the type's output routine.
00764          */
00765         if (column_info->typisvarlena)
00766             attr = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
00767         else
00768             attr = values[i];
00769 
00770         outputbytes = SendFunctionCall(&column_info->proc, attr);
00771 
00772         /* We assume the result will not have been toasted */
00773         pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
00774         pq_sendbytes(&buf, VARDATA(outputbytes),
00775                      VARSIZE(outputbytes) - VARHDRSZ);
00776 
00777         pfree(outputbytes);
00778 
00779         /* Clean up detoasted copy, if any */
00780         if (DatumGetPointer(attr) != DatumGetPointer(values[i]))
00781             pfree(DatumGetPointer(attr));
00782     }
00783 
00784     pfree(values);
00785     pfree(nulls);
00786     ReleaseTupleDesc(tupdesc);
00787 
00788     PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
00789 }
00790 
00791 
00792 /*
00793  * record_cmp()
00794  * Internal comparison function for records.
00795  *
00796  * Returns -1, 0 or 1
00797  *
00798  * Do not assume that the two inputs are exactly the same record type;
00799  * for instance we might be comparing an anonymous ROW() construct against a
00800  * named composite type.  We will compare as long as they have the same number
00801  * of non-dropped columns of the same types.
00802  */
00803 static int
00804 record_cmp(FunctionCallInfo fcinfo)
00805 {
00806     HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
00807     HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
00808     int         result = 0;
00809     Oid         tupType1;
00810     Oid         tupType2;
00811     int32       tupTypmod1;
00812     int32       tupTypmod2;
00813     TupleDesc   tupdesc1;
00814     TupleDesc   tupdesc2;
00815     HeapTupleData tuple1;
00816     HeapTupleData tuple2;
00817     int         ncolumns1;
00818     int         ncolumns2;
00819     RecordCompareData *my_extra;
00820     int         ncols;
00821     Datum      *values1;
00822     Datum      *values2;
00823     bool       *nulls1;
00824     bool       *nulls2;
00825     int         i1;
00826     int         i2;
00827     int         j;
00828 
00829     /* Extract type info from the tuples */
00830     tupType1 = HeapTupleHeaderGetTypeId(record1);
00831     tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
00832     tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
00833     ncolumns1 = tupdesc1->natts;
00834     tupType2 = HeapTupleHeaderGetTypeId(record2);
00835     tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
00836     tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
00837     ncolumns2 = tupdesc2->natts;
00838 
00839     /* Build temporary HeapTuple control structures */
00840     tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
00841     ItemPointerSetInvalid(&(tuple1.t_self));
00842     tuple1.t_tableOid = InvalidOid;
00843     tuple1.t_data = record1;
00844     tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
00845     ItemPointerSetInvalid(&(tuple2.t_self));
00846     tuple2.t_tableOid = InvalidOid;
00847     tuple2.t_data = record2;
00848 
00849     /*
00850      * We arrange to look up the needed comparison info just once per series
00851      * of calls, assuming the record types don't change underneath us.
00852      */
00853     ncols = Max(ncolumns1, ncolumns2);
00854     my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
00855     if (my_extra == NULL ||
00856         my_extra->ncolumns < ncols)
00857     {
00858         fcinfo->flinfo->fn_extra =
00859             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
00860                         sizeof(RecordCompareData) - sizeof(ColumnCompareData)
00861                                + ncols * sizeof(ColumnCompareData));
00862         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
00863         my_extra->ncolumns = ncols;
00864         my_extra->record1_type = InvalidOid;
00865         my_extra->record1_typmod = 0;
00866         my_extra->record2_type = InvalidOid;
00867         my_extra->record2_typmod = 0;
00868     }
00869 
00870     if (my_extra->record1_type != tupType1 ||
00871         my_extra->record1_typmod != tupTypmod1 ||
00872         my_extra->record2_type != tupType2 ||
00873         my_extra->record2_typmod != tupTypmod2)
00874     {
00875         MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
00876         my_extra->record1_type = tupType1;
00877         my_extra->record1_typmod = tupTypmod1;
00878         my_extra->record2_type = tupType2;
00879         my_extra->record2_typmod = tupTypmod2;
00880     }
00881 
00882     /* Break down the tuples into fields */
00883     values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
00884     nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
00885     heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
00886     values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
00887     nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
00888     heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
00889 
00890     /*
00891      * Scan corresponding columns, allowing for dropped columns in different
00892      * places in the two rows.  i1 and i2 are physical column indexes, j is
00893      * the logical column index.
00894      */
00895     i1 = i2 = j = 0;
00896     while (i1 < ncolumns1 || i2 < ncolumns2)
00897     {
00898         TypeCacheEntry *typentry;
00899         Oid         collation;
00900         FunctionCallInfoData locfcinfo;
00901         int32       cmpresult;
00902 
00903         /*
00904          * Skip dropped columns
00905          */
00906         if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
00907         {
00908             i1++;
00909             continue;
00910         }
00911         if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
00912         {
00913             i2++;
00914             continue;
00915         }
00916         if (i1 >= ncolumns1 || i2 >= ncolumns2)
00917             break;              /* we'll deal with mismatch below loop */
00918 
00919         /*
00920          * Have two matching columns, they must be same type
00921          */
00922         if (tupdesc1->attrs[i1]->atttypid !=
00923             tupdesc2->attrs[i2]->atttypid)
00924             ereport(ERROR,
00925                     (errcode(ERRCODE_DATATYPE_MISMATCH),
00926                      errmsg("cannot compare dissimilar column types %s and %s at record column %d",
00927                             format_type_be(tupdesc1->attrs[i1]->atttypid),
00928                             format_type_be(tupdesc2->attrs[i2]->atttypid),
00929                             j + 1)));
00930 
00931         /*
00932          * If they're not same collation, we don't complain here, but the
00933          * comparison function might.
00934          */
00935         collation = tupdesc1->attrs[i1]->attcollation;
00936         if (collation != tupdesc2->attrs[i2]->attcollation)
00937             collation = InvalidOid;
00938 
00939         /*
00940          * Lookup the comparison function if not done already
00941          */
00942         typentry = my_extra->columns[j].typentry;
00943         if (typentry == NULL ||
00944             typentry->type_id != tupdesc1->attrs[i1]->atttypid)
00945         {
00946             typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
00947                                          TYPECACHE_CMP_PROC_FINFO);
00948             if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
00949                 ereport(ERROR,
00950                         (errcode(ERRCODE_UNDEFINED_FUNCTION),
00951                 errmsg("could not identify a comparison function for type %s",
00952                        format_type_be(typentry->type_id))));
00953             my_extra->columns[j].typentry = typentry;
00954         }
00955 
00956         /*
00957          * We consider two NULLs equal; NULL > not-NULL.
00958          */
00959         if (!nulls1[i1] || !nulls2[i2])
00960         {
00961             if (nulls1[i1])
00962             {
00963                 /* arg1 is greater than arg2 */
00964                 result = 1;
00965                 break;
00966             }
00967             if (nulls2[i2])
00968             {
00969                 /* arg1 is less than arg2 */
00970                 result = -1;
00971                 break;
00972             }
00973 
00974             /* Compare the pair of elements */
00975             InitFunctionCallInfoData(locfcinfo, &typentry->cmp_proc_finfo, 2,
00976                                      collation, NULL, NULL);
00977             locfcinfo.arg[0] = values1[i1];
00978             locfcinfo.arg[1] = values2[i2];
00979             locfcinfo.argnull[0] = false;
00980             locfcinfo.argnull[1] = false;
00981             locfcinfo.isnull = false;
00982             cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
00983 
00984             if (cmpresult < 0)
00985             {
00986                 /* arg1 is less than arg2 */
00987                 result = -1;
00988                 break;
00989             }
00990             else if (cmpresult > 0)
00991             {
00992                 /* arg1 is greater than arg2 */
00993                 result = 1;
00994                 break;
00995             }
00996         }
00997 
00998         /* equal, so continue to next column */
00999         i1++, i2++, j++;
01000     }
01001 
01002     /*
01003      * If we didn't break out of the loop early, check for column count
01004      * mismatch.  (We do not report such mismatch if we found unequal column
01005      * values; is that a feature or a bug?)
01006      */
01007     if (result == 0)
01008     {
01009         if (i1 != ncolumns1 || i2 != ncolumns2)
01010             ereport(ERROR,
01011                     (errcode(ERRCODE_DATATYPE_MISMATCH),
01012                      errmsg("cannot compare record types with different numbers of columns")));
01013     }
01014 
01015     pfree(values1);
01016     pfree(nulls1);
01017     pfree(values2);
01018     pfree(nulls2);
01019     ReleaseTupleDesc(tupdesc1);
01020     ReleaseTupleDesc(tupdesc2);
01021 
01022     /* Avoid leaking memory when handed toasted input. */
01023     PG_FREE_IF_COPY(record1, 0);
01024     PG_FREE_IF_COPY(record2, 1);
01025 
01026     return result;
01027 }
01028 
01029 /*
01030  * record_eq :
01031  *        compares two records for equality
01032  * result :
01033  *        returns true if the records are equal, false otherwise.
01034  *
01035  * Note: we do not use record_cmp here, since equality may be meaningful in
01036  * datatypes that don't have a total ordering (and hence no btree support).
01037  */
01038 Datum
01039 record_eq(PG_FUNCTION_ARGS)
01040 {
01041     HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
01042     HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
01043     bool        result = true;
01044     Oid         tupType1;
01045     Oid         tupType2;
01046     int32       tupTypmod1;
01047     int32       tupTypmod2;
01048     TupleDesc   tupdesc1;
01049     TupleDesc   tupdesc2;
01050     HeapTupleData tuple1;
01051     HeapTupleData tuple2;
01052     int         ncolumns1;
01053     int         ncolumns2;
01054     RecordCompareData *my_extra;
01055     int         ncols;
01056     Datum      *values1;
01057     Datum      *values2;
01058     bool       *nulls1;
01059     bool       *nulls2;
01060     int         i1;
01061     int         i2;
01062     int         j;
01063 
01064     /* Extract type info from the tuples */
01065     tupType1 = HeapTupleHeaderGetTypeId(record1);
01066     tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
01067     tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
01068     ncolumns1 = tupdesc1->natts;
01069     tupType2 = HeapTupleHeaderGetTypeId(record2);
01070     tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
01071     tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
01072     ncolumns2 = tupdesc2->natts;
01073 
01074     /* Build temporary HeapTuple control structures */
01075     tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
01076     ItemPointerSetInvalid(&(tuple1.t_self));
01077     tuple1.t_tableOid = InvalidOid;
01078     tuple1.t_data = record1;
01079     tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
01080     ItemPointerSetInvalid(&(tuple2.t_self));
01081     tuple2.t_tableOid = InvalidOid;
01082     tuple2.t_data = record2;
01083 
01084     /*
01085      * We arrange to look up the needed comparison info just once per series
01086      * of calls, assuming the record types don't change underneath us.
01087      */
01088     ncols = Max(ncolumns1, ncolumns2);
01089     my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
01090     if (my_extra == NULL ||
01091         my_extra->ncolumns < ncols)
01092     {
01093         fcinfo->flinfo->fn_extra =
01094             MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
01095                         sizeof(RecordCompareData) - sizeof(ColumnCompareData)
01096                                + ncols * sizeof(ColumnCompareData));
01097         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
01098         my_extra->ncolumns = ncols;
01099         my_extra->record1_type = InvalidOid;
01100         my_extra->record1_typmod = 0;
01101         my_extra->record2_type = InvalidOid;
01102         my_extra->record2_typmod = 0;
01103     }
01104 
01105     if (my_extra->record1_type != tupType1 ||
01106         my_extra->record1_typmod != tupTypmod1 ||
01107         my_extra->record2_type != tupType2 ||
01108         my_extra->record2_typmod != tupTypmod2)
01109     {
01110         MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
01111         my_extra->record1_type = tupType1;
01112         my_extra->record1_typmod = tupTypmod1;
01113         my_extra->record2_type = tupType2;
01114         my_extra->record2_typmod = tupTypmod2;
01115     }
01116 
01117     /* Break down the tuples into fields */
01118     values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
01119     nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
01120     heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
01121     values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
01122     nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
01123     heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
01124 
01125     /*
01126      * Scan corresponding columns, allowing for dropped columns in different
01127      * places in the two rows.  i1 and i2 are physical column indexes, j is
01128      * the logical column index.
01129      */
01130     i1 = i2 = j = 0;
01131     while (i1 < ncolumns1 || i2 < ncolumns2)
01132     {
01133         TypeCacheEntry *typentry;
01134         Oid         collation;
01135         FunctionCallInfoData locfcinfo;
01136         bool        oprresult;
01137 
01138         /*
01139          * Skip dropped columns
01140          */
01141         if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
01142         {
01143             i1++;
01144             continue;
01145         }
01146         if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
01147         {
01148             i2++;
01149             continue;
01150         }
01151         if (i1 >= ncolumns1 || i2 >= ncolumns2)
01152             break;              /* we'll deal with mismatch below loop */
01153 
01154         /*
01155          * Have two matching columns, they must be same type
01156          */
01157         if (tupdesc1->attrs[i1]->atttypid !=
01158             tupdesc2->attrs[i2]->atttypid)
01159             ereport(ERROR,
01160                     (errcode(ERRCODE_DATATYPE_MISMATCH),
01161                      errmsg("cannot compare dissimilar column types %s and %s at record column %d",
01162                             format_type_be(tupdesc1->attrs[i1]->atttypid),
01163                             format_type_be(tupdesc2->attrs[i2]->atttypid),
01164                             j + 1)));
01165 
01166         /*
01167          * If they're not same collation, we don't complain here, but the
01168          * equality function might.
01169          */
01170         collation = tupdesc1->attrs[i1]->attcollation;
01171         if (collation != tupdesc2->attrs[i2]->attcollation)
01172             collation = InvalidOid;
01173 
01174         /*
01175          * Lookup the equality function if not done already
01176          */
01177         typentry = my_extra->columns[j].typentry;
01178         if (typentry == NULL ||
01179             typentry->type_id != tupdesc1->attrs[i1]->atttypid)
01180         {
01181             typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
01182                                          TYPECACHE_EQ_OPR_FINFO);
01183             if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
01184                 ereport(ERROR,
01185                         (errcode(ERRCODE_UNDEFINED_FUNCTION),
01186                 errmsg("could not identify an equality operator for type %s",
01187                        format_type_be(typentry->type_id))));
01188             my_extra->columns[j].typentry = typentry;
01189         }
01190 
01191         /*
01192          * We consider two NULLs equal; NULL > not-NULL.
01193          */
01194         if (!nulls1[i1] || !nulls2[i2])
01195         {
01196             if (nulls1[i1] || nulls2[i2])
01197             {
01198                 result = false;
01199                 break;
01200             }
01201 
01202             /* Compare the pair of elements */
01203             InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
01204                                      collation, NULL, NULL);
01205             locfcinfo.arg[0] = values1[i1];
01206             locfcinfo.arg[1] = values2[i2];
01207             locfcinfo.argnull[0] = false;
01208             locfcinfo.argnull[1] = false;
01209             locfcinfo.isnull = false;
01210             oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
01211             if (!oprresult)
01212             {
01213                 result = false;
01214                 break;
01215             }
01216         }
01217 
01218         /* equal, so continue to next column */
01219         i1++, i2++, j++;
01220     }
01221 
01222     /*
01223      * If we didn't break out of the loop early, check for column count
01224      * mismatch.  (We do not report such mismatch if we found unequal column
01225      * values; is that a feature or a bug?)
01226      */
01227     if (result)
01228     {
01229         if (i1 != ncolumns1 || i2 != ncolumns2)
01230             ereport(ERROR,
01231                     (errcode(ERRCODE_DATATYPE_MISMATCH),
01232                      errmsg("cannot compare record types with different numbers of columns")));
01233     }
01234 
01235     pfree(values1);
01236     pfree(nulls1);
01237     pfree(values2);
01238     pfree(nulls2);
01239     ReleaseTupleDesc(tupdesc1);
01240     ReleaseTupleDesc(tupdesc2);
01241 
01242     /* Avoid leaking memory when handed toasted input. */
01243     PG_FREE_IF_COPY(record1, 0);
01244     PG_FREE_IF_COPY(record2, 1);
01245 
01246     PG_RETURN_BOOL(result);
01247 }
01248 
01249 Datum
01250 record_ne(PG_FUNCTION_ARGS)
01251 {
01252     PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
01253 }
01254 
01255 Datum
01256 record_lt(PG_FUNCTION_ARGS)
01257 {
01258     PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
01259 }
01260 
01261 Datum
01262 record_gt(PG_FUNCTION_ARGS)
01263 {
01264     PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
01265 }
01266 
01267 Datum
01268 record_le(PG_FUNCTION_ARGS)
01269 {
01270     PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
01271 }
01272 
01273 Datum
01274 record_ge(PG_FUNCTION_ARGS)
01275 {
01276     PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
01277 }
01278 
01279 Datum
01280 btrecordcmp(PG_FUNCTION_ARGS)
01281 {
01282     PG_RETURN_INT32(record_cmp(fcinfo));
01283 }