cubicweb/server/querier.py
changeset 12060 0cdf5fafd234
parent 12051 bff3be6eebed
child 12062 601d65193619
equal deleted inserted replaced
12059:72724cf53b1d 12060:0cdf5fafd234
   476         self.set_schema(schema)
   476         self.set_schema(schema)
   477 
   477 
   478     def set_schema(self, schema):
   478     def set_schema(self, schema):
   479         self.schema = schema
   479         self.schema = schema
   480         repo = self._repo
   480         repo = self._repo
   481         # rql st and solution cache.
   481         self.rql_cache = RQLCache(repo, schema)
   482         self._rql_cache = QueryCache(repo.config['rql-cache-size'])
       
   483         # rql cache key cache. Don't bother using a Cache instance: we should
       
   484         # have a limited number of queries in there, since there are no entries
       
   485         # in this cache for user queries (which have no args)
       
   486         self._rql_ck_cache = {}
       
   487         # some cache usage stats
       
   488         self.cache_hit, self.cache_miss = 0, 0
       
   489         # rql parsing / analysing helper
       
   490         self.solutions = repo.vreg.solutions
       
   491         rqlhelper = repo.vreg.rqlhelper
   482         rqlhelper = repo.vreg.rqlhelper
   492         # set backend on the rql helper, will be used for function checking
       
   493         rqlhelper.backend = repo.config.system_source_config['db-driver']
       
   494         self._parse = rqlhelper.parse
       
   495         self._annotate = rqlhelper.annotate
   483         self._annotate = rqlhelper.annotate
   496         # rql planner
   484         # rql planner
   497         self._planner = SSPlanner(schema, rqlhelper)
   485         self._planner = SSPlanner(schema, rqlhelper)
   498         # sql generation annotator
   486         # sql generation annotator
   499         self.sqlgen_annotate = SQLGenAnnotator(schema).annotate
   487         self.sqlgen_annotate = SQLGenAnnotator(schema).annotate
   500 
       
   501     def parse(self, rql, annotate=False):
       
   502         """return a rql syntax tree for the given rql"""
       
   503         try:
       
   504             return self._parse(text_type(rql), annotate=annotate)
       
   505         except UnicodeError:
       
   506             raise RQLSyntaxError(rql)
       
   507 
   488 
   508     def plan_factory(self, rqlst, args, cnx):
   489     def plan_factory(self, rqlst, args, cnx):
   509         """create an execution plan for an INSERT RQL query"""
   490         """create an execution plan for an INSERT RQL query"""
   510         if rqlst.TYPE == 'insert':
   491         if rqlst.TYPE == 'insert':
   511             return InsertPlan(self, rqlst, args, cnx)
   492             return InsertPlan(self, rqlst, args, cnx)
   533         """
   514         """
   534         if server.DEBUG & (server.DBG_RQL | server.DBG_SQL):
   515         if server.DEBUG & (server.DBG_RQL | server.DBG_SQL):
   535             if server.DEBUG & (server.DBG_MORE | server.DBG_SQL):
   516             if server.DEBUG & (server.DBG_MORE | server.DBG_SQL):
   536                 print('*'*80)
   517                 print('*'*80)
   537             print('querier input', repr(rql), repr(args))
   518             print('querier input', repr(rql), repr(args))
   538         # parse the query and binds variables
       
   539         cachekey = (rql,)
       
   540         try:
   519         try:
   541             if args:
   520             rqlst, cachekey = self.rql_cache.get(cnx, rql, args)
   542                 # search for named args in query which are eids (hence
   521         except UnknownEid:
   543                 # influencing query's solutions)
   522             # we want queries such as "Any X WHERE X eid 9999"
   544                 eidkeys = self._rql_ck_cache[rql]
   523             # return an empty result instead of raising UnknownEid
   545                 if eidkeys:
   524             return empty_rset(rql, args)
   546                     # if there are some, we need a better cache key, eg (rql +
       
   547                     # entity type of each eid)
       
   548                     try:
       
   549                         cachekey = _rql_cache_key(cnx, rql, args, eidkeys)
       
   550                     except UnknownEid:
       
   551                         # we want queries such as "Any X WHERE X eid 9999"
       
   552                         # return an empty result instead of raising UnknownEid
       
   553                         return empty_rset(rql, args)
       
   554             rqlst = self._rql_cache[cachekey]
       
   555             self.cache_hit += 1
       
   556             statsd_c('cache_hit')
       
   557         except KeyError:
       
   558             self.cache_miss += 1
       
   559             statsd_c('cache_miss')
       
   560             rqlst = self.parse(rql)
       
   561             try:
       
   562                 # compute solutions for rqlst and return named args in query
       
   563                 # which are eids. Notice that if you may not need `eidkeys`, we
       
   564                 # have to compute solutions anyway (kept as annotation on the
       
   565                 # tree)
       
   566                 eidkeys = self.solutions(cnx, rqlst, args)
       
   567             except UnknownEid:
       
   568                 # we want queries such as "Any X WHERE X eid 9999" return an
       
   569                 # empty result instead of raising UnknownEid
       
   570                 return empty_rset(rql, args)
       
   571             if args and rql not in self._rql_ck_cache:
       
   572                 self._rql_ck_cache[rql] = eidkeys
       
   573                 if eidkeys:
       
   574                     cachekey = _rql_cache_key(cnx, rql, args, eidkeys)
       
   575             self._rql_cache[cachekey] = rqlst
       
   576         if rqlst.TYPE != 'select':
   525         if rqlst.TYPE != 'select':
   577             if cnx.read_security:
   526             if cnx.read_security:
   578                 check_no_password_selected(rqlst)
   527                 check_no_password_selected(rqlst)
   579             cachekey = None
   528             cachekey = None
   580         else:
   529         else:
   643     # these are overridden by set_log_methods below
   592     # these are overridden by set_log_methods below
   644     # only defining here to prevent pylint from complaining
   593     # only defining here to prevent pylint from complaining
   645     info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
   594     info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
   646 
   595 
   647 
   596 
       
   597 class RQLCache(object):
       
   598 
       
   599     def __init__(self, repo, schema):
       
   600         # rql st and solution cache.
       
   601         self._cache = QueryCache(repo.config['rql-cache-size'])
       
   602         # rql cache key cache. Don't bother using a Cache instance: we should
       
   603         # have a limited number of queries in there, since there are no entries
       
   604         # in this cache for user queries (which have no args)
       
   605         self._ck_cache = {}
       
   606         # some cache usage stats
       
   607         self.cache_hit, self.cache_miss = 0, 0
       
   608         # rql parsing / analysing helper
       
   609         self.solutions = repo.vreg.solutions
       
   610         rqlhelper = repo.vreg.rqlhelper
       
   611         # set backend on the rql helper, will be used for function checking
       
   612         rqlhelper.backend = repo.config.system_source_config['db-driver']
       
   613 
       
   614         def parse(rql, annotate=False, parse=rqlhelper.parse):
       
   615             """Return a freshly parsed syntax tree for the given RQL."""
       
   616             try:
       
   617                 return parse(text_type(rql), annotate=annotate)
       
   618             except UnicodeError:
       
   619                 raise RQLSyntaxError(rql)
       
   620         self._parse = parse
       
   621 
       
   622     def __len__(self):
       
   623         return len(self._cache)
       
   624 
       
   625     def get(self, cnx, rql, args):
       
   626         """Return syntax tree and cache key for the given RQL.
       
   627 
       
   628         Returned syntax tree is cached and must not be modified
       
   629         """
       
   630         # parse the query and binds variables
       
   631         cachekey = (rql,)
       
   632         try:
       
   633             if args:
       
   634                 # search for named args in query which are eids (hence
       
   635                 # influencing query's solutions)
       
   636                 eidkeys = self._ck_cache[rql]
       
   637                 if eidkeys:
       
   638                     # if there are some, we need a better cache key, eg (rql +
       
   639                     # entity type of each eid)
       
   640                     cachekey = _rql_cache_key(cnx, rql, args, eidkeys)
       
   641             rqlst = self._cache[cachekey]
       
   642             self.cache_hit += 1
       
   643             statsd_c('cache_hit')
       
   644         except KeyError:
       
   645             self.cache_miss += 1
       
   646             statsd_c('cache_miss')
       
   647             rqlst = self._parse(rql)
       
   648             # compute solutions for rqlst and return named args in query
       
   649             # which are eids. Notice that if you may not need `eidkeys`, we
       
   650             # have to compute solutions anyway (kept as annotation on the
       
   651             # tree)
       
   652             eidkeys = self.solutions(cnx, rqlst, args)
       
   653             if args and rql not in self._ck_cache:
       
   654                 self._ck_cache[rql] = eidkeys
       
   655                 if eidkeys:
       
   656                     cachekey = _rql_cache_key(cnx, rql, args, eidkeys)
       
   657             self._cache[cachekey] = rqlst
       
   658         return rqlst, cachekey
       
   659 
       
   660     def pop(self, key, *args):
       
   661         """Pop a key from the cache."""
       
   662         self._cache.pop(key, *args)
       
   663 
       
   664 
   648 def _rql_cache_key(cnx, rql, args, eidkeys):
   665 def _rql_cache_key(cnx, rql, args, eidkeys):
   649     cachekey = [rql]
   666     cachekey = [rql]
   650     type_from_eid = cnx.repo.type_from_eid
   667     type_from_eid = cnx.repo.type_from_eid
   651     for key in sorted(eidkeys):
   668     for key in sorted(eidkeys):
   652         try:
   669         try: