Add binary I/O support for composite types.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 6 Jun 2004 18:06:25 +0000 (18:06 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 6 Jun 2004 18:06:25 +0000 (18:06 +0000)
src/backend/utils/adt/rowtypes.c

index 96cbac992c57ff7336220ed69d757846410207b3..08189d3c143cfc2ac2e5b5cae0a5b85d0b1c8439 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.2 2004/06/06 04:50:28 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.3 2004/06/06 18:06:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -54,8 +54,9 @@ record_in(PG_FUNCTION_ARGS)
 {
    char       *string = PG_GETARG_CSTRING(0);
    Oid         tupType = PG_GETARG_OID(1);
-   HeapTuple   tuple;
+   int32       tupTypmod;
    TupleDesc   tupdesc;
+   HeapTuple   tuple;
    RecordIOData *my_extra;
    int         ncolumns;
    int         i;
@@ -74,7 +75,8 @@ record_in(PG_FUNCTION_ARGS)
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("input of anonymous composite types is not implemented")));
-   tupdesc = lookup_rowtype_tupdesc(tupType, -1);
+   tupTypmod = -1;             /* for all non-anonymous types */
+   tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
    ncolumns = tupdesc->natts;
 
    /*
@@ -91,17 +93,17 @@ record_in(PG_FUNCTION_ARGS)
                               + ncolumns * sizeof(ColumnIOData));
        my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
        my_extra->record_type = InvalidOid;
-       my_extra->record_typmod = -1;
+       my_extra->record_typmod = 0;
    }
 
    if (my_extra->record_type != tupType ||
-       my_extra->record_typmod != -1)
+       my_extra->record_typmod != tupTypmod)
    {
        MemSet(my_extra, 0,
               sizeof(RecordIOData) - sizeof(ColumnIOData)
               + ncolumns * sizeof(ColumnIOData));
        my_extra->record_type = tupType;
-       my_extra->record_typmod = -1;
+       my_extra->record_typmod = tupTypmod;
        my_extra->ncolumns = ncolumns;
    }
 
@@ -109,7 +111,8 @@ record_in(PG_FUNCTION_ARGS)
    nulls = (char *) palloc(ncolumns * sizeof(char));
 
    /*
-    * Scan the string.
+    * Scan the string.  We use "buf" to accumulate the de-quoted data
+    * for each column, which is then fed to the appropriate input converter.
     */
    ptr = string;
    /* Allow leading whitespace */
@@ -126,8 +129,9 @@ record_in(PG_FUNCTION_ARGS)
    for (i = 0; i < ncolumns; i++)
    {
        ColumnIOData *column_info = &my_extra->columns[i];
+       Oid         column_type = tupdesc->attrs[i]->atttypid;
 
-       /* Check for null */
+       /* Check for null: completely empty input means null */
        if (*ptr == ',' || *ptr == ')')
        {
            values[i] = (Datum) 0;
@@ -179,14 +183,14 @@ record_in(PG_FUNCTION_ARGS)
            /*
             * Convert the column value
             */
-           if (column_info->column_type != tupdesc->attrs[i]->atttypid)
+           if (column_info->column_type != column_type)
            {
-               getTypeInputInfo(tupdesc->attrs[i]->atttypid,
+               getTypeInputInfo(column_type,
                                 &column_info->typiofunc,
                                 &column_info->typioparam);
                fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
                              fcinfo->flinfo->fn_mcxt);
-               column_info->column_type = tupdesc->attrs[i]->atttypid;
+               column_info->column_type = column_type;
            }
 
            values[i] = FunctionCall3(&column_info->proc,
@@ -219,6 +223,7 @@ record_in(PG_FUNCTION_ARGS)
        }
    }
 
+   /* The check for ')' here is redundant except when ncolumns == 0 */
    if (*ptr++ != ')')
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
@@ -274,6 +279,7 @@ record_out(PG_FUNCTION_ARGS)
        tupTypmod = -1;
    tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
    ncolumns = tupdesc->natts;
+
    /* Build a temporary HeapTuple control structure */
    tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
    ItemPointerSetInvalid(&(tuple.t_self));
@@ -294,7 +300,7 @@ record_out(PG_FUNCTION_ARGS)
                               + ncolumns * sizeof(ColumnIOData));
        my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
        my_extra->record_type = InvalidOid;
