(unnamed)

text/plain

Filename: (unnamed)
Type: text/plain
Part: 0
Message: Proposed patch for operator lookup caching
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 */