OSDN Git Service

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 96cbac9..08189d3 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));
 }