server/msplanner.py
branchstable
changeset 5582 3e133b29a1a4
parent 5426 0d4853a6e5ee
child 5768 1e73a466aa69
equal deleted inserted replaced
5581:0aae5216f99e 5582:3e133b29a1a4
    93 
    93 
    94 from logilab.common.compat import any
    94 from logilab.common.compat import any
    95 from logilab.common.decorators import cached
    95 from logilab.common.decorators import cached
    96 
    96 
    97 from rql.stmts import Union, Select
    97 from rql.stmts import Union, Select
    98 from rql.nodes import VariableRef, Comparison, Relation, Constant, Variable
    98 from rql.nodes import (VariableRef, Comparison, Relation, Constant, Variable,
       
    99                        Not, Exists)
    99 
   100 
   100 from cubicweb import server
   101 from cubicweb import server
   101 from cubicweb.utils import make_uid
   102 from cubicweb.utils import make_uid
   102 from cubicweb.server.utils import cleanup_solutions
   103 from cubicweb.server.utils import cleanup_solutions
   103 from cubicweb.server.ssplanner import (SSPlanner, OneFetchStep,
   104 from cubicweb.server.ssplanner import (SSPlanner, OneFetchStep,
   106 
   107 
   107 Variable._ms_table_key = lambda x: x.name
   108 Variable._ms_table_key = lambda x: x.name
   108 Relation._ms_table_key = lambda x: x.r_type
   109 Relation._ms_table_key = lambda x: x.r_type
   109 # str() Constant.value to ensure generated table name won't be unicode
   110 # str() Constant.value to ensure generated table name won't be unicode
   110 Constant._ms_table_key = lambda x: str(x.value)
   111 Constant._ms_table_key = lambda x: str(x.value)
       
   112 
       
   113 def ms_scope(term):
       
   114     rel = None
       
   115     scope = term.scope
       
   116     if isinstance(term, Variable) and len(term.stinfo['relations']) == 1:
       
   117         rel = iter(term.stinfo['relations']).next().relation()
       
   118     elif isinstance(term, Constant):
       
   119         rel = term.relation()
       
   120     elif isinstance(term, Relation):
       
   121         rel = term
       
   122     if rel is not None and (
       
   123         rel.r_type != 'identity' and rel.scope is scope
       
   124         and isinstance(rel.parent, Exists) and rel.parent.neged(strict=True)):
       
   125         return scope.parent.scope
       
   126     return scope
       
   127 
       
   128 def need_intersect(select, getrschema):
       
   129     for rel in select.iget_nodes(Relation):
       
   130         if isinstance(rel.parent, Exists) and rel.parent.neged(strict=True) and not rel.is_types_restriction():
       
   131             rschema = getrschema(rel.r_type)
       
   132             if not rschema.final:
       
   133                 # if one of the relation's variable is ambiguous but not
       
   134                 # invariant, an intersection will be necessary
       
   135                 for vref in rel.get_nodes(VariableRef):
       
   136                     var = vref.variable
       
   137                     if (var.valuable_references() == 1
       
   138                         and len(var.stinfo['possibletypes']) > 1):
       
   139                         return True
       
   140     return False
       
   141 
       
   142 def neged_relation(rel):
       
   143     parent = rel.parent
       
   144     return isinstance(parent, Not) or (isinstance(parent, Exists) and
       
   145                                        isinstance(parent.parent, Not))
   111 
   146 
   112 def need_source_access_relation(vargraph):
   147 def need_source_access_relation(vargraph):
   113     if not vargraph:
   148     if not vargraph:
   114         return False
   149         return False
   115     # check vargraph contains some other relation than the identity relation
   150     # check vargraph contains some other relation than the identity relation
   193 
   228 
   194 def used_in_outer_scope(var, scope):
   229 def used_in_outer_scope(var, scope):
   195     """return true if the variable is used in an outer scope of the given scope
   230     """return true if the variable is used in an outer scope of the given scope
   196     """
   231     """
   197     for rel in var.stinfo['relations']:
   232     for rel in var.stinfo['relations']:
   198         rscope = rel.scope
   233         rscope = ms_scope(rel)
   199         if not rscope is scope and is_ancestor(scope, rscope):
   234         if not rscope is scope and is_ancestor(scope, rscope):
   200             return True
   235             return True
   201     return False
   236     return False
   202 
   237 
   203 ################################################################################
   238 ################################################################################
   376                 for const in vconsts:
   411                 for const in vconsts:
   377                     self._set_source_for_term(source, const)
   412                     self._set_source_for_term(source, const)
   378             elif not self._sourcesterms:
   413             elif not self._sourcesterms:
   379                 self._set_source_for_term(source, const)
   414                 self._set_source_for_term(source, const)
   380             elif source in self._sourcesterms:
   415             elif source in self._sourcesterms:
   381                 source_scopes = frozenset(t.scope for t in self._sourcesterms[source])
   416                 source_scopes = frozenset(ms_scope(t) for t in self._sourcesterms[source])
   382                 for const in vconsts:
   417                 for const in vconsts:
   383                     if const.scope in source_scopes:
   418                     if ms_scope(const) in source_scopes:
   384                         self._set_source_for_term(source, const)
   419                         self._set_source_for_term(source, const)
   385                         # if system source is used, add every rewritten constant
   420                         # if system source is used, add every rewritten constant
   386                         # to its supported terms even when associated entity
   421                         # to its supported terms even when associated entity
   387                         # doesn't actually come from it so we get a changes
   422                         # doesn't actually come from it so we get a changes
   388                         # that allequals will return True as expected when
   423                         # that allequals will return True as expected when
   503         return termv
   538         return termv
   504 
   539 
   505     def _remove_sources_until_stable(self, term, termssources):
   540     def _remove_sources_until_stable(self, term, termssources):
   506         sourcesterms = self._sourcesterms
   541         sourcesterms = self._sourcesterms
   507         for oterm, rel in self._linkedterms.get(term, ()):
   542         for oterm, rel in self._linkedterms.get(term, ()):
   508             if not term.scope is oterm.scope and rel.scope.neged(strict=True):
   543             tscope = ms_scope(term)
       
   544             otscope = ms_scope(oterm)
       
   545             rscope = ms_scope(rel)
       
   546             if not tscope is otscope and rscope.neged(strict=True):
   509                 # can't get information from relation inside a NOT exists
   547                 # can't get information from relation inside a NOT exists
   510                 # where terms don't belong to the same scope
   548                 # where terms don't belong to the same scope
   511                 continue
   549                 continue
   512             need_ancestor_scope = False
   550             need_ancestor_scope = False
   513             if not (term.scope is rel.scope and oterm.scope is rel.scope):
   551             if not (tscope is rscope and otscope is rscope):
   514                 if rel.ored():
   552                 if rel.ored():
   515                     continue
   553                     continue
   516                 if rel.ored(traverse_scope=True):
   554                 if rel.ored(traverse_scope=True):
   517                     # if relation has some OR as parent, constraints should only
   555                     # if relation has some OR as parent, constraints should only
   518                     # propagate from parent scope to child scope, nothing else
   556                     # propagate from parent scope to child scope, nothing else
   519                     need_ancestor_scope = True
   557                     need_ancestor_scope = True
   520             relsources = self._repo.rel_type_sources(rel.r_type)
   558             relsources = self._repo.rel_type_sources(rel.r_type)
   521             if rel.neged(strict=True) and (
   559             if neged_relation(rel) and (
   522                 len(relsources) < 2
   560                 len(relsources) < 2
   523                 or not isinstance(oterm, Variable)
   561                 or not isinstance(oterm, Variable)
   524                 or oterm.valuable_references() != 1
   562                 or oterm.valuable_references() != 1
   525                 or any(sourcesterms[source][term] != sourcesterms[source][oterm]
   563                 or any(sourcesterms[source][term] != sourcesterms[source][oterm]
   526                        for source in relsources
   564                        for source in relsources
   530                 # we're on a multisource relation for a term only used by this
   568                 # we're on a multisource relation for a term only used by this
   531                 # relation (eg "Any X WHERE NOT X multisource_rel Y" and over is
   569                 # relation (eg "Any X WHERE NOT X multisource_rel Y" and over is
   532                 # Y)
   570                 # Y)
   533                 continue
   571                 continue
   534             # compute invalid sources for terms and remove them
   572             # compute invalid sources for terms and remove them
   535             if not need_ancestor_scope or is_ancestor(term.scope, oterm.scope):
   573             if not need_ancestor_scope or is_ancestor(tscope, otscope):
   536                 self._remove_term_sources(term, rel, oterm, termssources)
   574                 self._remove_term_sources(term, rel, oterm, termssources)
   537             if not need_ancestor_scope or is_ancestor(oterm.scope, term.scope):
   575             if not need_ancestor_scope or is_ancestor(otscope, tscope):
   538                 self._remove_term_sources(oterm, rel, term, termssources)
   576                 self._remove_term_sources(oterm, rel, term, termssources)
   539 
   577 
   540     def _remove_term_sources(self, term, rel, oterm, termssources):
   578     def _remove_term_sources(self, term, rel, oterm, termssources):
   541         """remove invalid sources for term according to oterm's sources and the
   579         """remove invalid sources for term according to oterm's sources and the
   542         relation between those two terms.
   580         relation between those two terms.
   691                     scope = select
   729                     scope = select
   692                     terms = scope.defined_vars.values() + scope.aliases.values()
   730                     terms = scope.defined_vars.values() + scope.aliases.values()
   693                     sourceterms.clear()
   731                     sourceterms.clear()
   694                     sources = [source]
   732                     sources = [source]
   695                 else:
   733                 else:
   696                     scope = term.scope
   734                     scope = ms_scope(term)
   697                     # find which sources support the same term and solutions
   735                     # find which sources support the same term and solutions
   698                     sources = self._expand_sources(source, term, solindices)
   736                     sources = self._expand_sources(source, term, solindices)
   699                     # no try to get as much terms as possible
   737                     # no try to get as much terms as possible
   700                     terms = self._expand_terms(term, sources, sourceterms,
   738                     terms = self._expand_terms(term, sources, sourceterms,
   701                                                scope, solindices)
   739                                                scope, solindices)
   777                             # supporting the associated entity, this step can't
   815                             # supporting the associated entity, this step can't
   778                             # be final (unless the relation is explicitly in
   816                             # be final (unless the relation is explicitly in
   779                             # `terms`, eg cross relations)
   817                             # `terms`, eg cross relations)
   780                             for c in vconsts:
   818                             for c in vconsts:
   781                                 rel = c.relation()
   819                                 rel = c.relation()
   782                                 if rel is None or not (rel in terms or rel.neged(strict=True)):
   820                                 if rel is None or not (rel in terms or neged_relation(rel)):
   783                                     final = False
   821                                     final = False
   784                                     break
   822                                     break
   785                             break
   823                             break
   786                 if final:
   824                 if final:
   787                     self._cleanup_sourcesterms(sources, solindices)
   825                     self._cleanup_sourcesterms(sources, solindices)
   800             # supported relation with at least one end supported, check the
   838             # supported relation with at least one end supported, check the
   801             # other end is in as well. If not this usually means the
   839             # other end is in as well. If not this usually means the
   802             # variable is refed by an outer scope and should be substituted
   840             # variable is refed by an outer scope and should be substituted
   803             # using an 'identity' relation (else we'll get a conflict of
   841             # using an 'identity' relation (else we'll get a conflict of
   804             # temporary tables)
   842             # temporary tables)
   805             if rhsvar in terms and not lhsvar in terms and lhsvar.scope is lhsvar.stmt:
   843             if rhsvar in terms and not lhsvar in terms and ms_scope(lhsvar) is lhsvar.stmt:
   806                 self._identity_substitute(rel, lhsvar, terms, needsel)
   844                 self._identity_substitute(rel, lhsvar, terms, needsel)
   807             elif lhsvar in terms and not rhsvar in terms and rhsvar.scope is rhsvar.stmt:
   845             elif lhsvar in terms and not rhsvar in terms and ms_scope(rhsvar) is rhsvar.stmt:
   808                 self._identity_substitute(rel, rhsvar, terms, needsel)
   846                 self._identity_substitute(rel, rhsvar, terms, needsel)
   809 
   847 
   810     def _identity_substitute(self, relation, var, terms, needsel):
   848     def _identity_substitute(self, relation, var, terms, needsel):
   811         newvar = self._insert_identity_variable(relation.scope, var)
   849         newvar = self._insert_identity_variable(ms_scope(relation), var)
   812         # ensure relation is using '=' operator, else we rely on a
   850         # ensure relation is using '=' operator, else we rely on a
   813         # sqlgenerator side effect (it won't insert an inequality operator
   851         # sqlgenerator side effect (it won't insert an inequality operator
   814         # in this case)
   852         # in this case)
   815         relation.children[1].operator = '='
   853         relation.children[1].operator = '='
   816         terms.append(newvar)
   854         terms.append(newvar)
   822         """
   860         """
   823         secondchoice = None
   861         secondchoice = None
   824         if len(self._sourcesterms) > 1:
   862         if len(self._sourcesterms) > 1:
   825             # priority to variable from subscopes
   863             # priority to variable from subscopes
   826             for term in sourceterms:
   864             for term in sourceterms:
   827                 if not term.scope is self.rqlst:
   865                 if not ms_scope(term) is self.rqlst:
   828                     if isinstance(term, Variable):
   866                     if isinstance(term, Variable):
   829                         return term, sourceterms.pop(term)
   867                         return term, sourceterms.pop(term)
   830                     secondchoice = term
   868                     secondchoice = term
   831         else:
   869         else:
   832             # priority to variable from outer scope
   870             # priority to variable from outer scope
   833             for term in sourceterms:
   871             for term in sourceterms:
   834                 if term.scope is self.rqlst:
   872                 if ms_scope(term) is self.rqlst:
   835                     if isinstance(term, Variable):
   873                     if isinstance(term, Variable):
   836                         return term, sourceterms.pop(term)
   874                         return term, sourceterms.pop(term)
   837                     secondchoice = term
   875                     secondchoice = term
   838         if secondchoice is not None:
   876         if secondchoice is not None:
   839             return secondchoice, sourceterms.pop(secondchoice)
   877             return secondchoice, sourceterms.pop(secondchoice)
   879         sourcesterms = self._sourcesterms
   917         sourcesterms = self._sourcesterms
   880         linkedterms = self._linkedterms
   918         linkedterms = self._linkedterms
   881         # term has to belong to the same scope if there is more
   919         # term has to belong to the same scope if there is more
   882         # than the system source remaining
   920         # than the system source remaining
   883         if len(sourcesterms) > 1 and not scope is self.rqlst:
   921         if len(sourcesterms) > 1 and not scope is self.rqlst:
   884             candidates = (t for t in sourceterms.keys() if scope is t.scope)
   922             candidates = (t for t in sourceterms.keys() if scope is ms_scope(t))
   885         else:
   923         else:
   886             candidates = sourceterms #.iterkeys()
   924             candidates = sourceterms #.iterkeys()
   887         # we only want one unlinked term in each generated query
   925         # we only want one unlinked term in each generated query
   888         candidates = [t for t in candidates
   926         candidates = [t for t in candidates
   889                       if isinstance(t, (Constant, Relation)) or
   927                       if isinstance(t, (Constant, Relation)) or
  1198         # finally: join parts, deal with aggregat/group/sorts if necessary
  1236         # finally: join parts, deal with aggregat/group/sorts if necessary
  1199         if atemptable is not None:
  1237         if atemptable is not None:
  1200             step = AggrStep(plan, selection, select, atemptable, temptable)
  1238             step = AggrStep(plan, selection, select, atemptable, temptable)
  1201             step.children = steps
  1239             step.children = steps
  1202         elif len(steps) > 1:
  1240         elif len(steps) > 1:
  1203             if select.need_intersect or any(select.need_intersect
  1241             getrschema = self.schema.rschema
  1204                                             for step in steps
  1242             if need_intersect(select, getrschema) or any(need_intersect(select, getrschema)
  1205                                             for select in step.union.children):
  1243                                                          for step in steps
       
  1244                                                          for select in step.union.children):
  1206                 if temptable:
  1245                 if temptable:
  1207                     step = IntersectFetchStep(plan) # XXX not implemented
  1246                     step = IntersectFetchStep(plan) # XXX not implemented
  1208                 else:
  1247                 else:
  1209                     step = IntersectStep(plan)
  1248                     step = IntersectStep(plan)
  1210             else:
  1249             else: