server/rqlannotation.py
branchstable
changeset 5582 3e133b29a1a4
parent 5426 0d4853a6e5ee
child 6297 23c1e50ff97b
equal deleted inserted replaced
5581:0aae5216f99e 5582:3e133b29a1a4
    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,))