-       my_extra->record_typmod = -1;
+       my_extra->record_typmod = 0;
    }
 
    if (my_extra->record_type != tupType ||
@@ -308,9 +314,10 @@ record_out(PG_FUNCTION_ARGS)
        my_extra->ncolumns = ncolumns;
    }
 
-   /* Break down the tuple into fields */
    values = (Datum *) palloc(ncolumns * sizeof(Datum));
    nulls = (char *) palloc(ncolumns * sizeof(char));
+
+   /* Break down the tuple into fields */
    heap_deformtuple(&tuple, tupdesc, values, nulls);
 
    /* And build the result string */
@@ -321,6 +328,7 @@ record_out(PG_FUNCTION_ARGS)
    for (i = 0; i < ncolumns; i++)
    {
        ColumnIOData *column_info = &my_extra->columns[i];
+       Oid         column_type = tupdesc->attrs[i]->atttypid;
        char    *value;
        char    *tmp;
        bool    nq;
@@ -335,19 +343,19 @@ record_out(PG_FUNCTION_ARGS)
        }
 
        /*
-        * Convert the column value
+        * Convert the column value to text
         */
-       if (column_info->column_type != tupdesc->attrs[i]->atttypid)
+       if (column_info->column_type != column_type)
        {
            bool    typIsVarlena;
 
-           getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
+           getTypeOutputInfo(column_type,
                              &column_info->typiofunc,
                              &column_info->typioparam,
                              &typIsVarlena);
            fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
                          fcinfo->flinfo->fn_mcxt);
-           column_info->column_type = tupdesc->attrs[i]->atttypid;
+           column_info->column_type = column_type;
        }
 
        value = DatumGetCString(FunctionCall3(&column_info->proc,
@@ -370,6 +378,7 @@ record_out(PG_FUNCTION_ARGS)
            }
        }
 
+       /* And emit the string */
        if (nq)
            appendStringInfoChar(&buf, '"');
        for (tmp = value; *tmp; tmp++)
@@ -377,7 +386,7 @@ record_out(PG_FUNCTION_ARGS)
            char        ch = *tmp;
 
            if (ch == '"' || ch == '\\')
-               appendStringInfoChar(&buf, '\\');
+               appendStringInfoChar(&buf, ch);
            appendStringInfoChar(&buf, ch);
        }
        if (nq)
@@ -398,12 +407,154 @@ record_out(PG_FUNCTION_ARGS)
 Datum
 record_recv(PG_FUNCTION_ARGS)
 {
-   /* Need to decide on external format before we can write this */
-   ereport(ERROR,
-           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-            errmsg("input of composite types not implemented yet")));
+   StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
+   Oid         tupType = PG_GETARG_OID(1);
+   int32       tupTypmod;
+   TupleDesc   tupdesc;
+   HeapTuple   tuple;
+   RecordIOData *my_extra;
+   int         ncolumns;
+   int         i;
+   Datum      *values;
+   char       *nulls;
 
