22 __docformat__ = "restructuredtext en" |
22 __docformat__ = "restructuredtext en" |
23 |
23 |
24 from logilab.common.compat import any |
24 from logilab.common.compat import any |
25 |
25 |
26 from rql import BadRQLQuery |
26 from rql import BadRQLQuery |
27 from rql.nodes import Relation, VariableRef, Constant, Variable, Or |
27 from rql.nodes import Relation, VariableRef, Constant, Variable, Or, Exists |
28 from rql.utils import common_parent |
28 from rql.utils import common_parent |
29 |
29 |
30 def _annotate_select(annotator, rqlst): |
30 def _annotate_select(annotator, rqlst): |
31 for subquery in rqlst.with_: |
31 for subquery in rqlst.with_: |
32 annotator._annotate_union(subquery.query) |
32 annotator._annotate_union(subquery.query) |
34 # print '-------- sql annotate', repr(rqlst) |
34 # print '-------- sql annotate', repr(rqlst) |
35 getrschema = annotator.schema.rschema |
35 getrschema = annotator.schema.rschema |
36 has_text_query = False |
36 has_text_query = False |
37 need_distinct = rqlst.distinct |
37 need_distinct = rqlst.distinct |
38 for rel in rqlst.iget_nodes(Relation): |
38 for rel in rqlst.iget_nodes(Relation): |
39 if getrschema(rel.r_type).symmetric and not rel.neged(strict=True): |
39 if getrschema(rel.r_type).symmetric and not isinstance(rel.parent, Exists): |
40 for vref in rel.iget_nodes(VariableRef): |
40 for vref in rel.iget_nodes(VariableRef): |
41 stinfo = vref.variable.stinfo |
41 stinfo = vref.variable.stinfo |
42 if not stinfo['constnode'] and stinfo['selected']: |
42 if not stinfo['constnode'] and stinfo['selected']: |
43 need_distinct = True |
43 need_distinct = True |
44 # XXX could mark as not invariant |
44 # XXX could mark as not invariant |
133 # we have to select a kindof "main" relation which will "extrajoins" |
133 # we have to select a kindof "main" relation which will "extrajoins" |
134 # the other |
134 # the other |
135 # priority should be given to relation which are not in inner queries |
135 # priority should be given to relation which are not in inner queries |
136 # (eg exists) |
136 # (eg exists) |
137 try: |
137 try: |
138 stinfo['principal'] = _select_principal(var.sqlscope, joins) |
138 stinfo['principal'] = _select_principal(var.scope, joins) |
139 except CantSelectPrincipal: |
139 except CantSelectPrincipal: |
140 stinfo['invariant'] = False |
140 stinfo['invariant'] = False |
141 rqlst.need_distinct = need_distinct |
141 rqlst.need_distinct = need_distinct |
142 return has_text_query |
142 return has_text_query |
143 |
143 |
144 |
144 |
145 |
145 |
146 class CantSelectPrincipal(Exception): |
146 class CantSelectPrincipal(Exception): |
147 """raised when no 'principal' variable can be found""" |
147 """raised when no 'principal' variable can be found""" |
148 |
148 |
149 def _select_principal(sqlscope, relations, _sort=lambda x:x): |
149 def _select_principal(scope, relations, _sort=lambda x:x): |
150 """given a list of rqlst relations, select one which will be used to |
150 """given a list of rqlst relations, select one which will be used to |
151 represent an invariant variable (e.g. using on extremity of the relation |
151 represent an invariant variable (e.g. using on extremity of the relation |
152 instead of the variable's type table |
152 instead of the variable's type table |
153 """ |
153 """ |
154 # _sort argument is there for test |
154 # _sort argument is there for test |
159 # note: only eid and has_text among all final relations may be there |
159 # note: only eid and has_text among all final relations may be there |
160 if rel.r_type in ('eid', 'identity'): |
160 if rel.r_type in ('eid', 'identity'): |
161 continue |
161 continue |
162 if rel.ored(traverse_scope=True): |
162 if rel.ored(traverse_scope=True): |
163 ored_rels.add(rel) |
163 ored_rels.add(rel) |
164 elif rel.sqlscope is sqlscope: |
164 elif rel.scope is scope: |
165 return rel |
165 return rel |
166 elif not rel.neged(traverse_scope=True): |
166 elif not rel.neged(traverse_scope=True): |
167 diffscope_rels.add(rel) |
167 diffscope_rels.add(rel) |
168 if len(ored_rels) > 1: |
168 if len(ored_rels) > 1: |
169 ored_rels_copy = tuple(ored_rels) |
169 ored_rels_copy = tuple(ored_rels) |
173 continue |
173 continue |
174 if isinstance(common_parent(rel1, rel2), Or): |
174 if isinstance(common_parent(rel1, rel2), Or): |
175 ored_rels.discard(rel1) |
175 ored_rels.discard(rel1) |
176 ored_rels.discard(rel2) |
176 ored_rels.discard(rel2) |
177 for rel in _sort(ored_rels): |
177 for rel in _sort(ored_rels): |
178 if rel.sqlscope is sqlscope: |
178 if rel.scope is scope: |
179 return rel |
179 return rel |
180 diffscope_rels.add(rel) |
180 diffscope_rels.add(rel) |
181 # if DISTINCT query, can use variable from a different scope as principal |
181 # if DISTINCT query, can use variable from a different scope as principal |
182 # since introduced duplicates will be removed |
182 # since introduced duplicates will be removed |
183 if sqlscope.stmt.distinct and diffscope_rels: |
183 if scope.stmt.distinct and diffscope_rels: |
184 return iter(_sort(diffscope_rels)).next() |
184 return iter(_sort(diffscope_rels)).next() |
185 # XXX could use a relation for a different scope if it can't generate |
185 # XXX could use a relation for a different scope if it can't generate |
186 # duplicates, so we would have to check cardinality |
186 # duplicates, so we would have to check cardinality |
187 raise CantSelectPrincipal() |
187 raise CantSelectPrincipal() |
188 |
188 |
195 for rel in sorted(relations, key=lambda x: (x.children[0].name, x.r_type)): |
195 for rel in sorted(relations, key=lambda x: (x.children[0].name, x.r_type)): |
196 # only equality relation with a variable as rhs may be principal |
196 # only equality relation with a variable as rhs may be principal |
197 if rel.operator() not in ('=', 'IS') \ |
197 if rel.operator() not in ('=', 'IS') \ |
198 or not isinstance(rel.children[1].children[0], VariableRef): |
198 or not isinstance(rel.children[1].children[0], VariableRef): |
199 continue |
199 continue |
200 if rel.sqlscope is rel.stmt: |
200 if rel.scope is rel.stmt: |
201 return rel |
201 return rel |
202 principal = rel |
202 principal = rel |
203 if principal is None: |
203 if principal is None: |
204 raise BadRQLQuery('unable to find principal in %s' % ', '.join( |
204 raise BadRQLQuery('unable to find principal in %s' % ', '.join( |
205 r.as_string() for r in relations)) |
205 r.as_string() for r in relations)) |
218 var._q_invariant = False |
218 var._q_invariant = False |
219 else: |
219 else: |
220 var._q_invariant = True |
220 var._q_invariant = True |
221 else: |
221 else: |
222 var._q_invariant = False |
222 var._q_invariant = False |
223 for rel in select.iget_nodes(Relation): |
|
224 if rel.neged(strict=True) and not rel.is_types_restriction(): |
|
225 rschema = getrschema(rel.r_type) |
|
226 if not rschema.final: |
|
227 # if one of the relation's variable is ambiguous but not |
|
228 # invariant, an intersection will be necessary |
|
229 for vref in rel.get_nodes(VariableRef): |
|
230 var = vref.variable |
|
231 if (not var._q_invariant and var.valuable_references() == 1 |
|
232 and len(var.stinfo['possibletypes']) > 1): |
|
233 select.need_intersect = True |
|
234 break |
|
235 else: |
|
236 continue |
|
237 break |
|
238 else: |
|
239 select.need_intersect = False |
|
240 |
223 |
241 |
224 |
242 class SQLGenAnnotator(object): |
225 class SQLGenAnnotator(object): |
243 def __init__(self, schema): |
226 def __init__(self, schema): |
244 self.schema = schema |
227 self.schema = schema |
268 return has_text_query |
251 return has_text_query |
269 |
252 |
270 def is_ambiguous(self, var): |
253 def is_ambiguous(self, var): |
271 # ignore has_text relation |
254 # ignore has_text relation |
272 if len([rel for rel in var.stinfo['relations'] |
255 if len([rel for rel in var.stinfo['relations'] |
273 if rel.sqlscope is var.sqlscope and rel.r_type == 'has_text']) == 1: |
256 if rel.scope is var.scope and rel.r_type == 'has_text']) == 1: |
274 return False |
257 return False |
275 try: |
258 try: |
276 data = var.stmt._deamb_data |
259 data = var.stmt._deamb_data |
277 except AttributeError: |
260 except AttributeError: |
278 data = var.stmt._deamb_data = IsAmbData(self.schema, self.nfdomain) |
261 data = var.stmt._deamb_data = IsAmbData(self.schema, self.nfdomain) |
351 |
334 |
352 def set_rel_constraint(self, term, rel, etypes_func): |
335 def set_rel_constraint(self, term, rel, etypes_func): |
353 if isinstance(term, VariableRef) and self.is_ambiguous(term.variable): |
336 if isinstance(term, VariableRef) and self.is_ambiguous(term.variable): |
354 var = term.variable |
337 var = term.variable |
355 if len(var.stinfo['relations']) == 1 \ |
338 if len(var.stinfo['relations']) == 1 \ |
356 or rel.sqlscope is var.sqlscope or rel.r_type == 'identity': |
339 or rel.scope is var.scope or rel.r_type == 'identity': |
357 self.restrict(var, frozenset(etypes_func())) |
340 self.restrict(var, frozenset(etypes_func())) |
358 try: |
341 try: |
359 self.maydeambrels[var].add(rel) |
342 self.maydeambrels[var].add(rel) |
360 except KeyError: |
343 except KeyError: |
361 self.maydeambrels[var] = set((rel,)) |
344 self.maydeambrels[var] = set((rel,)) |