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