def test_get_with_default(self): """ Tests the capability of QueryCache for retrieving items with a default value """ c = QueryCache(ceiling=20) # set 10 values for x in range(10): c[x] = x # arrange for the first 5 to be permanent for x in range(5): for r in range(QueryCache._maxlevel + 2): v = c[x] self.assertEqual(v, x) self.assertEqual(c._usage_report(), { 'transientcount': 0, 'itemcount': 10, 'permanentcount': 5 }) # Test defaults for existing (including in permanents) for x in range(10): v = c.get(x, -1) self.assertEqual(v, x) # Test defaults for others for x in range(10, 15): v = c.get(x, -1) self.assertEqual(v, -1)
def test_clear_on_overflow(self): """Tests that only non-permanent items in the cache are wiped-out on ceiling overflow """ c = QueryCache(ceiling=10) # set 10 values for x in range(10): c[x] = x # arrange for the first 5 to be permanent for x in range(5): for r in range(QueryCache._maxlevel + 2): v = c[x] self.assertEqual(v, x) # Add the 11-th c[10] = 10 self.assertEqual(c._usage_report(), { 'transientcount': 0, 'itemcount': 6, 'permanentcount': 5 })
def test_iterkeys(self): """ Tests the iterating on keys in the cache """ c = QueryCache(ceiling=20) # set 10 values for x in range(10): c[x] = x # arrange for the first 5 to be permanent for x in range(5): for r in range(QueryCache._maxlevel + 2): v = c[x] self.assertEqual(v, x) self.assertEqual(c._usage_report(), { 'transientcount': 0, 'itemcount': 10, 'permanentcount': 5 }) keys = sorted(c) for x in range(10): self.assertEqual(x, keys[x])
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(rql, annotate=annotate) except UnicodeError: raise RQLSyntaxError(rql) self._parse = parse
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(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 test_querycache(self): c = QueryCache(ceiling=20) # write only for x in range(10): c[x] = x self.assertEqual(c._usage_report(), { 'transientcount': 0, 'itemcount': 10, 'permanentcount': 0 }) c = QueryCache(ceiling=10) # we should also get a warning for x in range(20): c[x] = x self.assertEqual(c._usage_report(), { 'transientcount': 0, 'itemcount': 10, 'permanentcount': 0 }) # write + reads c = QueryCache(ceiling=20) for n in range(4): for x in range(10): c[x] = x c[x] self.assertEqual(c._usage_report(), { 'transientcount': 10, 'itemcount': 10, 'permanentcount': 0 }) c = QueryCache(ceiling=20) for n in range(17): for x in range(10): c[x] = x c[x] self.assertEqual(c._usage_report(), { 'transientcount': 0, 'itemcount': 10, 'permanentcount': 10 }) c = QueryCache(ceiling=20) for n in range(17): for x in range(10): c[x] = x if n % 2: c[x] if x % 2: c[x] self.assertEqual(c._usage_report(), { 'transientcount': 5, 'itemcount': 10, 'permanentcount': 5 })