Improve caching in range type I/O functions.
authorTom Lane <[email protected]>
Tue, 15 Nov 2011 20:47:51 +0000 (15:47 -0500)
committerTom Lane <[email protected]>
Tue, 15 Nov 2011 20:47:51 +0000 (15:47 -0500)
Cache the the element type's I/O info across calls, not only the range
type's info.  In passing, also clean up hash_range a bit more.

src/backend/utils/adt/rangetypes.c

index a2eb0562e0a582ad75ce9eb2313a28bb4c55bc4a..4d7625a625561c3810132a31e0ec985fb2c58f60 100644 (file)
 #include "postgres.h"
 
 #include "access/hash.h"
-#include "access/nbtree.h"
-#include "catalog/pg_opclass.h"
-#include "catalog/pg_range.h"
-#include "catalog/pg_type.h"
-#include "fmgr.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
-#include "utils/fmgroids.h"
 #include "utils/int8.h"
 #include "utils/lsyscache.h"
 #include "utils/rangetypes.h"
-#include "utils/syscache.h"
 #include "utils/timestamp.h"
-#include "utils/typcache.h"
 
 
 #define RANGE_EMPTY_LITERAL "empty"
 
-#define RANGE_DEFAULT_FLAGS    "[)"
+/* fn_extra cache entry for one of the range I/O functions */
+typedef struct RangeIOData
+{
+   TypeCacheEntry *typcache;   /* range type's typcache entry */
+   Oid         typiofunc;      /* element type's I/O function */
+   Oid         typioparam;     /* element type's I/O parameter */
+   FmgrInfo    proc;           /* lookup result for typiofunc */
+} RangeIOData;
 
 
+static RangeIOData *get_range_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
+                                     IOFuncSelector func);
 static char range_parse_flags(const char *flags_str);
 static void range_parse(char *input_str, char *flags, char **lbound_str,
            char **ubound_str);
@@ -66,31 +67,25 @@ range_in(PG_FUNCTION_ARGS)
    Oid         rngtypoid = PG_GETARG_OID(1);
    Oid         typmod = PG_GETARG_INT32(2);
    RangeType  *range;
-   TypeCacheEntry *typcache;
+   RangeIOData *cache;
    char        flags;
    char       *lbound_str;
    char       *ubound_str;
-   regproc     subInput;
-   FmgrInfo    subInputFn;
-   Oid         ioParam;
    RangeBound  lower;
    RangeBound  upper;
 
-   typcache = range_get_typcache(fcinfo, rngtypoid);
+   cache = get_range_io_data(fcinfo, rngtypoid, IOFunc_input);
 
    /* parse */
    range_parse(input_str, &flags, &lbound_str, &ubound_str);
 
-   /* input */
-   getTypeInputInfo(typcache->rngelemtype->type_id, &subInput, &ioParam);
-   fmgr_info(subInput, &subInputFn);
-
+   /* call element type's input function */
    if (RANGE_HAS_LBOUND(flags))
-       lower.val = InputFunctionCall(&subInputFn, lbound_str,
-                                     ioParam, typmod);
+       lower.val = InputFunctionCall(&cache->proc, lbound_str,
+                                     cache->typioparam, typmod);
    if (RANGE_HAS_UBOUND(flags))
-       upper.val = InputFunctionCall(&subInputFn, ubound_str,
-                                     ioParam, typmod);
+       upper.val = InputFunctionCall(&cache->proc, ubound_str,
+                                     cache->typioparam, typmod);
 
    lower.infinite = (flags & RANGE_LB_INF) != 0;
    lower.inclusive = (flags & RANGE_LB_INC) != 0;
@@ -100,7 +95,7 @@ range_in(PG_FUNCTION_ARGS)
    upper.lower = false;
 
    /* serialize and canonicalize */
-   range = make_range(typcache, &lower, &upper, flags & RANGE_EMPTY);
+   range = make_range(cache->typcache, &lower, &upper, flags & RANGE_EMPTY);
 
    PG_RETURN_RANGE(range);
 }
