(unnamed)
text/plain
Index: src/backend/catalog/namespace.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/catalog/namespace.c,v
retrieving revision 1.102
diff -c -r1.102 namespace.c
*** src/backend/catalog/namespace.c 25 Nov 2007 02:09:46 -0000 1.102
--- src/backend/catalog/namespace.c 27 Nov 2007 02:07:01 -0000
***************
*** 3007,3012 ****
--- 3007,3046 ----
}
/*
+ * Fetch the active search path into a caller-allocated array of OIDs.
+ * Returns the number of path entries. (If this is more than sarray_len,
+ * then the data didn't fit and is not all stored.)
+ *
+ * The returned list always includes the implicitly-prepended namespaces,
+ * but never includes the temp namespace. (This is suitable for existing
+ * users, which would want to ignore the temp namespace anyway.) This
+ * definition allows us to not worry about initializing the temp namespace.
+ */
+ int
+ fetch_search_path_array(Oid *sarray, int sarray_len)
+ {
+ int count = 0;
+ ListCell *l;
+
+ recomputeNamespacePath();
+
+ foreach(l, activeSearchPath)
+ {
+ Oid namespaceId = lfirst_oid(l);
+
+ if (namespaceId == myTempNamespace)
+ continue; /* do not include temp namespace */
+
+ if (count < sarray_len)
+ sarray[count] = namespaceId;
+ count++;
+ }
+
+ return count;
+ }
+
+
+ /*
* Export the FooIsVisible functions as SQL-callable functions.
*/
Index: src/backend/parser/parse_oper.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/parser/parse_oper.c,v
retrieving revision 1.98
diff -c -r1.98 parse_oper.c
*** src/backend/parser/parse_oper.c 22 Nov 2007 19:40:25 -0000 1.98
--- src/backend/parser/parse_oper.c 27 Nov 2007 02:07:01 -0000
***************
*** 24,34 ****
--- 24,70 ----
#include "parser/parse_oper.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
+ #include "utils/hsearch.h"
+ #include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
+ /*
+ * The lookup key for the operator lookaside hash table. Unused bits must be
+ * zeroes to ensure hashing works consistently --- in particular, oprname
+ * must be zero-padded and any unused entries in search_path must be zero.
+ *
+ * search_path contains the actual search_path with which the entry was
+ * derived (minus temp namespace if any), or else the single specified
+ * schema OID if we are looking up an explicitly-qualified operator name.
+ *
+ * search_path has to be fixed-length since the hashtable code insists on
+ * fixed-size keys. If your search path is longer than that, we just punt
+ * and don't cache anything.
+ */
+
+ /* If your search_path is longer than this, sucks to be you ... */
+ #define MAX_CACHED_PATH_LEN 16
+
+ typedef struct OprCacheKey
+ {
+ char oprname[NAMEDATALEN];
+ Oid left_arg; /* Left input OID, or 0 if prefix op */
+ Oid right_arg; /* Right input OID, or 0 if postfix op */
+ Oid search_path[MAX_CACHED_PATH_LEN];
+ } OprCacheKey;
+
+ typedef struct OprCacheEntry
+ {
+ /* the hash lookup key MUST BE FIRST */
+ OprCacheKey key;
+
+ Oid opr_oid; /* OID of the resolved operator */
+ } OprCacheEntry;
+
+
static Oid binary_oper_exact(List *opname, Oid arg1, Oid arg2);
static FuncDetailCode oper_select_candidate(int nargs,
Oid *input_typeids,
***************
*** 42,47 ****
--- 78,88 ----
static Expr *make_op_expr(ParseState *pstate, Operator op,
Node *ltree, Node *rtree,
Oid ltypeId, Oid rtypeId);
+ static bool make_oper_cache_key(OprCacheKey *key, List *opname,
+ Oid ltypeId, Oid rtypeId);
+ static Oid find_oper_cache_entry(OprCacheKey *key);
+ static void make_oper_cache_entry(OprCacheKey *key, Oid opr_oid);
+ static void InvalidateOprCacheCallBack(Datum arg, Oid relid);
/*
***************
*** 496,505 ****
--- 537,565 ----
bool noError, int location)
{
Oid operOid;
+ OprCacheKey key;
+ bool key_ok;
FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND;
HeapTuple tup = NULL;
/*
+ * Try to find the mapping in the lookaside cache.
+ */
+ key_ok = make_oper_cache_key(&key, opname, ltypeId, rtypeId);
+ if (key_ok)
+ {
+ operOid = find_oper_cache_entry(&key);
+ if (OidIsValid(operOid))
+ {
+ tup = SearchSysCache(OPEROID,
+ ObjectIdGetDatum(operOid),
+ 0, 0, 0);
+ if (HeapTupleIsValid(tup))
+ return (Operator) tup;
+ }
+ }
+
+ /*
* First try for an "exact" match.
*/
operOid = binary_oper_exact(opname, ltypeId, rtypeId);
***************
*** 537,543 ****
ObjectIdGetDatum(operOid),
0, 0, 0);
! if (!HeapTupleIsValid(tup) && !noError)
op_error(pstate, opname, 'b', ltypeId, rtypeId, fdresult, location);
return (Operator) tup;
--- 597,608 ----
ObjectIdGetDatum(operOid),
0, 0, 0);
! if (HeapTupleIsValid(tup))
! {
! if (key_ok)
! make_oper_cache_entry(&key, operOid);
! }
! else if (!noError)
op_error(pstate, opname, 'b', ltypeId, rtypeId, fdresult, location);
return (Operator) tup;
***************
*** 622,631 ****
--- 687,715 ----
right_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
{
Oid operOid;
+ OprCacheKey key;
+ bool key_ok;
FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND;
HeapTuple tup = NULL;
/*
+ * Try to find the mapping in the lookaside cache.
+ */
+ key_ok = make_oper_cache_key(&key, op, arg, InvalidOid);
+ if (key_ok)
+ {
+ operOid = find_oper_cache_entry(&key);
+ if (OidIsValid(operOid))
+ {
+ tup = SearchSysCache(OPEROID,
+ ObjectIdGetDatum(operOid),
+ 0, 0, 0);
+ if (HeapTupleIsValid(tup))
+ return (Operator) tup;
+ }
+ }
+
+ /*
* First try for an "exact" match.
*/
operOid = OpernameGetOprid(op, arg, InvalidOid);
***************
*** 655,661 ****
ObjectIdGetDatum(operOid),
0, 0, 0);
! if (!HeapTupleIsValid(tup) && !noError)
op_error(pstate, op, 'r', arg, InvalidOid, fdresult, location);
return (Operator) tup;
--- 739,750 ----
ObjectIdGetDatum(operOid),
0, 0, 0);
! if (HeapTupleIsValid(tup))
! {
! if (key_ok)
! make_oper_cache_entry(&key, operOid);
! }
! else if (!noError)
op_error(pstate, op, 'r', arg, InvalidOid, fdresult, location);
return (Operator) tup;
***************
*** 680,689 ****
--- 769,797 ----
left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
{
Oid operOid;
+ OprCacheKey key;
+ bool key_ok;
FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND;
HeapTuple tup = NULL;
/*
+ * Try to find the mapping in the lookaside cache.
+ */
+ key_ok = make_oper_cache_key(&key, op, InvalidOid, arg);
+ if (key_ok)
+ {
+ operOid = find_oper_cache_entry(&key);
+ if (OidIsValid(operOid))
+ {
+ tup = SearchSysCache(OPEROID,
+ ObjectIdGetDatum(operOid),
+ 0, 0, 0);
+ if (HeapTupleIsValid(tup))
+ return (Operator) tup;
+ }
+ }
+
+ /*
* First try for an "exact" match.
*/
operOid = OpernameGetOprid(op, InvalidOid, arg);
***************
*** 725,731 ****
ObjectIdGetDatum(operOid),
0, 0, 0);
! if (!HeapTupleIsValid(tup) && !noError)
op_error(pstate, op, 'l', InvalidOid, arg, fdresult, location);
return (Operator) tup;
--- 833,844 ----
ObjectIdGetDatum(operOid),
0, 0, 0);
! if (HeapTupleIsValid(tup))
! {
! if (key_ok)
! make_oper_cache_entry(&key, operOid);
! }
! else if (!noError)
op_error(pstate, op, 'l', InvalidOid, arg, fdresult, location);
return (Operator) tup;
***************
*** 1018,1020 ****
--- 1131,1290 ----
return (Expr *) result;
}
+
+
+ /*
+ * Lookaside cache to speed operator lookup. Possibly this should be in
+ * a separate module under utils/cache/ ?
+ *
+ * The idea here is that the mapping from operator name and given argument
+ * types is constant for a given search path (or single specified schema OID)
+ * so long as the contents of pg_operator and pg_cast don't change. And that
+ * mapping is pretty expensive to compute, especially for ambiguous operators;
+ * this is mainly because there are a *lot* of instances of popular operator
+ * names such as "=", and we have to check each one to see which is the
+ * best match. So once we have identified the correct mapping, we save it
+ * in a cache that need only be flushed on pg_operator or pg_cast change.
+ * (pg_cast must be considered because changes in the set of implicit casts
+ * affect the set of applicable operators for any given input datatype.)
+ *
+ * XXX in principle, ALTER TABLE ... INHERIT could affect the mapping as
+ * well, but we disregard that since there's no convenient way to find out
+ * about it, and it seems a pretty far-fetched corner-case anyway.
+ *
+ * Note: at some point it might be worth doing a similar cache for function
+ * lookups. However, the potential gain is a lot less since (a) function
+ * names are generally not overloaded as heavily as operator names, and
+ * (b) we'd have to flush on pg_proc updates, which are probably a good
+ * deal more common than pg_operator updates.
+ */
+
+ /* The operator cache hashtable */
+ static HTAB *OprCacheHash = NULL;
+
+
+ /*
+ * make_oper_cache_key
+ * Fill the lookup key struct given operator name and arg types.
+ *
+ * Returns TRUE if successful, FALSE if the search_path overflowed
+ * (hence no caching is possible).
+ */
+ static bool
+ make_oper_cache_key(OprCacheKey *key, List *opname, Oid ltypeId, Oid rtypeId)
+ {
+ char *schemaname;
+ char *opername;
+
+ /* deconstruct the name list */
+ DeconstructQualifiedName(opname, &schemaname, &opername);
+
+ /* ensure zero-fill for stable hashing */
+ MemSet(key, 0, sizeof(OprCacheKey));
+
+ /* save operator name and input types into key */
+ strlcpy(key->oprname, opername, NAMEDATALEN);
+ key->left_arg = ltypeId;
+ key->right_arg = rtypeId;
+
+ if (schemaname)
+ {
+ /* search only in exact schema given */
+ key->search_path[0] = LookupExplicitNamespace(schemaname);
+ }
+ else
+ {
+ /* get the active search path */
+ if (fetch_search_path_array(key->search_path,
+ MAX_CACHED_PATH_LEN) > MAX_CACHED_PATH_LEN)
+ return false; /* oops, didn't fit */
+ }
+
+ return true;
+ }
+
+ /*
+ * find_oper_cache_entry
+ *
+ * Look for a cache entry matching the given key. If found, return the
+ * contained operator OID, else return InvalidOid.
+ */
+ static Oid
+ find_oper_cache_entry(OprCacheKey *key)
+ {
+ OprCacheEntry *oprentry;
+
+ if (OprCacheHash == NULL)
+ {
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(OprCacheKey);
+ ctl.entrysize = sizeof(OprCacheEntry);
+ ctl.hash = tag_hash;
+ OprCacheHash = hash_create("Operator lookup cache", 256,
+ &ctl, HASH_ELEM | HASH_FUNCTION);
+
+ /* Arrange to flush cache on pg_operator and pg_cast changes */
+ CacheRegisterSyscacheCallback(OPERNAMENSP,
+ InvalidateOprCacheCallBack,
+ (Datum) 0);
+ CacheRegisterSyscacheCallback(CASTSOURCETARGET,
+ InvalidateOprCacheCallBack,
+ (Datum) 0);
+ }
+
+ /* Look for an existing entry */
+ oprentry = (OprCacheEntry *) hash_search(OprCacheHash,
+ (void *) key,
+ HASH_FIND, NULL);
+ if (oprentry == NULL)
+ return InvalidOid;
+
+ return oprentry->opr_oid;
+ }
+
+ /*
+ * make_oper_cache_entry
+ *
+ * Insert a cache entry for the given key.
+ */
+ static void
+ make_oper_cache_entry(OprCacheKey *key, Oid opr_oid)
+ {
+ OprCacheEntry *oprentry;
+
+ Assert(OprCacheHash != NULL);
+
+ oprentry = (OprCacheEntry *) hash_search(OprCacheHash,
+ (void *) key,
+ HASH_ENTER, NULL);
+ oprentry->opr_oid = opr_oid;
+ }
+
+ /*
+ * Callback for pg_operator and pg_cast inval events
+ */
+ static void
+ InvalidateOprCacheCallBack(Datum arg, Oid relid)
+ {
+ HASH_SEQ_STATUS status;
+ OprCacheEntry *hentry;
+
+ Assert(OprCacheHash != NULL);
+
+ /* Currently we just flush all entries; hard to be smarter ... */
+ hash_seq_init(&status, OprCacheHash);
+
+ while ((hentry = (OprCacheEntry *) hash_seq_search(&status)) != NULL)
+ {
+ if (hash_search(OprCacheHash,
+ (void *) &hentry->key,
+ HASH_REMOVE, NULL) == NULL)
+ elog(ERROR, "hash table corrupted");
+ }
+ }
Index: src/include/catalog/namespace.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/catalog/namespace.h,v
retrieving revision 1.51
diff -c -r1.51 namespace.h
*** src/include/catalog/namespace.h 15 Nov 2007 22:25:17 -0000 1.51
--- src/include/catalog/namespace.h 27 Nov 2007 02:07:01 -0000
***************
*** 115,119 ****
--- 115,120 ----
extern char *namespace_search_path;
extern List *fetch_search_path(bool includeImplicit);
+ extern int fetch_search_path_array(Oid *sarray, int sarray_len);
#endif /* NAMESPACE_H */