513 self.set_schema(schema) |
513 self.set_schema(schema) |
514 |
514 |
515 def set_schema(self, schema): |
515 def set_schema(self, schema): |
516 self.schema = schema |
516 self.schema = schema |
517 repo = self._repo |
517 repo = self._repo |
|
518 # rql st and solution cache |
|
519 self._rql_cache = Cache(repo.config['rql-cache-size']) |
|
520 # rql cache key cache |
|
521 self._rql_ck_cache = Cache(repo.config['rql-cache-size']) |
|
522 # some cache usage stats |
|
523 self.cache_hit, self.cache_miss = 0, 0 |
518 # rql parsing / analysing helper |
524 # rql parsing / analysing helper |
519 self.solutions = repo.vreg.solutions |
525 self.solutions = repo.vreg.solutions |
520 self._rql_cache = Cache(repo.config['rql-cache-size']) |
526 rqlhelper = repo.vreg.rqlhelper |
521 self.cache_hit, self.cache_miss = 0, 0 |
527 self._parse = rqlhelper.parse |
|
528 self._annotate = rqlhelper.annotate |
522 # rql planner |
529 # rql planner |
523 # note: don't use repo.sources, may not be built yet, and also "admin" |
530 # note: don't use repo.sources, may not be built yet, and also "admin" |
524 # isn't an actual source |
531 # isn't an actual source |
525 rqlhelper = repo.vreg.rqlhelper |
|
526 self._parse = rqlhelper.parse |
|
527 self._annotate = rqlhelper.annotate |
|
528 if len([uri for uri in repo.config.sources() if uri != 'admin']) < 2: |
532 if len([uri for uri in repo.config.sources() if uri != 'admin']) < 2: |
529 from cubicweb.server.ssplanner import SSPlanner |
533 from cubicweb.server.ssplanner import SSPlanner |
530 self._planner = SSPlanner(schema, rqlhelper) |
534 self._planner = SSPlanner(schema, rqlhelper) |
531 else: |
535 else: |
532 from cubicweb.server.msplanner import MSPlanner |
536 from cubicweb.server.msplanner import MSPlanner |
545 """create an execution plan for an INSERT RQL query""" |
549 """create an execution plan for an INSERT RQL query""" |
546 if rqlst.TYPE == 'insert': |
550 if rqlst.TYPE == 'insert': |
547 return InsertPlan(self, rqlst, args, session) |
551 return InsertPlan(self, rqlst, args, session) |
548 return ExecutionPlan(self, rqlst, args, session) |
552 return ExecutionPlan(self, rqlst, args, session) |
549 |
553 |
550 def execute(self, session, rql, args=None, eid_key=None, build_descr=True): |
554 def execute(self, session, rql, args=None, build_descr=True): |
551 """execute a rql query, return resulting rows and their description in |
555 """execute a rql query, return resulting rows and their description in |
552 a `ResultSet` object |
556 a `ResultSet` object |
553 |
557 |
554 * `rql` should be an Unicode string or a plain ASCII string |
558 * `rql` should be an Unicode string or a plain ASCII string |
555 * `args` the optional parameters dictionary associated to the query |
559 * `args` the optional parameters dictionary associated to the query |
556 * `build_descr` is a boolean flag indicating if the description should |
560 * `build_descr` is a boolean flag indicating if the description should |
557 be built on select queries (if false, the description will be en empty |
561 be built on select queries (if false, the description will be en empty |
558 list) |
562 list) |
559 * `eid_key` must be both a key in args and a substitution in the rql |
|
560 query. It should be used to enhance cacheability of rql queries. |
|
561 It may be a tuple for keys in args. |
|
562 `eid_key` must be provided in cases where a eid substitution is provided |
|
563 and resolves ambiguities in the possible solutions inferred for each |
|
564 variable in the query. |
|
565 |
563 |
566 on INSERT queries, there will be one row with the eid of each inserted |
564 on INSERT queries, there will be one row with the eid of each inserted |
567 entity |
565 entity |
568 |
566 |
569 result for DELETE and SET queries is undefined yet |
567 result for DELETE and SET queries is undefined yet |
575 if server.DEBUG & (server.DBG_RQL | server.DBG_SQL): |
573 if server.DEBUG & (server.DBG_RQL | server.DBG_SQL): |
576 if server.DEBUG & (server.DBG_MORE | server.DBG_SQL): |
574 if server.DEBUG & (server.DBG_MORE | server.DBG_SQL): |
577 print '*'*80 |
575 print '*'*80 |
578 print 'querier input', rql, args |
576 print 'querier input', rql, args |
579 # parse the query and binds variables |
577 # parse the query and binds variables |
580 if eid_key is not None: |
578 try: |
581 if not isinstance(eid_key, (tuple, list)): |
|
582 eid_key = (eid_key,) |
|
583 cachekey = [rql] |
|
584 for key in eid_key: |
|
585 try: |
|
586 etype = self._repo.type_from_eid(args[key], session) |
|
587 except KeyError: |
|
588 raise QueryError('bad cache key %s (no value)' % key) |
|
589 except TypeError: |
|
590 raise QueryError('bad cache key %s (value: %r)' % ( |
|
591 key, args[key])) |
|
592 except UnknownEid: |
|
593 # we want queries such as "Any X WHERE X eid 9999" |
|
594 # return an empty result instead of raising UnknownEid |
|
595 return empty_rset(rql, args) |
|
596 cachekey.append(etype) |
|
597 # ensure eid is correctly typed in args |
|
598 args[key] = typed_eid(args[key]) |
|
599 cachekey = tuple(cachekey) |
|
600 else: |
|
601 cachekey = rql |
579 cachekey = rql |
602 try: |
580 if args: |
|
581 eidkeys = self._rql_ck_cache[rql] |
|
582 if eidkeys: |
|
583 try: |
|
584 cachekey = self._repo.querier_cache_key(session, rql, |
|
585 args, eidkeys) |
|
586 except UnknownEid: |
|
587 # we want queries such as "Any X WHERE X eid 9999" |
|
588 # return an empty result instead of raising UnknownEid |
|
589 return empty_rset(rql, args) |
603 rqlst = self._rql_cache[cachekey] |
590 rqlst = self._rql_cache[cachekey] |
604 self.cache_hit += 1 |
591 self.cache_hit += 1 |
605 except KeyError: |
592 except KeyError: |
606 self.cache_miss += 1 |
593 self.cache_miss += 1 |
607 rqlst = self.parse(rql) |
594 rqlst = self.parse(rql) |
608 try: |
595 try: |
609 self.solutions(session, rqlst, args) |
596 eidkeys = self.solutions(session, rqlst, args) |
610 except UnknownEid: |
597 except UnknownEid: |
611 # we want queries such as "Any X WHERE X eid 9999" return an |
598 # we want queries such as "Any X WHERE X eid 9999" return an |
612 # empty result instead of raising UnknownEid |
599 # empty result instead of raising UnknownEid |
613 return empty_rset(rql, args, rqlst) |
600 return empty_rset(rql, args, rqlst) |
|
601 self._rql_ck_cache[rql] = eidkeys |
|
602 if eidkeys: |
|
603 cachekey = self._repo.querier_cache_key(session, rql, args, |
|
604 eidkeys) |
614 self._rql_cache[cachekey] = rqlst |
605 self._rql_cache[cachekey] = rqlst |
615 orig_rqlst = rqlst |
606 orig_rqlst = rqlst |
616 if rqlst.TYPE != 'select': |
607 if rqlst.TYPE != 'select': |
617 if session.read_security: |
608 if session.read_security: |
618 check_no_password_selected(rqlst) |
609 check_no_password_selected(rqlst) |
668 todetermine = zip(xrange(len(plan.selected)), repeat(False)) |
659 todetermine = zip(xrange(len(plan.selected)), repeat(False)) |
669 descr = session._build_descr(results, basedescr, todetermine) |
660 descr = session._build_descr(results, basedescr, todetermine) |
670 # FIXME: get number of affected entities / relations on non |
661 # FIXME: get number of affected entities / relations on non |
671 # selection queries ? |
662 # selection queries ? |
672 # return a result set object |
663 # return a result set object |
673 return ResultSet(results, rql, args, descr, eid_key, orig_rqlst) |
664 return ResultSet(results, rql, args, descr, orig_rqlst) |
674 |
665 |
675 from logging import getLogger |
666 from logging import getLogger |
676 from cubicweb import set_log_methods |
667 from cubicweb import set_log_methods |
677 LOGGER = getLogger('cubicweb.querier') |
668 LOGGER = getLogger('cubicweb.querier') |
678 set_log_methods(QuerierHelper, LOGGER) |
669 set_log_methods(QuerierHelper, LOGGER) |