@@ -109,39 +104,26 @@ Datum
 range_out(PG_FUNCTION_ARGS)
 {
    RangeType  *range = PG_GETARG_RANGE(0);
-   TypeCacheEntry *typcache;
    char       *output_str;
-   regproc     subOutput;
-   FmgrInfo    subOutputFn;
-   bool        isVarlena;
-   char        flags = 0;
+   RangeIOData *cache;
+   char        flags;
    char       *lbound_str = NULL;
    char       *ubound_str = NULL;
-   bool        empty;
    RangeBound  lower;
    RangeBound  upper;
+   bool        empty;
 
-   typcache = range_get_typcache(fcinfo, RangeTypeGetOid(range));
+   cache = get_range_io_data(fcinfo, RangeTypeGetOid(range), IOFunc_output);
 
    /* deserialize */
-   range_deserialize(typcache, range, &lower, &upper, &empty);
-
-   if (empty)
-       flags |= RANGE_EMPTY;
-
-   flags |= lower.inclusive ? RANGE_LB_INC : 0;
-   flags |= lower.infinite ? RANGE_LB_INF : 0;
-   flags |= upper.inclusive ? RANGE_UB_INC : 0;
-   flags |= upper.infinite ? RANGE_UB_INF : 0;
-
-   /* output */
-   getTypeOutputInfo(typcache->rngelemtype->type_id, &subOutput, &isVarlena);
-   fmgr_info(subOutput, &subOutputFn);
+   range_deserialize(cache->typcache, range, &lower, &upper, &empty);
+   flags = range_get_flags(range);
 
+   /* call element type's output function */
    if (RANGE_HAS_LBOUND(flags))
-       lbound_str = OutputFunctionCall(&subOutputFn, lower.val);
+       lbound_str = OutputFunctionCall(&cache->proc, lower.val);
    if (RANGE_HAS_UBOUND(flags))
-       ubound_str = OutputFunctionCall(&subOutputFn, upper.val);
+       ubound_str = OutputFunctionCall(&cache->proc, upper.val);
 
    /* deparse */
    output_str = range_deparse(flags, lbound_str, ubound_str);
@@ -150,11 +132,10 @@ range_out(PG_FUNCTION_ARGS)
 }
 
 /*
- * Binary representation: The first byte is the flags, then 4 bytes are the
- * range type Oid, then the lower bound (if present) then the upper bound (if
- * present). Each bound is represented by a 4-byte length header and the binary
- * representation of that bound (as returned by a call to the send function for
- * the subtype).
+ * Binary representation: The first byte is the flags, then the lower bound
+ * (if present), then the upper bound (if present).  Each bound is represented
+ * by a 4-byte length header and the binary representation of that bound (as
+ * returned by a call to the send function for the subtype).
  */
 
 Datum
@@ -164,19 +145,27 @@ range_recv(PG_FUNCTION_ARGS)
    Oid         rngtypoid = PG_GETARG_OID(1);
    int32       typmod = PG_GETARG_INT32(2);
    RangeType  *range;
-   TypeCacheEntry *typcache;
-   Oid         subrecv;
-   Oid         ioparam;
+   RangeIOData *cache;
    char        flags;
    RangeBound  lower;
    RangeBound  upper;
 
-   typcache = range_get_typcache(fcinfo, rngtypoid);
+   cache = get_range_io_data(fcinfo, rngtypoid, IOFunc_receive);
 
+   /* receive the flags... */
    flags = (unsigned char) pq_getmsgbyte(buf);
 
-   getTypeBinaryInputInfo(typcache->rngelemtype->type_id, &subrecv, &ioparam);
+   /*
+    * mask out any unsupported flags, particularly RANGE_xB_NULL which would
+    * confuse following tests.
+    */
+   flags &= (RANGE_EMPTY |
+             RANGE_LB_INC |
+             RANGE_LB_INF |
+             RANGE_UB_INC |
+             RANGE_UB_INF);
 
+   /* receive the bounds ... */
    if (RANGE_HAS_LBOUND(flags))
    {
        uint32      bound_len = pq_getmsgint(buf, 4);
@@ -186,10 +175,10 @@ range_recv(PG_FUNCTION_ARGS)
        initStringInfo(&bound_buf);
        appendBinaryStringInfo(&bound_buf, bound_data, bound_len);
 
-       lower.val = OidReceiveFunctionCall(subrecv,
-                                          &bound_buf,
-                                          ioparam,
-                                          typmod);
+       lower.val = ReceiveFunctionCall(&cache->proc,
+                                       &bound_buf,
+                                       cache->typioparam,
+                                       typmod);
        pfree(bound_buf.data);
    }
    else
@@ -204,10 +193,10 @@ range_recv(PG_FUNCTION_ARGS)
        initStringInfo(&bound_buf);
        appendBinaryStringInfo(&bound_buf, bound_data, bound_len);
 
-       upper.val = OidReceiveFunctionCall(subrecv,
-                                          &bound_buf,
-                                          ioparam,
-                                          typmod);
+       upper.val = ReceiveFunctionCall(&cache->proc,
+                                       &bound_buf,
+                                       cache->typioparam,
+                                       typmod);
        pfree(bound_buf.data);
    }
    else
