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: |