Implement array_send/array_recv (binary I/O for arrays). This exposed
authorTom Lane
Fri, 9 May 2003 23:01:45 +0000 (23:01 +0000)
committerTom Lane
Fri, 9 May 2003 23:01:45 +0000 (23:01 +0000)
the folly of not passing element type to typsend/typreceive, so fix that.

doc/src/sgml/ref/create_type.sgml
src/backend/commands/typecmds.c
src/backend/utils/adt/arrayfuncs.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.h
src/test/regress/expected/type_sanity.out
src/test/regress/sql/type_sanity.sql

index 5fe4d2be4fc76660afd9ceb46d7a692e7c9fb767..2d2b92a85aabb07a3105dbabdb3093cb43f23aa0 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -117,15 +117,20 @@ CREATE TYPE typename (
    representation is in the machine's native byte order.)  The receive
    function should perform adequate checking to ensure that the value is
    valid.
-   The receive function should be declared as taking one argument of type
-   internal and returning a value of the data type itself.
-   (The argument actually supplied is a pointer to a StringInfo buffer
-   holding the received byte string.)  Similarly, the optional
+   The receive function may be declared as taking one argument of type
+   internal, or two arguments of types internal
+   and oid.  It must return a value of the data type itself.
+   (The first argument is a pointer to a StringInfo buffer
+   holding the received byte string; the optional second argument is the
+   element type in case this is an array type.)  Similarly, the optional
    send_function converts
    from the internal representation to the external binary representation.
    If this function is not supplied, the type cannot participate in binary
-   output.  The send function should be declared as taking one argument of the
-   new data type and returning type bytea.
+   output.  The send function may be
+   declared as taking one argument of the new data type,  or as taking
+   two arguments of which the second is type oid.
+   The second argument is again the array element type for array types.
+   The send function must return type bytea.
   
 
   
index f7bf3d3ee87e4d61743d78cc59a0b7d124967327..2036b9e714ad7da897f5a5ede1b4ca8b4f5fe854 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/commands/typecmds.c,v 1.35 2003/05/08 22:19:56 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/commands/typecmds.c,v 1.36 2003/05/09 23:01:45 tgl Exp $
  *
  * DESCRIPTION
  *   The "DefineFoo" routines take the parse tree and pick out the
@@ -911,7 +911,8 @@ findTypeReceiveFunction(List *procname, Oid typeOid)
    Oid         procOid;
 
    /*
-    * Receive functions take a single argument of type INTERNAL.
+    * Receive functions can take a single argument of type INTERNAL, or
+    * two arguments (internal, oid).
     */
    MemSet(argList, 0, FUNC_MAX_ARGS * sizeof(Oid));
 
@@ -921,6 +922,12 @@ findTypeReceiveFunction(List *procname, Oid typeOid)
    if (OidIsValid(procOid))
        return procOid;
 
+   argList[1] = OIDOID;
+
+   procOid = LookupFuncName(procname, 2, argList);
+   if (OidIsValid(procOid))
+       return procOid;
+
    func_error("TypeCreate", procname, 1, argList, NULL);
 
    return InvalidOid;          /* keep compiler quiet */
@@ -933,7 +940,8 @@ findTypeSendFunction(List *procname, Oid typeOid)
    Oid         procOid;
 
    /*
-    * Send functions take a single argument of the type.
+    * Send functions can take a single argument of the type, or two
+    * arguments (data value, element OID).
     */
    MemSet(argList, 0, FUNC_MAX_ARGS * sizeof(Oid));
 
@@ -943,6 +951,12 @@ findTypeSendFunction(List *procname, Oid typeOid)
    if (OidIsValid(procOid))
        return procOid;
 
+   argList[1] = OIDOID;
+
+   procOid = LookupFuncName(procname, 2, argList);
+   if (OidIsValid(procOid))
+       return procOid;
+
    func_error("TypeCreate", procname, 1, argList, NULL);
 
    return InvalidOid;          /* keep compiler quiet */
index f713fda7d57f16dbe159881a019424fc459d2ffc..b53c896e4314b0fb9fd6f5378239e581e677ed54 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.88 2003/05/08 22:19:56 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.89 2003/05/09 23:01:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,6 +19,7 @@
 #include "access/tupmacs.h"
 #include "catalog/catalog.h"
 #include "catalog/pg_type.h"
+#include "libpq/pqformat.h"
 #include "parser/parse_coerce.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 
 #define RETURN_NULL(type)  do { *isNull = true; return (type) 0; } while (0)
 
+/* I/O function selector for system_cache_lookup */
+typedef enum IOFuncSelector
+{
+   IOFunc_input,
+   IOFunc_output,
+   IOFunc_receive,
+   IOFunc_send
+} IOFuncSelector;
+
 
 static int ArrayCount(char *str, int *dim, char typdelim);
 static Datum *ReadArrayStr(char *arrayStr, int nitems, int ndim, int *dim,
@@ -76,12 +86,17 @@ static Datum *ReadArrayStr(char *arrayStr, int nitems, int ndim, int *dim,
             char typdelim,
             int typlen, bool typbyval, char typalign,
             int *nbytes);
+static Datum *ReadArrayBinary(StringInfo buf, int nitems,
+                             FmgrInfo *receiveproc, Oid typelem,
+                             int typlen, bool typbyval, char typalign,
+                             int *nbytes);
 static void CopyArrayEls(char *p, Datum *values, int nitems,
             int typlen, bool typbyval, char typalign,
             bool freedata);
-static void system_cache_lookup(Oid element_type, bool input, int *typlen,
-                   bool *typbyval, char *typdelim, Oid *typelem,
-                   Oid *proc, char *typalign);
+static void system_cache_lookup(Oid element_type, IOFuncSelector which_func,
+                               int *typlen, bool *typbyval,
+                               char *typdelim, Oid *typelem,
+                               Oid *proc, char *typalign);
 static Datum ArrayCast(char *value, bool byval, int len);
 static int ArrayCastAndSet(Datum src,
                int typlen, bool typbyval, char typalign,
@@ -141,7 +156,8 @@ array_in(PG_FUNCTION_ARGS)
    char        typalign;
 
    /* Get info about element type, including its input conversion proc */
-   system_cache_lookup(element_type, true, &typlen, &typbyval, &typdelim,
+   system_cache_lookup(element_type, IOFunc_input,
+                       &typlen, &typbyval, &typdelim,
                        &typelem, &typinput, &typalign);
    fmgr_info(typinput, &inputproc);
 
@@ -622,8 +638,9 @@ array_out(PG_FUNCTION_ARGS)
               *dim;
 
    element_type = ARR_ELEMTYPE(v);
-   system_cache_lookup(element_type, false, &typlen, &typbyval,
-                       &typdelim, &typelem, &typoutput, &typalign);
+   system_cache_lookup(element_type, IOFunc_output,
+                       &typlen, &typbyval, &typdelim,
+                       &typelem, &typoutput, &typalign);
    fmgr_info(typoutput, &outputproc);
 
    ndim = ARR_NDIM(v);
@@ -763,10 +780,178 @@ array_out(PG_FUNCTION_ARGS)
 Datum
 array_recv(PG_FUNCTION_ARGS)
 {
-   elog(ERROR, "array_recv: not implemented yet");
-   return 0;
+   StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
+   Oid         spec_element_type = PG_GETARG_OID(1);   /* type of an array
+                                                        * element */
+   Oid         element_type;
+   int         typlen;
+   bool        typbyval;
+   char        typdelim;
+   Oid         typreceive;
+   Oid         typelem;
+   FmgrInfo    receiveproc;
+   int         i,
+               nitems;
+   int32       nbytes;
+   Datum      *dataPtr;
+   ArrayType  *retval;
+   int         ndim,
+               flags,
+               dim[MAXDIM],
+               lBound[MAXDIM];
+   char        typalign;
+
+   /* Get the array header information */
+   ndim = pq_getmsgint(buf, 4);
+   if (ndim < 0 || ndim > MAXDIM)
+       elog(ERROR, "array_recv: invalid number of dimensions");
+   flags = pq_getmsgint(buf, 4);
+   if (flags != 0)
+       elog(ERROR, "array_recv: invalid array flags");
+   element_type = pq_getmsgint(buf, sizeof(Oid));
+   if (element_type != spec_element_type)
+   {
+       /* XXX Can we allow taking the input element type in any cases? */
+       elog(ERROR, "array_recv: wrong element type");
+   }
+
+   for (i = 0; i < ndim; i++)
+   {
+       dim[i] = pq_getmsgint(buf, 4);
+       lBound[i] = pq_getmsgint(buf, 4);
+   }
+   nitems = ArrayGetNItems(ndim, dim);
+
+   if (nitems == 0)
+   {
+       /* Return empty array */
+       retval = (ArrayType *) palloc0(sizeof(ArrayType));
+       retval->size = sizeof(ArrayType);
+       retval->elemtype = element_type;
+       PG_RETURN_ARRAYTYPE_P(retval);
+   }
+
+   /* Get info about element type, including its receive conversion proc */
+   system_cache_lookup(element_type, IOFunc_receive,
+                       &typlen, &typbyval, &typdelim,
+                       &typelem, &typreceive, &typalign);
+   if (!OidIsValid(typreceive))
+       elog(ERROR, "No binary input function available for type %s",
+            format_type_be(element_type));
+   fmgr_info(typreceive, &receiveproc);
+
+   dataPtr = ReadArrayBinary(buf, nitems, &receiveproc, typelem,
+                             typlen, typbyval, typalign,
+                             &nbytes);
+   nbytes += ARR_OVERHEAD(ndim);
+
+   retval = (ArrayType *) palloc0(nbytes);
+   retval->size = nbytes;
+   retval->ndim = ndim;
+   retval->elemtype = element_type;
+   memcpy((char *) ARR_DIMS(retval), (char *) dim,
+          ndim * sizeof(int));
+   memcpy((char *) ARR_LBOUND(retval), (char *) lBound,
+          ndim * sizeof(int));
+
+   CopyArrayEls(ARR_DATA_PTR(retval), dataPtr, nitems,
+                typlen, typbyval, typalign, true);
+   pfree(dataPtr);
+
+   PG_RETURN_ARRAYTYPE_P(retval);
+}
+
+/*---------------------------------------------------------------------------
+ * ReadArrayBinary:
+ *  collect the data elements of an array being read in binary style.
+ * result :
+ *  returns a palloc'd array of Datum representations of the array elements.
+ *  If element type is pass-by-ref, the Datums point to palloc'd values.
+ *  *nbytes is set to the amount of data space needed for the array,
+ *  including alignment padding but not including array header overhead.
+ *---------------------------------------------------------------------------
+ */
+static Datum *
+ReadArrayBinary(StringInfo buf,
+               int nitems,
+               FmgrInfo *receiveproc,
+               Oid typelem,
+               int typlen,
+               bool typbyval,
+               char typalign,
+               int *nbytes)
+{
+   Datum      *values;
+   int         i;
+
+   values = (Datum *) palloc(nitems * sizeof(Datum));
+
+   for (i = 0; i < nitems; i++)
+   {
+       int     itemlen;
+       StringInfoData elem_buf;
+       char    csave;
+
+       /* Get and check the item length */
+       itemlen = pq_getmsgint(buf, 4);
+       if (itemlen < 0 || itemlen > (buf->len - buf->cursor))
+           elog(ERROR, "insufficient data left in message");
+
+       /*
+        * 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.
+        */
+       elem_buf.data = &buf->data[buf->cursor];
+       elem_buf.maxlen = itemlen + 1;
+       elem_buf.len = itemlen;
+       elem_buf.cursor = 0;
+
+       buf->cursor += itemlen;
+
+       csave = buf->data[buf->cursor];
+       buf->data[buf->cursor] = '\0';
+
+       /* Now call the element's receiveproc */
+       values[i] = FunctionCall2(receiveproc,
+                                 PointerGetDatum(&elem_buf),
+                                 ObjectIdGetDatum(typelem));
+
+       /* Trouble if it didn't eat the whole buffer */
+       if (elem_buf.cursor != itemlen)
+           elog(ERROR, "Improper binary format in array element %d",
+                i + 1);
+
+       buf->data[buf->cursor] = csave;
+   }
+
+   /*
+    * Compute total data space needed
+    */
+   if (typlen > 0)
+   {
+       *nbytes = nitems * att_align(typlen, typalign);
+   }
+   else
+   {
+       Assert(!typbyval);
+       *nbytes = 0;
+       for (i = 0; i < nitems; i++)
+       {
+           /* let's just make sure data is not toasted */
+           if (typlen == -1)
+               values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
+           *nbytes = att_addlength(*nbytes, typlen, values[i]);
+           *nbytes = att_align(*nbytes, typalign);
+       }
+   }
+
+   return values;
 }
 
+
 /*-------------------------------------------------------------------------
  * array_send :
  *        takes the internal representation of an array and returns a bytea
@@ -776,8 +961,70 @@ array_recv(PG_FUNCTION_ARGS)
 Datum
 array_send(PG_FUNCTION_ARGS)
 {
-   elog(ERROR, "array_send: not implemented yet");
-   return 0;
+   ArrayType  *v = PG_GETARG_ARRAYTYPE_P(0);
+   Oid         element_type;
+   int         typlen;
+   bool        typbyval;
+   char        typdelim;
+   Oid         typsend,
+               typelem;
+   FmgrInfo    sendproc;
+   char        typalign;
+   char       *p;
+   int         nitems,
+               i;
+   int         ndim,
+              *dim;
+   StringInfoData buf;
+
+   /* Get information about the element type and the array dimensions */
+   element_type = ARR_ELEMTYPE(v);
+   system_cache_lookup(element_type, IOFunc_send, &typlen, &typbyval,
+                       &typdelim, &typelem, &typsend, &typalign);
+   if (!OidIsValid(typsend))
+       elog(ERROR, "No binary output function available for type %s",
+            format_type_be(element_type));
+   fmgr_info(typsend, &sendproc);
+
+   ndim = ARR_NDIM(v);
+   dim = ARR_DIMS(v);
+   nitems = ArrayGetNItems(ndim, dim);
+
+   pq_begintypsend(&buf);
+
+   /* Send the array header information */
+   pq_sendint(&buf, ndim, 4);
+   pq_sendint(&buf, v->flags, 4);
+   pq_sendint(&buf, element_type, sizeof(Oid));
+   for (i = 0; i < ndim; i++)
+   {
+       pq_sendint(&buf, ARR_DIMS(v)[i], 4);
+       pq_sendint(&buf, ARR_LBOUND(v)[i], 4);
+   }
+
+   /* Send the array elements using the element's own sendproc */
+   p = ARR_DATA_PTR(v);
+   for (i = 0; i < nitems; i++)
+   {
+       Datum       itemvalue;
+       bytea      *outputbytes;
+
+       itemvalue = fetch_att(p, typbyval, typlen);
+
+       outputbytes = DatumGetByteaP(FunctionCall2(&sendproc,
+                                                  itemvalue,
+                                                  ObjectIdGetDatum(typelem)));
+       /* 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);
+
+       p = att_addlength(p, typlen, PointerGetDatum(p));
+       p = (char *) att_align(p, typalign);
+   }
+
+   PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 }
 
 /*-------------------------------------------------------------------------
@@ -1583,9 +1830,9 @@ array_map(FunctionCallInfo fcinfo, Oid inpType, Oid retType)
        PG_RETURN_ARRAYTYPE_P(v);
 
    /* Lookup source and result types. Unneeded variables are reused. */
-   system_cache_lookup(inpType, false, &inp_typlen, &inp_typbyval,
+   system_cache_lookup(inpType, IOFunc_input, &inp_typlen, &inp_typbyval,
                        &typdelim, &typelem, &proc, &inp_typalign);
-   system_cache_lookup(retType, false, &typlen, &typbyval,
+   system_cache_lookup(retType, IOFunc_input, &typlen, &typbyval,
                        &typdelim, &typelem, &proc, &typalign);
 
    /* Allocate temporary array for new values */
@@ -1832,7 +2079,7 @@ array_eq(PG_FUNCTION_ARGS)
 
 static void
 system_cache_lookup(Oid element_type,
-                   bool input,
+                   IOFuncSelector which_func,
                    int *typlen,
                    bool *typbyval,
                    char *typdelim,
@@ -1855,10 +2102,21 @@ system_cache_lookup(Oid element_type,
    *typdelim = typeStruct->typdelim;
    *typelem = typeStruct->typelem;
    *typalign = typeStruct->typalign;
-   if (input)
-       *proc = typeStruct->typinput;
-   else
-       *proc = typeStruct->typoutput;
+   switch (which_func)
+   {
+       case IOFunc_input:
+           *proc = typeStruct->typinput;
+           break;
+       case IOFunc_output:
+           *proc = typeStruct->typoutput;
+           break;
+       case IOFunc_receive:
+           *proc = typeStruct->typreceive;
+           break;
+       case IOFunc_send:
+           *proc = typeStruct->typsend;
+           break;
+   }
    ReleaseSysCache(typeTuple);
 }
 
index e205448ec6da5864058dd6574746e6de092d4968..e4b28000ded5fb315db066669291cd34424a499b 100644 (file)
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.189 2003/05/09 21:19:49 tgl Exp $
+ * $Id: catversion.h,v 1.190 2003/05/09 23:01:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 200305092
+#define CATALOG_VERSION_NO 200305093
 
 #endif
index bc186f2eed9058ad938ec00921df7587a05cc4e3..a6acb4cf93d548c5da814d7be44785cd0a280d1b 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_proc.h,v 1.296 2003/05/09 21:19:49 tgl Exp $
+ * $Id: pg_proc.h,v 1.297 2003/05/09 23:01:45 tgl Exp $
  *
  * NOTES
  *   The script catalog/genbki.sh reads this file and generates .bki
@@ -3153,9 +3153,9 @@ DATA(insert OID =  2311 (  md5       PGNSP PGUID 12 f f t f i 1 25 "25"  md5_text -
 DESCR("calculates md5 hash");
 
 
-DATA(insert OID = 2400 (  array_recv          PGNSP PGUID 12 f f t f s 1 2277 "2281"  array_recv - _null_ ));
+DATA(insert OID = 2400 (  array_recv          PGNSP PGUID 12 f f t f s 2 2277 "2281 26"  array_recv - _null_ ));
 DESCR("I/O");
-DATA(insert OID = 2401 (  array_send          PGNSP PGUID 12 f f t f s 1 17 "2277"  array_send - _null_ ));
+DATA(insert OID = 2401 (  array_send          PGNSP PGUID 12 f f t f s 2 17 "2277 26"  array_send - _null_ ));
 DESCR("I/O");
 DATA(insert OID = 2402 (  record_recv         PGNSP PGUID 12 f f t f i 1 2249 "2281"  record_recv - _null_ ));
 DESCR("I/O");
index d466052fed3e4e379a9b5a84bba77bf2d389d7dd..8bd18ca29b2afcd6c720eb3e01b6dd848a5794f4 100644 (file)
@@ -143,7 +143,9 @@ WHERE p1.typoutput = p2.oid AND p1.typtype in ('b', 'p') AND NOT
 SELECT p1.oid, p1.typname, p2.oid, p2.proname
 FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typreceive = p2.oid AND p1.typtype in ('b', 'p') AND NOT
-    (p2.pronargs = 1 AND p2.proargtypes[0] = 'internal'::regtype);
+    ((p2.pronargs = 1 AND p2.proargtypes[0] = 'internal'::regtype) OR
+     (p2.pronargs = 2 AND p2.proargtypes[0] = 'internal'::regtype AND
+      p2.proargtypes[1] = 'oid'::regtype));
  oid | typname | oid | proname 
 -----+---------+-----+---------
 (0 rows)
index 04a0557d836ec12f98c1021f014f09abcdb27a7a..3c9004ce569b3159cba09c83ef0f5df0bf5c0e31 100644 (file)
@@ -114,7 +114,9 @@ WHERE p1.typoutput = p2.oid AND p1.typtype in ('b', 'p') AND NOT
 SELECT p1.oid, p1.typname, p2.oid, p2.proname
 FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typreceive = p2.oid AND p1.typtype in ('b', 'p') AND NOT
-    (p2.pronargs = 1 AND p2.proargtypes[0] = 'internal'::regtype);
+    ((p2.pronargs = 1 AND p2.proargtypes[0] = 'internal'::regtype) OR
+     (p2.pronargs = 2 AND p2.proargtypes[0] = 'internal'::regtype AND
+      p2.proargtypes[1] = 'oid'::regtype));
 
 -- As of 7.4, this check finds refcursor, which is borrowing
 -- other types' I/O routines