cubicweb/server/querier.py
changeset 12060 0cdf5fafd234
parent 12051 bff3be6eebed
child 12062 601d65193619
--- a/cubicweb/server/querier.py	Thu Mar 16 17:25:07 2017 +0100
+++ b/cubicweb/server/querier.py	Fri Mar 03 13:09:11 2017 +0100
@@ -478,33 +478,14 @@
     def set_schema(self, schema):
         self.schema = schema
         repo = self._repo
-        # rql st and solution cache.
-        self._rql_cache = QueryCache(repo.config['rql-cache-size'])
-        # rql cache key cache. Don't bother using a Cache instance: we should
-        # have a limited number of queries in there, since there are no entries
-        # in this cache for user queries (which have no args)
-        self._rql_ck_cache = {}
-        # some cache usage stats
-        self.cache_hit, self.cache_miss = 0, 0
-        # rql parsing / analysing helper
-        self.solutions = repo.vreg.solutions
+        self.rql_cache = RQLCache(repo, schema)
         rqlhelper = repo.vreg.rqlhelper
-        # set backend on the rql helper, will be used for function checking
-        rqlhelper.backend = repo.config.system_source_config['db-driver']
-        self._parse = rqlhelper.parse
         self._annotate = rqlhelper.annotate
         # rql planner
         self._planner = SSPlanner(schema, rqlhelper)
         # sql generation annotator
         self.sqlgen_annotate = SQLGenAnnotator(schema).annotate
 
-    def parse(self, rql, annotate=False):
-        """return a rql syntax tree for the given rql"""
-        try:
-            return self._parse(text_type(rql), annotate=annotate)
-        except UnicodeError:
-            raise RQLSyntaxError(rql)
-
     def plan_factory(self, rqlst, args, cnx):
         """create an execution plan for an INSERT RQL query"""
         if rqlst.TYPE == 'insert':
@@ -535,44 +516,12 @@
             if server.DEBUG & (server.DBG_MORE | server.DBG_SQL):
                 print('*'*80)
             print('querier input', repr(rql), repr(args))
-        # parse the query and binds variables
-        cachekey = (rql,)
         try:
-            if args:
-                # search for named args in query which are eids (hence
-                # influencing query's solutions)
-                eidkeys = self._rql_ck_cache[rql]
-                if eidkeys:
-                    # if there are some, we need a better cache key, eg (rql +
-                    # entity type of each eid)
-                    try:
-                        cachekey = _rql_cache_key(cnx, rql, args, eidkeys)
-                    except UnknownEid:
-                        # we want queries such as "Any X WHERE X eid 9999"
-                        # return an empty result instead of raising UnknownEid
-                        return empty_rset(rql, args)
-            rqlst = self._rql_cache[cachekey]
-            self.cache_hit += 1
-            statsd_c('cache_hit')
-        except KeyError:
-            self.cache_miss += 1
-            statsd_c('cache_miss')
-            rqlst = self.parse(rql)
-            try:
-                # compute solutions for rqlst and return named args in query
-                # which are eids. Notice that if you may not need `eidkeys`, we
-                # have to compute solutions anyway (kept as annotation on the
-                # tree)
-                eidkeys = self.solutions(cnx, rqlst, args)
-            except UnknownEid:
-                # we want queries such as "Any X WHERE X eid 9999" return an
-                # empty result instead of raising UnknownEid
-                return empty_rset(rql, args)
-            if args and rql not in self._rql_ck_cache:
-                self._rql_ck_cache[rql] = eidkeys
-                if eidkeys:
-                    cachekey = _rql_cache_key(cnx, rql, args, eidkeys)
-            self._rql_cache[cachekey] = rqlst
+            rqlst, cachekey = self.rql_cache.get(cnx, rql, args)
+        except UnknownEid:
+            # we want queries such as "Any X WHERE X eid 9999"
+            # return an empty result instead of raising UnknownEid
+            return empty_rset(rql, args)
         if rqlst.TYPE != 'select':
             if cnx.read_security:
                 check_no_password_selected(rqlst)
@@ -645,6 +594,74 @@
     info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
 
 
+class RQLCache(object):
+
+    def __init__(self, repo, schema):
+        # rql st and solution cache.
+        self._cache = QueryCache(repo.config['rql-cache-size'])
+        # rql cache key cache. Don't bother using a Cache instance: we should
+        # have a limited number of queries in there, since there are no entries
+        # in this cache for user queries (which have no args)
+        self._ck_cache = {}
+        # some cache usage stats
+        self.cache_hit, self.cache_miss = 0, 0
+        # rql parsing / analysing helper
+        self.solutions = repo.vreg.solutions
+        rqlhelper = repo.vreg.rqlhelper
+        # set backend on the rql helper, will be used for function checking
+        rqlhelper.backend = repo.config.system_source_config['db-driver']
+
+        def parse(rql, annotate=False, parse=rqlhelper.parse):
+            """Return a freshly parsed syntax tree for the given RQL."""
+            try:
+                return parse(text_type(rql), annotate=annotate)
+            except UnicodeError:
+                raise RQLSyntaxError(rql)
+        self._parse = parse
+
+    def __len__(self):
+        return len(self._cache)
+
+    def get(self, cnx, rql, args):
+        """Return syntax tree and cache key for the given RQL.
+
+        Returned syntax tree is cached and must not be modified
+        """
+        # parse the query and binds variables
+        cachekey = (rql,)
+        try:
+            if args:
+                # search for named args in query which are eids (hence
+                # influencing query's solutions)
+                eidkeys = self._ck_cache[rql]
+                if eidkeys:
+                    # if there are some, we need a better cache key, eg (rql +
+                    # entity type of each eid)
+                    cachekey = _rql_cache_key(cnx, rql, args, eidkeys)
+            rqlst = self._cache[cachekey]
+            self.cache_hit += 1
+            statsd_c('cache_hit')
+        except KeyError:
+            self.cache_miss += 1
+            statsd_c('cache_miss')
+            rqlst = self._parse(rql)
+            # compute solutions for rqlst and return named args in query
+            # which are eids. Notice that if you may not need `eidkeys`, we
+            # have to compute solutions anyway (kept as annotation on the
+            # tree)
+            eidkeys = self.solutions(cnx, rqlst, args)
+            if args and rql not in self._ck_cache:
+                self._ck_cache[rql] = eidkeys
+                if eidkeys:
+                    cachekey = _rql_cache_key(cnx, rql, args, eidkeys)
+            self._cache[cachekey] = rqlst
+        return rqlst, cachekey
+
+    def pop(self, key, *args):
+        """Pop a key from the cache."""
+        self._cache.pop(key, *args)
+
+
 def _rql_cache_key(cnx, rql, args, eidkeys):
     cachekey = [rql]
     type_from_eid = cnx.repo.type_from_eid