@@ -215,6 +204,7 @@ range_recv(PG_FUNCTION_ARGS)
 
    pq_getmsgend(buf);
 
+   /* finish constructing RangeBound representation */
    lower.infinite = (flags & RANGE_LB_INF) != 0;
    lower.inclusive = (flags & RANGE_LB_INC) != 0;
    lower.lower = true;
@@ -223,7 +213,7 @@ range_recv(PG_FUNCTION_ARGS)
    upper.lower = false;
 
    /* serialize and canonicalize */
-   range = make_range(typcache, &lower, &upper, flags & RANGE_EMPTY);
+   range = make_range(cache->typcache, &lower, &upper, flags & RANGE_EMPTY);
 
    PG_RETURN_RANGE(range);
 }
@@ -233,37 +223,27 @@ range_send(PG_FUNCTION_ARGS)
 {
    RangeType  *range = PG_GETARG_RANGE(0);
    StringInfo  buf = makeStringInfo();
-   TypeCacheEntry *typcache;
-   char        flags = 0;
+   RangeIOData *cache;
+   char        flags;
    RangeBound  lower;
    RangeBound  upper;
    bool        empty;
-   Oid         subsend;
-   bool        typIsVarlena;
 
-   typcache = range_get_typcache(fcinfo, RangeTypeGetOid(range));
+   cache = get_range_io_data(fcinfo, RangeTypeGetOid(range), IOFunc_send);
 
-   getTypeBinaryOutputInfo(typcache->rngelemtype->type_id,
-                           &subsend, &typIsVarlena);
+   /* deserialize */
+   range_deserialize(cache->typcache, range, &lower, &upper, &empty);
+   flags = range_get_flags(range);
 
+   /* construct output */
    pq_begintypsend(buf);
 
-   range_deserialize(typcache, range, &lower, &upper, &empty);
-
-   if (empty)
-       flags |= RANGE_EMPTY;
-
-   flags |= lower.inclusive ? RANGE_LB_INC : 0;
-   flags |= lower.infinite ? RANGE_LB_INF : 0;
-   flags |= upper.inclusive ? RANGE_UB_INC : 0;
-   flags |= upper.infinite ? RANGE_UB_INF : 0;
-
    pq_sendbyte(buf, flags);
 
    if (RANGE_HAS_LBOUND(flags))
    {
-       Datum       bound = PointerGetDatum(OidSendFunctionCall(subsend,
-                                                               lower.val));
+       Datum       bound = PointerGetDatum(SendFunctionCall(&cache->proc,
+                                                            lower.val));
        uint32      bound_len = VARSIZE(bound) - VARHDRSZ;
        char       *bound_data = VARDATA(bound);
 
@@ -273,8 +253,8 @@ range_send(PG_FUNCTION_ARGS)
 
    if (RANGE_HAS_UBOUND(flags))
    {
-       Datum       bound = PointerGetDatum(OidSendFunctionCall(subsend,
-                                                               upper.val));
+       Datum       bound = PointerGetDatum(SendFunctionCall(&cache->proc,
+                                                            upper.val));
        uint32      bound_len = VARSIZE(bound) - VARHDRSZ;
        char       *bound_data = VARDATA(bound);
 
@@ -285,6 +265,64 @@ range_send(PG_FUNCTION_ARGS)
    PG_RETURN_BYTEA_P(pq_endtypsend(buf));
 }
 
+/*
+ * get_range_io_data: get cached information needed for range type I/O
+ *
+ * The range I/O functions need a bit more cached info than other range
+ * functions, so they store a RangeIOData struct in fn_extra, not just a
+ * pointer to a type cache entry.
+ */
+static RangeIOData *
+get_range_io_data(FunctionCallInfo fcinfo, Oid rngtypid, IOFuncSelector func)
+{
+   RangeIOData *cache = (RangeIOData *) fcinfo->flinfo->fn_extra;
+
+   if (cache == NULL || cache->typcache->type_id != rngtypid)
+   {
+       int16   typlen;
+       bool    typbyval;
+       char    typalign;
+       char    typdelim;
+
+       cache = (RangeIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+                                                  sizeof(RangeIOData));
+       cache->typcache = lookup_type_cache(rngtypid, TYPECACHE_RANGE_INFO);
+       if (cache->typcache->rngelemtype == NULL)
+           elog(ERROR, "type %u is not a range type", rngtypid);
+
+       /* get_type_io_data does more than we need, but is convenient */
+       get_type_io_data(cache->typcache->rngelemtype->type_id,
+                        func,
+                        &typlen,
+                        &typbyval,
+                        &typalign,
+                        &typdelim,
+                        &cache->typioparam,
+                        &cache->typiofunc);
+
+       if (!OidIsValid(cache->typiofunc))
+       {
+           /* this could only happen for receive or send */
+           if (func == IOFunc_receive)
+               ereport(ERROR,
+                       (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                        errmsg("no binary input function available for type %s",
+                               format_type_be(cache->typcache->rngelemtype->type_id))));
+           else
+               ereport(ERROR,
+                       (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                        errmsg("no binary output function available for type %s",
+                               format_type_be(cache->typcache->rngelemtype->type_id))));
+       }
+       fmgr_info_cxt(cache->typiofunc, &cache->proc,
+                     fcinfo->flinfo->fn_mcxt);
+
+       fcinfo->flinfo->fn_extra = (void *) cache;
+   }
+
+   return cache;
+}
+
 
 /*
  *----------------------------------------------------------
@@ -360,20 +398,17 @@ range_constructor2(PG_FUNCTION_ARGS)
    TypeCacheEntry *typcache;
    RangeBound  lower;
    RangeBound  upper;
-   char        flags;
 
    typcache = range_get_typcache(fcinfo, rngtypid);
 
-   flags = range_parse_flags(RANGE_DEFAULT_FLAGS);
-
    lower.val = PG_ARGISNULL(0) ? (Datum) 0 : arg1;
    lower.infinite = PG_ARGISNULL(0);
-   lower.inclusive = (flags & RANGE_LB_INC) != 0;
+   lower.inclusive = true;
    lower.lower = true;
 
    upper.val = PG_ARGISNULL(1) ? (Datum) 0 : arg2;
    upper.infinite = PG_ARGISNULL(1);
-   upper.inclusive = (flags & RANGE_UB_INC) != 0;
+   upper.inclusive = false;
    upper.lower = false;
 
    range = make_range(typcache, &lower, &upper, false);
@@ -1085,66 +1120,54 @@ Datum
 hash_range(PG_FUNCTION_ARGS)
 {
    RangeType  *r = PG_GETARG_RANGE(0);
+   uint32      result;
    TypeCacheEntry *typcache;
+   TypeCacheEntry *scache;
    RangeBound  lower;
    RangeBound  upper;
    bool        empty;
-   char        flags = 0;
-   uint32      lower_hash = 0;
-   uint32      upper_hash = 0;
-   uint32      result = 0;
-   TypeCacheEntry *subtypcache;
-   FunctionCallInfoData locfcinfo;
+   char        flags;
+   uint32      lower_hash;
+   uint32      upper_hash;
 
    typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r));
 
+   /* deserialize */
    range_deserialize(typcache, r, &lower, &upper, &empty);
-
-   if (empty)
-       flags |= RANGE_EMPTY;
-
-   flags |= lower.inclusive ? RANGE_LB_INC : 0;
-   flags |= lower.infinite ? RANGE_LB_INF : 0;
-   flags |= upper.inclusive ? RANGE_UB_INC : 0;
-   flags |= upper.infinite ? RANGE_UB_INF : 0;
+   flags = range_get_flags(r);
 
    /*
     * Look up the element type's hash function, if not done already.
     */
-   subtypcache = typcache->rngelemtype;
-   if (!OidIsValid(subtypcache->hash_proc_finfo.fn_oid))
+   scache = typcache->rngelemtype;
+   if (!OidIsValid(scache->hash_proc_finfo.fn_oid))
    {
-       subtypcache = lookup_type_cache(subtypcache->type_id,
-                                       TYPECACHE_HASH_PROC_FINFO);
-       if (!OidIsValid(subtypcache->hash_proc_finfo.fn_oid))
+       scache = lookup_type_cache(scache->type_id, TYPECACHE_HASH_PROC_FINFO);
+       if (!OidIsValid(scache->hash_proc_finfo.fn_oid))
            ereport(ERROR,
                    (errcode(ERRCODE_UNDEFINED_FUNCTION),
                     errmsg("could not identify a hash function for type %s",
-                           format_type_be(subtypcache->type_id))));
+                           format_type_be(scache->type_id))));
    }
 
    /*
-    * Apply the hash function to each bound (the hash function shouldn't care
-    * about the collation).
+    * Apply the hash function to each bound.
     */