-   PG_RETURN_VOID();           /* keep compiler quiet */
+   /*
+    * Use the passed type unless it's RECORD; we can't support input
+    * of anonymous types, mainly because there's no good way to figure
+    * out which anonymous type is wanted.  Note that for RECORD,
+    * what we'll probably actually get is RECORD's typelem, ie, zero.
+    */
+   if (tupType == InvalidOid || tupType == RECORDOID)
+       ereport(ERROR,
+               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                errmsg("input of anonymous composite types is not implemented")));
+   tupTypmod = -1;             /* for all non-anonymous types */
+   tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+   ncolumns = tupdesc->natts;
+
+   /*
+    * We arrange to look up the needed I/O info just once per series of
+    * calls, assuming the record type doesn't change underneath us.
+    */
+   my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+   if (my_extra == NULL ||
+       my_extra->ncolumns != ncolumns)
+   {
+       fcinfo->flinfo->fn_extra =
+           MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+                              sizeof(RecordIOData) - sizeof(ColumnIOData)
+                              + ncolumns * sizeof(ColumnIOData));
+       my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+       my_extra->record_type = InvalidOid;
+       my_extra->record_typmod = 0;
+   }
+
+   if (my_extra->record_type != tupType ||
+       my_extra->record_typmod != tupTypmod)
+   {
+       MemSet(my_extra, 0,
+              sizeof(RecordIOData) - sizeof(ColumnIOData)
+              + ncolumns * sizeof(ColumnIOData));
+       my_extra->record_type = tupType;
+       my_extra->record_typmod = tupTypmod;
+       my_extra->ncolumns = ncolumns;
+   }
+
+   values = (Datum *) palloc(ncolumns * sizeof(Datum));
+   nulls = (char *) palloc(ncolumns * sizeof(char));
+
+   /* Verify number of columns */
+   i = pq_getmsgint(buf, 4);
+   if (i != ncolumns)
+       ereport(ERROR,
+               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                errmsg("wrong number of columns: %d, expected %d",
+                       i, ncolumns)));
+
+   /* Process each column */
+   for (i = 0; i < ncolumns; i++)
+   {
+       ColumnIOData *column_info = &my_extra->columns[i];
+       Oid         column_type = tupdesc->attrs[i]->atttypid;
+       Oid         coltypoid;
+       int         itemlen;
+
+       /* Verify column datatype */
+       coltypoid = pq_getmsgint(buf, sizeof(Oid));
+       if (coltypoid != column_type)
+       ereport(ERROR,
+               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                errmsg("wrong data type: %u, expected %u",
+                       coltypoid, column_type)));
+
+       /* Get and check the item length */
+       itemlen = pq_getmsgint(buf, 4);
+       if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+                    errmsg("insufficient data left in message")));
+
+       if (itemlen == -1)
+       {
+           /* -1 length means NULL */
+           values[i] = (Datum) 0;
+           nulls[i] = 'n';
+       }
+       else
+       {
+           /*
+            * Rather than copying data around, we just set up a phony
+            * StringInfo pointing to the correct portion of the input buffer.
+            * We assume we can scribble on the input buffer so as to maintain
+            * the convention that StringInfos have a trailing null.
+            */
+           StringInfoData item_buf;
+           char        csave;
+
+           item_buf.data = &buf->data[buf->cursor];
+           item_buf.maxlen = itemlen + 1;
+           item_buf.len = itemlen;
+           item_buf.cursor = 0;
+
+           buf->cursor += itemlen;
+
+           csave = buf->data[buf->cursor];
+           buf->data[buf->cursor] = '\0';
+
+           /* Now call the column's receiveproc */
+           if (column_info->column_type != column_type)
+           {
+               getTypeBinaryInputInfo(column_type,
+                                      &column_info->typiofunc,
+                                      &column_info->typioparam);
+               fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+                             fcinfo->flinfo->fn_mcxt);
+               column_info->column_type = column_type;
+           }
+
+           values[i] = FunctionCall2(&column_info->proc,
+                                     PointerGetDatum(&item_buf),
+                                     ObjectIdGetDatum(column_info->typioparam));
+
+           nulls[i] = ' ';
+
+           /* Trouble if it didn't eat the whole buffer */
+           if (item_buf.cursor != itemlen)
+               ereport(ERROR,
+                       (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+                        errmsg("improper binary format in record column %d",
+                               i + 1)));
+
+           buf->data[buf->cursor] = csave;
+       }
+   }
+
+   tuple = heap_formtuple(tupdesc, values, nulls);
+
+   pfree(values);
+   pfree(nulls);
+
+   PG_RETURN_HEAPTUPLEHEADER(tuple->t_data);
 }
 
 /*
@@ -412,10 +563,122 @@ record_recv(PG_FUNCTION_ARGS)
 Datum
 record_send(PG_FUNCTION_ARGS)
 {
-   /* Need to decide on external format before we can write this */
-   ereport(ERROR,
-           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-            errmsg("output of composite types not implemented yet")));
+   HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
+   Oid         tupType = PG_GETARG_OID(1);
+   int32       tupTypmod;
+   TupleDesc   tupdesc;
+   HeapTupleData tuple;
+   RecordIOData *my_extra;
+   int         ncolumns;
+   int         i;
+   Datum      *values;
+   char       *nulls;
+   StringInfoData buf;
+
+   /*
+    * Use the passed type unless it's RECORD; in that case, we'd better
+    * get the type info out of the datum itself.  Note that for RECORD,
+    * what we'll probably actually get is RECORD's typelem, ie, zero.
+    */
+   if (tupType == InvalidOid || tupType == RECORDOID)
+   {
+       tupType = HeapTupleHeaderGetTypeId(rec);
+       tupTypmod = HeapTupleHeaderGetTypMod(rec);
+   }
+   else
+       tupTypmod = -1;
+   tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+   ncolumns = tupdesc->natts;
+
+   /* Build a temporary HeapTuple control structure */
+   tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+   ItemPointerSetInvalid(&(tuple.t_self));
+   tuple.t_tableOid = InvalidOid;
+   tuple.t_data = rec;
+
+   /*
+    * We arrange to look up the needed I/O info just once per series of
+    * calls, assuming the record type doesn't change underneath us.
+    */
+   my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+   if (my_extra == NULL ||
+       my_extra->ncolumns != ncolumns)
+   {
+       fcinfo->flinfo->fn_extra =
+           MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+                              sizeof(RecordIOData) - sizeof(ColumnIOData)
+                              + ncolumns * sizeof(ColumnIOData));
+       my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+       my_extra->record_type = InvalidOid;
+       my_extra->record_typmod = 0;
+   }
+
+   if (my_extra->record_type != tupType ||
+       my_extra->record_typmod != tupTypmod)
+   {
+       MemSet(my_extra, 0,
+              sizeof(RecordIOData) - sizeof(ColumnIOData)
+              + ncolumns * sizeof(ColumnIOData));
+       my_extra->record_type = tupType;
+       my_extra->record_typmod = tupTypmod;
+       my_extra->ncolumns = ncolumns;
+   }
+
+   values = (Datum *) palloc(ncolumns * sizeof(Datum));
+   nulls = (char *) palloc(ncolumns * sizeof(char));
+
+   /* Break down the tuple into fields */
+   heap_deformtuple(&tuple, tupdesc, values, nulls);
+
+   /* And build the result string */
+   pq_begintypsend(&buf);
+
+   pq_sendint(&buf, ncolumns, 4);
+
+   for (i = 0; i < ncolumns; i++)
+   {
+       ColumnIOData *column_info = &my_extra->columns[i];
+       Oid         column_type = tupdesc->attrs[i]->atttypid;
+       bytea      *outputbytes;
+
+       pq_sendint(&buf, column_type, sizeof(Oid));
+
+       if (nulls[i] == 'n')
+       {
+           /* emit -1 data length to signify a NULL */
+           pq_sendint(&buf, -1, 4);
+           continue;
+       }
+
+       /*
+        * Convert the column value to binary
+        */
+       if (column_info->column_type != column_type)
+       {
+           bool    typIsVarlena;
+
+           getTypeBinaryOutputInfo(column_type,
+                                   &column_info->typiofunc,
+                                   &column_info->typioparam,
+                                   &typIsVarlena);
+           fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+                         fcinfo->flinfo->fn_mcxt);
+           column_info->column_type = column_type;
+       }
+
+       outputbytes = DatumGetByteaP(FunctionCall2(&column_info->proc,
+                                                  values[i],
+                                                  ObjectIdGetDatum(column_info->typioparam)));
+
+       /* We assume the result will not have been toasted */
+       pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
+       pq_sendbytes(&buf, VARDATA(outputbytes),
+                    VARSIZE(outputbytes) - VARHDRSZ);
+       pfree(outputbytes);
+   }
+
+   pfree(values);
+   pfree(nulls);
 
-   PG_RETURN_VOID();           /* keep compiler quiet */
+   PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 }