-   InitFunctionCallInfoData(locfcinfo, &subtypcache->hash_proc_finfo, 1,
-                            InvalidOid, NULL, NULL);
-
    if (RANGE_HAS_LBOUND(flags))
-   {
-       locfcinfo.arg[0] = lower.val;
-       locfcinfo.argnull[0] = false;
-       locfcinfo.isnull = false;
-       lower_hash = DatumGetUInt32(FunctionCallInvoke(&locfcinfo));
-   }
+       lower_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo,
+                                                     typcache->rng_collation,
+                                                     lower.val));
+   else
+       lower_hash = 0;
+
    if (RANGE_HAS_UBOUND(flags))
-   {
-       locfcinfo.arg[0] = upper.val;
-       locfcinfo.argnull[0] = false;
-       locfcinfo.isnull = false;
-       upper_hash = DatumGetUInt32(FunctionCallInvoke(&locfcinfo));
-   }
+       upper_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo,
+                                                     typcache->rng_collation,
+                                                     upper.val));
+   else
+       upper_hash = 0;
 
+   /* Merge hashes of flags and bounds */
    result = hash_uint32((uint32) flags);
    result ^= lower_hash;
    result = (result << 1) | (result >> 31);
@@ -1420,15 +1443,18 @@ range_serialize(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper,
    /* construct flags value */
    if (empty)
        flags |= RANGE_EMPTY;
-   else if (range_cmp_bounds(typcache, lower, upper) > 0)
-       ereport(ERROR,
-               (errcode(ERRCODE_DATA_EXCEPTION),
-                errmsg("range lower bound must be less than or equal to range upper bound")));
+   else
+   {
+       if (range_cmp_bounds(typcache, lower, upper) > 0)
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATA_EXCEPTION),
+                    errmsg("range lower bound must be less than or equal to range upper bound")));
 
-   flags |= lower->inclusive ? RANGE_LB_INC : 0;
-   flags |= lower->infinite ? RANGE_LB_INF : 0;
-   flags |= upper->inclusive ? RANGE_UB_INC : 0;
-   flags |= upper->infinite ? RANGE_UB_INF : 0;
+       flags |= lower->inclusive ? RANGE_LB_INC : 0;
+       flags |= lower->infinite ? RANGE_LB_INF : 0;
+       flags |= upper->inclusive ? RANGE_UB_INC : 0;
+       flags |= upper->infinite ? RANGE_UB_INF : 0;
+   }
 
    /* Count space for varlena header and range type's OID */
    msize = sizeof(RangeType);
@@ -1731,7 +1757,7 @@ range_parse(char *string, char *flags, char **lbound_str,
    *flags = 0;
 
    /* consume whitespace */
-   while (*ptr != '\0' && isspace(*ptr))
+   while (*ptr != '\0' && isspace((unsigned char) *ptr))
        ptr++;
 
    /* check for empty range */
@@ -1743,7 +1769,7 @@ range_parse(char *string, char *flags, char **lbound_str,
        ptr += strlen(RANGE_EMPTY_LITERAL);
 
        /* the rest should be whitespace */
-       while (*ptr != '\0' && isspace(*ptr))
+       while (*ptr != '\0' && isspace((unsigned char) *ptr))
            ptr++;
 
        /* should have consumed everything */
@@ -1812,7 +1838,7 @@ range_parse(char *string, char *flags, char **lbound_str,
    }
 
    /* consume whitespace */
-   while (*ptr != '\0' && isspace(*ptr))
+   while (*ptr != '\0' && isspace((unsigned char) *ptr))
        ptr++;
 
    if (*ptr != '\0')
@@ -1885,16 +1911,21 @@ range_parse_bound(char *string, char *ptr, char **bound_str, bool *infinite)
    return ptr;
 }
 
+/*
+ * Convert a deserialized range value to text form
+ *
+ * Result is a palloc'd string
+ */
 static char *
 range_deparse(char flags, char *lbound_str, char *ubound_str)
 {
    StringInfoData buf;
 
-   initStringInfo(&buf);
-
    if (flags & RANGE_EMPTY)
        return pstrdup(RANGE_EMPTY_LITERAL);
 
+   initStringInfo(&buf);
+
    appendStringInfoString(&buf, (flags & RANGE_LB_INC) ? "[" : "(");
 
    if (RANGE_HAS_LBOUND(flags))
@@ -1910,6 +1941,11 @@ range_deparse(char flags, char *lbound_str, char *ubound_str)
    return buf.data;
 }
 
+/*
+ * Helper for range_deparse: quote a bound value as needed
+ *
+ * Result is a palloc'd string
+ */
 static char *
 range_bound_escape(char *value)
 {