[rql2sql] follow rql 0.26.1 changes: NOT nodes normalization, allowing simplification of sql generation, and fix #XXX stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 26 May 2010 10:28:48 +0200
branchstable
changeset 5582 3e133b29a1a4
parent 5581 0aae5216f99e
child 5583 24125df012f3
[rql2sql] follow rql 0.26.1 changes: NOT nodes normalization, allowing simplification of sql generation, and fix #XXX
debian/control
rqlrewrite.py
server/msplanner.py
server/rqlannotation.py
server/sources/pyrorql.py
server/sources/rql2sql.py
server/test/unittest_msplanner.py
server/test/unittest_multisources.py
server/test/unittest_repository.py
server/test/unittest_rql2sql.py
test/unittest_entity.py
test/unittest_rqlrewrite.py
web/test/unittest_views_searchrestriction.py
--- a/debian/control	Tue May 25 12:21:17 2010 +0200
+++ b/debian/control	Wed May 26 10:28:48 2010 +0200
@@ -97,7 +97,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.50.2), python-yams (>= 0.29.0), python-rql (>= 0.26.0), python-lxml
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.50.2), python-yams (>= 0.29.0), python-rql (>= 0.26.1), python-lxml
 Recommends: python-simpletal (>= 4.0), python-crypto
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/rqlrewrite.py	Tue May 25 12:21:17 2010 +0200
+++ b/rqlrewrite.py	Wed May 26 10:28:48 2010 +0200
@@ -155,7 +155,7 @@
         snippets: (varmap, list of rql expression)
                   with varmap a *tuple* (select var, snippet var)
         """
-        self.select = self.insert_scope = select
+        self.select = select
         self.solutions = solutions
         self.kwargs = kwargs
         self.u_varname = None
@@ -163,6 +163,7 @@
         self.exists_snippet = {}
         self.pending_keys = []
         self.existingvars = existingvars
+        self._insert_scope = None
         # we have to annotate the rqlst before inserting snippets, even though
         # we'll have to redo it latter
         self.annotate(select)
@@ -249,15 +250,19 @@
 
     def _insert_snippet(self, varmap, parent, new):
         if new is not None:
+            if self._insert_scope is None:
+                insert_scope = self.varinfo.get('stinfo', {}).get('scope', self.select)
+            else:
+                insert_scope = self._insert_scope
             if self.varinfo.get('stinfo', {}).get('optrelations'):
                 assert parent is None
-                self.insert_scope = self.snippet_subquery(varmap, new)
+                self._insert_scope = self.snippet_subquery(varmap, new)
                 self.insert_pending()
-                self.insert_scope = self.select
+                self._insert_scope = None
                 return
             new = n.Exists(new)
             if parent is None:
-                self.insert_scope.add_restriction(new)
+                insert_scope.add_restriction(new)
             else:
                 grandpa = parent.parent
                 or_ = n.Or(parent, new)
@@ -274,9 +279,9 @@
                         self._cleanup_inserted(new)
                     raise
                 else:
-                    self.insert_scope = new
+                    self._insert_scope = new
                     self.insert_pending()
-                    self.insert_scope = self.select
+                    self._insert_scope = None
             return new
         self.insert_pending()
 
--- a/server/msplanner.py	Tue May 25 12:21:17 2010 +0200
+++ b/server/msplanner.py	Wed May 26 10:28:48 2010 +0200
@@ -95,7 +95,8 @@
 from logilab.common.decorators import cached
 
 from rql.stmts import Union, Select
-from rql.nodes import VariableRef, Comparison, Relation, Constant, Variable
+from rql.nodes import (VariableRef, Comparison, Relation, Constant, Variable,
+                       Not, Exists)
 
 from cubicweb import server
 from cubicweb.utils import make_uid
@@ -109,6 +110,40 @@
 # str() Constant.value to ensure generated table name won't be unicode
 Constant._ms_table_key = lambda x: str(x.value)
 
+def ms_scope(term):
+    rel = None
+    scope = term.scope
+    if isinstance(term, Variable) and len(term.stinfo['relations']) == 1:
+        rel = iter(term.stinfo['relations']).next().relation()
+    elif isinstance(term, Constant):
+        rel = term.relation()
+    elif isinstance(term, Relation):
+        rel = term
+    if rel is not None and (
+        rel.r_type != 'identity' and rel.scope is scope
+        and isinstance(rel.parent, Exists) and rel.parent.neged(strict=True)):
+        return scope.parent.scope
+    return scope
+
+def need_intersect(select, getrschema):
+    for rel in select.iget_nodes(Relation):
+        if isinstance(rel.parent, Exists) and rel.parent.neged(strict=True) and not rel.is_types_restriction():
+            rschema = getrschema(rel.r_type)
+            if not rschema.final:
+                # if one of the relation's variable is ambiguous but not
+                # invariant, an intersection will be necessary
+                for vref in rel.get_nodes(VariableRef):
+                    var = vref.variable
+                    if (var.valuable_references() == 1
+                        and len(var.stinfo['possibletypes']) > 1):
+                        return True
+    return False
+
+def neged_relation(rel):
+    parent = rel.parent
+    return isinstance(parent, Not) or (isinstance(parent, Exists) and
+                                       isinstance(parent.parent, Not))
+
 def need_source_access_relation(vargraph):
     if not vargraph:
         return False
@@ -195,7 +230,7 @@
     """return true if the variable is used in an outer scope of the given scope
     """
     for rel in var.stinfo['relations']:
-        rscope = rel.scope
+        rscope = ms_scope(rel)
         if not rscope is scope and is_ancestor(scope, rscope):
             return True
     return False
@@ -378,9 +413,9 @@
             elif not self._sourcesterms:
                 self._set_source_for_term(source, const)
             elif source in self._sourcesterms:
-                source_scopes = frozenset(t.scope for t in self._sourcesterms[source])
+                source_scopes = frozenset(ms_scope(t) for t in self._sourcesterms[source])
                 for const in vconsts:
-                    if const.scope in source_scopes:
+                    if ms_scope(const) in source_scopes:
                         self._set_source_for_term(source, const)
                         # if system source is used, add every rewritten constant
                         # to its supported terms even when associated entity
@@ -505,12 +540,15 @@
     def _remove_sources_until_stable(self, term, termssources):
         sourcesterms = self._sourcesterms
         for oterm, rel in self._linkedterms.get(term, ()):
-            if not term.scope is oterm.scope and rel.scope.neged(strict=True):
+            tscope = ms_scope(term)
+            otscope = ms_scope(oterm)
+            rscope = ms_scope(rel)
+            if not tscope is otscope and rscope.neged(strict=True):
                 # can't get information from relation inside a NOT exists
                 # where terms don't belong to the same scope
                 continue
             need_ancestor_scope = False
-            if not (term.scope is rel.scope and oterm.scope is rel.scope):
+            if not (tscope is rscope and otscope is rscope):
                 if rel.ored():
                     continue
                 if rel.ored(traverse_scope=True):
@@ -518,7 +556,7 @@
                     # propagate from parent scope to child scope, nothing else
                     need_ancestor_scope = True
             relsources = self._repo.rel_type_sources(rel.r_type)
-            if rel.neged(strict=True) and (
+            if neged_relation(rel) and (
                 len(relsources) < 2
                 or not isinstance(oterm, Variable)
                 or oterm.valuable_references() != 1
@@ -532,9 +570,9 @@
                 # Y)
                 continue
             # compute invalid sources for terms and remove them
-            if not need_ancestor_scope or is_ancestor(term.scope, oterm.scope):
+            if not need_ancestor_scope or is_ancestor(tscope, otscope):
                 self._remove_term_sources(term, rel, oterm, termssources)
-            if not need_ancestor_scope or is_ancestor(oterm.scope, term.scope):
+            if not need_ancestor_scope or is_ancestor(otscope, tscope):
                 self._remove_term_sources(oterm, rel, term, termssources)
 
     def _remove_term_sources(self, term, rel, oterm, termssources):
@@ -693,7 +731,7 @@
                     sourceterms.clear()
                     sources = [source]
                 else:
-                    scope = term.scope
+                    scope = ms_scope(term)
                     # find which sources support the same term and solutions
                     sources = self._expand_sources(source, term, solindices)
                     # no try to get as much terms as possible
@@ -779,7 +817,7 @@
                             # `terms`, eg cross relations)
                             for c in vconsts:
                                 rel = c.relation()
-                                if rel is None or not (rel in terms or rel.neged(strict=True)):
+                                if rel is None or not (rel in terms or neged_relation(rel)):
                                     final = False
                                     break
                             break
@@ -802,13 +840,13 @@
             # variable is refed by an outer scope and should be substituted
             # using an 'identity' relation (else we'll get a conflict of
             # temporary tables)
-            if rhsvar in terms and not lhsvar in terms and lhsvar.scope is lhsvar.stmt:
+            if rhsvar in terms and not lhsvar in terms and ms_scope(lhsvar) is lhsvar.stmt:
                 self._identity_substitute(rel, lhsvar, terms, needsel)
-            elif lhsvar in terms and not rhsvar in terms and rhsvar.scope is rhsvar.stmt:
+            elif lhsvar in terms and not rhsvar in terms and ms_scope(rhsvar) is rhsvar.stmt:
                 self._identity_substitute(rel, rhsvar, terms, needsel)
 
     def _identity_substitute(self, relation, var, terms, needsel):
-        newvar = self._insert_identity_variable(relation.scope, var)
+        newvar = self._insert_identity_variable(ms_scope(relation), var)
         # ensure relation is using '=' operator, else we rely on a
         # sqlgenerator side effect (it won't insert an inequality operator
         # in this case)
@@ -824,14 +862,14 @@
         if len(self._sourcesterms) > 1:
             # priority to variable from subscopes
             for term in sourceterms:
-                if not term.scope is self.rqlst:
+                if not ms_scope(term) is self.rqlst:
                     if isinstance(term, Variable):
                         return term, sourceterms.pop(term)
                     secondchoice = term
         else:
             # priority to variable from outer scope
             for term in sourceterms:
-                if term.scope is self.rqlst:
+                if ms_scope(term) is self.rqlst:
                     if isinstance(term, Variable):
                         return term, sourceterms.pop(term)
                     secondchoice = term
@@ -881,7 +919,7 @@
         # term has to belong to the same scope if there is more
         # than the system source remaining
         if len(sourcesterms) > 1 and not scope is self.rqlst:
-            candidates = (t for t in sourceterms.keys() if scope is t.scope)
+            candidates = (t for t in sourceterms.keys() if scope is ms_scope(t))
         else:
             candidates = sourceterms #.iterkeys()
         # we only want one unlinked term in each generated query
@@ -1200,9 +1238,10 @@
             step = AggrStep(plan, selection, select, atemptable, temptable)
             step.children = steps
         elif len(steps) > 1:
-            if select.need_intersect or any(select.need_intersect
-                                            for step in steps
-                                            for select in step.union.children):
+            getrschema = self.schema.rschema
+            if need_intersect(select, getrschema) or any(need_intersect(select, getrschema)
+                                                         for step in steps
+                                                         for select in step.union.children):
                 if temptable:
                     step = IntersectFetchStep(plan) # XXX not implemented
                 else:
--- a/server/rqlannotation.py	Tue May 25 12:21:17 2010 +0200
+++ b/server/rqlannotation.py	Wed May 26 10:28:48 2010 +0200
@@ -24,7 +24,7 @@
 from logilab.common.compat import any
 
 from rql import BadRQLQuery
-from rql.nodes import Relation, VariableRef, Constant, Variable, Or
+from rql.nodes import Relation, VariableRef, Constant, Variable, Or, Exists
 from rql.utils import common_parent
 
 def _annotate_select(annotator, rqlst):
@@ -36,7 +36,7 @@
     has_text_query = False
     need_distinct = rqlst.distinct
     for rel in rqlst.iget_nodes(Relation):
-        if getrschema(rel.r_type).symmetric and not rel.neged(strict=True):
+        if getrschema(rel.r_type).symmetric and not isinstance(rel.parent, Exists):
             for vref in rel.iget_nodes(VariableRef):
                 stinfo = vref.variable.stinfo
                 if not stinfo['constnode'] and stinfo['selected']:
@@ -135,7 +135,7 @@
             # priority should be given to relation which are not in inner queries
             # (eg exists)
             try:
-                stinfo['principal'] = _select_principal(var.sqlscope, joins)
+                stinfo['principal'] = _select_principal(var.scope, joins)
             except CantSelectPrincipal:
                 stinfo['invariant'] = False
     rqlst.need_distinct = need_distinct
@@ -146,7 +146,7 @@
 class CantSelectPrincipal(Exception):
     """raised when no 'principal' variable can be found"""
 
-def _select_principal(sqlscope, relations, _sort=lambda x:x):
+def _select_principal(scope, relations, _sort=lambda x:x):
     """given a list of rqlst relations, select one which will be used to
     represent an invariant variable (e.g. using on extremity of the relation
     instead of the variable's type table
@@ -161,7 +161,7 @@
             continue
         if rel.ored(traverse_scope=True):
             ored_rels.add(rel)
-        elif rel.sqlscope is sqlscope:
+        elif rel.scope is scope:
             return rel
         elif not rel.neged(traverse_scope=True):
             diffscope_rels.add(rel)
@@ -175,12 +175,12 @@
                     ored_rels.discard(rel1)
                     ored_rels.discard(rel2)
     for rel in _sort(ored_rels):
-        if rel.sqlscope is sqlscope:
+        if rel.scope is scope:
             return rel
         diffscope_rels.add(rel)
     # if DISTINCT query, can use variable from a different scope as principal
     # since introduced duplicates will be removed
-    if sqlscope.stmt.distinct and diffscope_rels:
+    if scope.stmt.distinct and diffscope_rels:
         return iter(_sort(diffscope_rels)).next()
     # XXX  could use a relation for a different scope if it can't generate
     # duplicates, so we would have to check cardinality
@@ -197,7 +197,7 @@
         if rel.operator() not in ('=', 'IS') \
                or not isinstance(rel.children[1].children[0], VariableRef):
             continue
-        if rel.sqlscope is rel.stmt:
+        if rel.scope is rel.stmt:
             return rel
         principal = rel
     if principal is None:
@@ -220,23 +220,6 @@
                     var._q_invariant = True
             else:
                 var._q_invariant = False
-        for rel in select.iget_nodes(Relation):
-            if rel.neged(strict=True) and not rel.is_types_restriction():
-                rschema = getrschema(rel.r_type)
-                if not rschema.final:
-                    # if one of the relation's variable is ambiguous but not
-                    # invariant, an intersection will be necessary
-                    for vref in rel.get_nodes(VariableRef):
-                        var = vref.variable
-                        if (not var._q_invariant and var.valuable_references() == 1
-                            and len(var.stinfo['possibletypes']) > 1):
-                            select.need_intersect = True
-                            break
-                    else:
-                        continue
-                    break
-        else:
-            select.need_intersect = False
 
 
 class SQLGenAnnotator(object):
@@ -270,7 +253,7 @@
     def is_ambiguous(self, var):
         # ignore has_text relation
         if len([rel for rel in var.stinfo['relations']
-                if rel.sqlscope is var.sqlscope and rel.r_type == 'has_text']) == 1:
+                if rel.scope is var.scope and rel.r_type == 'has_text']) == 1:
             return False
         try:
             data = var.stmt._deamb_data
@@ -353,7 +336,7 @@
         if isinstance(term, VariableRef) and self.is_ambiguous(term.variable):
             var = term.variable
             if len(var.stinfo['relations']) == 1 \
-                   or rel.sqlscope is var.sqlscope or rel.r_type == 'identity':
+                   or rel.scope is var.scope or rel.r_type == 'identity':
                 self.restrict(var, frozenset(etypes_func()))
                 try:
                     self.maydeambrels[var].add(rel)
--- a/server/sources/pyrorql.py	Tue May 25 12:21:17 2010 +0200
+++ b/server/sources/pyrorql.py	Wed May 26 10:28:48 2010 +0200
@@ -38,7 +38,7 @@
 from cubicweb.cwconfig import register_persistent_options
 from cubicweb.server.sources import (AbstractSource, ConnectionWrapper,
                                      TimedCache, dbg_st_search, dbg_results)
-
+from cubicweb.server.msplanner import neged_relation
 
 def uidtype(union, col, etype, args):
     select, col = union.locate_subquery(col, etype, args)
@@ -476,7 +476,10 @@
         return
 
     def visit_exists(self, node):
-        return 'EXISTS(%s)' % node.children[0].accept(self)
+        rql = node.children[0].accept(self)
+        if rql:
+            return 'EXISTS(%s)' % rql
+        return
 
     def visit_relation(self, node):
         try:
@@ -486,7 +489,7 @@
                     restr, lhs = self.process_eid_const(node.children[0])
                 except UnknownEid:
                     # can safely skip not relation with an unsupported eid
-                    if node.neged(strict=True):
+                    if neged_relation(node):
                         return
                     raise
             else:
@@ -494,7 +497,7 @@
                 restr = None
         except UnknownEid:
             # can safely skip not relation with an unsupported eid
-            if node.neged(strict=True):
+            if neged_relation(node):
                 return
             # XXX what about optional relation or outer NOT EXISTS()
             raise
@@ -511,7 +514,7 @@
             rhs = node.children[1].accept(self)
         except UnknownEid:
             # can safely skip not relation with an unsupported eid
-            if node.neged(strict=True):
+            if neged_relation(node):
                 return
             # XXX what about optional relation or outer NOT EXISTS()
             raise
--- a/server/sources/rql2sql.py	Tue May 25 12:21:17 2010 +0200
+++ b/server/sources/rql2sql.py	Wed May 26 10:28:48 2010 +0200
@@ -44,7 +44,7 @@
 by Troels Arvin. Features SQL ISO Standard, PG, mysql, Oracle, MS SQL, DB2
 and Informix.
 
-.. _Comparison of different SQL implementations: http://www.troels.arvin.dk/db/rdbms 
+.. _Comparison of different SQL implementations: http://www.troels.arvin.dk/db/rdbms
 
 
 """
@@ -112,7 +112,7 @@
         unstable.remove(varname)
         torewrite.add(var)
         newselect = Select()
-        newselect.need_distinct = newselect.need_intersect = False
+        newselect.need_distinct = False
         myunion = Union()
         myunion.append(newselect)
         # extract aliases / selection
@@ -316,13 +316,15 @@
 # IGenerator implementation for RQL->SQL #######################################
 
 class StateInfo(object):
-    def __init__(self, existssols, unstablevars):
+    def __init__(self, select, existssols, unstablevars):
         self.existssols = existssols
         self.unstablevars = unstablevars
         self.subtables = {}
         self.needs_source_cb = None
         self.subquery_source_cb = None
         self.source_cb_funcs = set()
+        self.scopes = {select: 0}
+        self.scope_nodes = []
 
     def reset(self, solution):
         """reset some visit variables"""
@@ -381,12 +383,16 @@
         self.solution = origsol
         self.tables = origtables
 
-    def push_scope(self):
+    def push_scope(self, scope_node):
+        self.scope_nodes.append(scope_node)
+        self.scopes[scope_node] = len(self.actual_tables)
         self.actual_tables.append([])
         self._restr_stack.append(self.restrictions)
         self.restrictions = []
 
     def pop_scope(self):
+        del self.scopes[self.scope_nodes[-1]]
+        self.scope_nodes.pop()
         restrictions = self.restrictions
         self.restrictions = self._restr_stack.pop()
         return restrictions, self.actual_tables.pop()
@@ -442,7 +448,7 @@
         self._varmap = varmap
         self._query_attrs = {}
         self._state = None
-        self._not_scope_offset = 0
+        # self._not_scope_offset = 0
         try:
             # union query for each rqlst / solution
             sql = self.union_sql(union)
@@ -509,7 +515,7 @@
                     needwrap = True
         else:
             existssols, unstable = {}, ()
-        state = StateInfo(existssols, unstable)
+        state = StateInfo(select, existssols, unstable)
         if self._state is not None:
             # state from a previous unioned select
             state.merge_source_cbs(self._state.needs_source_cb)
@@ -622,12 +628,7 @@
             elif self._state.restrictions and self.dbhelper.needs_from_clause:
                 sql.insert(1, 'FROM (SELECT 1) AS _T')
             sqls.append('\n'.join(sql))
-        if select.need_intersect:
-            #if distinct or not self.dbhelper.intersect_all_support:
-            return '\nINTERSECT\n'.join(sqls)
-            #else:
-            #    return '\nINTERSECT ALL\n'.join(sqls)
-        elif distinct:
+        if distinct:
             return '\nUNION\n'.join(sqls)
         else:
             return '\nUNION ALL\n'.join(sqls)
@@ -682,32 +683,11 @@
         return ''
 
     def visit_not(self, node):
-        self._state.push_scope()
-        if isinstance(node.children[0], Relation):
-            self._not_scope_offset += 1
         csql = node.children[0].accept(self)
-        if isinstance(node.children[0], Relation):
-            self._not_scope_offset -= 1
-        sqls, tables = self._state.pop_scope()
         if node in self._state.done or not csql:
             # already processed or no sql generated by children
-            self._state.actual_tables[-1] += tables
-            self._state.restrictions += sqls
             return csql
-        if isinstance(node.children[0], Exists):
-            assert not sqls, (sqls, str(node.stmt))
-            assert not tables, (tables, str(node.stmt))
-            return 'NOT %s' % csql
-        sqls.append(csql)
-        if tables:
-            select = 'SELECT 1 FROM %s' % ','.join(tables)
-        else:
-            select = 'SELECT 1'
-        if sqls:
-            sql = 'NOT EXISTS(%s WHERE %s)' % (select, ' AND '.join(sqls))
-        else:
-            sql = 'NOT EXISTS(%s)' % select
-        return sql
+        return 'NOT (%s)' % csql
 
     def visit_exists(self, exists):
         """generate SQL name for a exists subquery"""
@@ -721,7 +701,7 @@
         return 'EXISTS(%s)' % ' UNION '.join(sqls)
 
     def _visit_exists(self, exists):
-        self._state.push_scope()
+        self._state.push_scope(exists)
         restriction = exists.children[0].accept(self)
         restrictions, tables = self._state.pop_scope()
         if restriction:
@@ -762,9 +742,6 @@
                 else:
                     # no variables in the RHS
                     sql = self._visit_attribute_relation(relation)
-                if relation.neged(strict=True):
-                    self._state.done.add(relation.parent)
-                    sql = 'NOT (%s)' % sql
         else:
             if rtype == 'is' and rhs.operator == 'IS':
                 # special case "C is NULL"
@@ -833,9 +810,6 @@
         if relation.r_type == 'identity':
             # special case "X identity Y"
             lhs, rhs = relation.get_parts()
-            if isinstance(relation.parent, Not):
-                self._state.done.add(relation.parent)
-                return 'NOT %s%s' % (lhs.accept(self), rhs.accept(self))
             return '%s%s' % (lhs.accept(self), rhs.accept(self))
         lhsvar, lhsconst, rhsvar, rhsconst = relation_info(relation)
         rid = self._relation_table(relation)
@@ -1041,7 +1015,7 @@
         else:
             not_ = False
         return self.dbhelper.fti_restriction_sql(alias, const.eval(self._args),
-                                                    jointo, not_) + restriction
+                                                 jointo, not_) + restriction
 
     def visit_comparison(self, cmp):
         """generate SQL for a comparison"""
@@ -1204,22 +1178,10 @@
         return ''
 
     def _var_info(self, var):
-        # if current var or one of its attribute is selected , it *must*
-        # appear in the toplevel's FROM even if we're currently visiting
-        # a EXISTS node
-        if var.sqlscope is var.stmt:
-            scope = 0
-        # don't consider not_scope_offset if the variable is only used in one
-        # relation
-        elif len(var.stinfo['relations']) > 1:
-            scope = -1 - self._not_scope_offset
-        else:
-            scope = -1
+        scope = self._state.scopes[var.scope]
         try:
             sql = self._varmap[var.name]
             tablealias = sql.split('.', 1)[0]
-            if scope < 0:
-                scope = self._varmap_table_scope(var.stmt, tablealias)
             self.add_table(tablealias, scope=scope)
         except KeyError:
             etype = self._state.solution[var.name]
@@ -1235,7 +1197,7 @@
     def _inlined_var_sql(self, var, rtype):
         try:
             sql = self._varmap['%s.%s' % (var.name, rtype)]
-            scope = var.sqlscope is var.stmt and 0 or -1
+            scope = self._state.scopes[var.scope]
             self.add_table(sql.split('.', 1)[0], scope=scope)
         except KeyError:
             sql = '%s.%s%s' % (self._var_table(var), SQL_PREFIX, rtype)
@@ -1358,7 +1320,7 @@
                 break
             # XXX may have a principal without being invariant for this generation,
             #     not sure this is a pb or not
-            if var.stinfo.get('principal') is relation and var.sqlscope is var.stmt:
+            if var.stinfo.get('principal') is relation and var.scope is var.stmt:
                 scope = 0
                 break
         else:
@@ -1379,15 +1341,3 @@
         alias = self.alias_and_add_table(self.dbhelper.fti_table)
         relation._q_sqltable = alias
         return alias
-
-    def _varmap_table_scope(self, select, table):
-        """since a varmap table may be used for multiple variable, its scope is
-        the most outer scope of each variables
-        """
-        scope = -1
-        for varname, alias in self._varmap.iteritems():
-            # check '.' in varname since there are 'X.attribute' keys in varmap
-            if not '.' in varname and alias.split('.', 1)[0] == table:
-                if select.defined_vars[varname].sqlscope is select:
-                    return 0
-        return scope
--- a/server/test/unittest_msplanner.py	Tue May 25 12:21:17 2010 +0200
+++ b/server/test/unittest_msplanner.py	Wed May 26 10:28:48 2010 +0200
@@ -15,9 +15,6 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-"""
 from cubicweb.devtools import init_test_database
 from cubicweb.devtools.repotest import BasePlannerTC, test_plan
 
@@ -748,7 +745,6 @@
                     ])
 
     def test_not_identity(self):
-        # both system and rql support all variables, can be
         self._test('Any X WHERE NOT X identity U, U eid %s' % self.session.user.eid,
                    [('OneFetchStep',
                      [('Any X WHERE NOT X identity 5, X is CWUser', [{'X': 'CWUser'}])],
@@ -1105,7 +1101,7 @@
                      [('Any L,X WHERE X login L, X is CWUser', [{'X': 'CWUser', 'L': 'String'}])],
                      [self.ldap, self.system], None, {'X': 'table2.C1', 'X.login': 'table2.C0', 'L': 'table2.C0'}, []),
                     ('OneFetchStep',
-                     [('Any G,L WHERE X in_group G, X login L, G name "managers", (EXISTS(X copain T, T login L, T is CWUser)) OR (EXISTS(X in_state S, S name "pascontent", NOT X copain T2, S is State, T2 is CWUser)), G is CWGroup, X is CWUser',
+                     [('Any G,L WHERE X in_group G, X login L, G name "managers", (EXISTS(X copain T, T login L, T is CWUser)) OR (EXISTS(X in_state S, S name "pascontent", NOT EXISTS(X copain T2), S is State)), G is CWGroup, T2 is CWUser, X is CWUser',
                        [{'G': 'CWGroup', 'L': 'String', 'S': 'State', 'T': 'CWUser', 'T2': 'CWUser', 'X': 'CWUser'}])],
                      None, None, [self.system],
                      {'T2': 'table1.C0', 'L': 'table2.C0',
@@ -1222,7 +1218,7 @@
         # in the source where %(x)s is not coming from and will be removed during rql
         # generation for the external source
         self._test('Any SN WHERE NOT X in_state S, X eid %(x)s, S name SN',
-                   [('OneFetchStep', [('Any SN WHERE NOT 5 in_state S, S name SN, S is State',
+                   [('OneFetchStep', [('Any SN WHERE NOT EXISTS(5 in_state S), S name SN, S is State',
                                        [{'S': 'State', 'SN': 'String'}])],
                      None, None, [self.cards, self.system], {}, [])],
                    {'x': ueid})
@@ -1233,7 +1229,7 @@
         # the same plan may be used, since we won't find any record in the system source
         # linking 9999999 to a state
         self._test('Any SN WHERE NOT X in_state S, X eid %(x)s, S name SN',
-                   [('OneFetchStep', [('Any SN WHERE NOT 999999 in_state S, S name SN, S is State',
+                   [('OneFetchStep', [('Any SN WHERE NOT EXISTS(999999 in_state S), S name SN, S is State',
                                        [{'S': 'State', 'SN': 'String'}])],
                      None, None, [self.cards, self.system], {}, [])],
                    {'x': 999999})
@@ -1246,12 +1242,12 @@
                      []),
                     ('IntersectStep', None, None,
                      [('OneFetchStep',
-                       [('Any SN WHERE NOT X in_state S, S name SN, S is State, X is Note',
+                       [('Any SN WHERE NOT EXISTS(X in_state S, X is Note), S name SN, S is State',
                          [{'S': 'State', 'SN': 'String', 'X': 'Note'}])],
                        None, None, [self.cards, self.system], {},
                        []),
                       ('OneFetchStep',
-                       [('Any SN WHERE NOT X in_state S, S name SN, S is State, X is IN(Affaire, CWUser)',
+                       [('Any SN WHERE NOT EXISTS(X in_state S, X is IN(Affaire, CWUser)), S name SN, S is State',
                          [{'S': 'State', 'SN': 'String', 'X': 'Affaire'},
                           {'S': 'State', 'SN': 'String', 'X': 'CWUser'}])],
                        None, None, [self.system], {'S': 'table0.C1', 'S.name': 'table0.C0', 'SN': 'table0.C0'},
@@ -1505,7 +1501,7 @@
         self._test('Any Y WHERE X eid %(x)s, NOT X multisource_crossed_rel Y',
                    [('FetchStep', [('Any Y WHERE Y is Note', [{'Y': 'Note'}])],
                      [self.cards, self.system], None, {'Y': 'table0.C0'}, []),
-                    ('OneFetchStep', [('Any Y WHERE NOT 999999 multisource_crossed_rel Y, Y is Note',
+                    ('OneFetchStep', [('Any Y WHERE NOT EXISTS(999999 multisource_crossed_rel Y), Y is Note',
                                        [{'Y': 'Note'}])],
                      None, None, [self.system],
                      {'Y': 'table0.C0'},  [])],
@@ -1633,7 +1629,7 @@
         repo._type_source_cache[999999] = ('Note', 'system', 999999)
         self._test('DELETE Note X WHERE X eid %(x)s, NOT Y multisource_rel X',
                    [('DeleteEntitiesStep',
-                     [('OneFetchStep', [('Any 999999 WHERE NOT Y multisource_rel 999999, Y is IN(Card, Note)',
+                     [('OneFetchStep', [('Any 999999 WHERE NOT EXISTS(Y multisource_rel 999999), Y is IN(Card, Note)',
                                          [{'Y': 'Card'}, {'Y': 'Note'}])],
                        None, None, [self.system], {}, [])
                       ])
@@ -2185,7 +2181,7 @@
         self.repo._type_source_cache[999998] = ('Note', 'vcs', 999998)
         self.repo._type_source_cache[999999] = ('Note', 'vcs', 999999)
         self._test('Any X, Y WHERE NOT X multisource_rel Y, X eid 999998, Y eid 999999',
-                   [('OneFetchStep', [('Any 999998,999999 WHERE NOT 999998 multisource_rel 999999', [{}])],
+                   [('OneFetchStep', [('Any 999998,999999 WHERE NOT EXISTS(999998 multisource_rel 999999)', [{}])],
                      None, None, [self.vcs], {}, [])
                     ])
 
--- a/server/test/unittest_multisources.py	Tue May 25 12:21:17 2010 +0200
+++ b/server/test/unittest_multisources.py	Wed May 26 10:28:48 2010 +0200
@@ -15,9 +15,6 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-"""
 from os.path import dirname, join, abspath
 from datetime import datetime, timedelta
 
--- a/server/test/unittest_repository.py	Tue May 25 12:21:17 2010 +0200
+++ b/server/test/unittest_repository.py	Wed May 26 10:28:48 2010 +0200
@@ -239,10 +239,11 @@
                                if not r.type in ('eid', 'is', 'is_instance_of', 'identity',
                                                  'creation_date', 'modification_date', 'cwuri',
                                                  'owned_by', 'created_by',
-                                                 'update_permission', 'read_permission')],
+                                                 'update_permission', 'read_permission',
+                                                 'in_basket')],
                               ['relation_type',
                                'from_entity', 'to_entity',
-                               'in_basket', 'constrained_by', 
+                               'constrained_by',
                                'cardinality', 'ordernum',
                                'indexed', 'fulltextindexed', 'internationalizable',
                                'defaultval', 'description', 'description_format'])
--- a/server/test/unittest_rql2sql.py	Tue May 25 12:21:17 2010 +0200
+++ b/server/test/unittest_rql2sql.py	Wed May 26 10:28:48 2010 +0200
@@ -15,10 +15,6 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-"""
-
 """unit tests for module cubicweb.server.sources.rql2sql"""
 
 import sys
@@ -180,7 +176,7 @@
      "NOT EXISTS(X owned_by U, U in_group G, G name 'lulufanclub' OR G name 'managers');",
      '''SELECT _X.cw_eid
 FROM cw_Personne AS _X
-WHERE _X.cw_prenom=lulu AND NOT EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, in_group_relation AS rel_in_group1, cw_CWGroup AS _G WHERE rel_owned_by0.eid_from=_X.cw_eid AND rel_in_group1.eid_from=rel_owned_by0.eid_to AND rel_in_group1.eid_to=_G.cw_eid AND ((_G.cw_name=lulufanclub) OR (_G.cw_name=managers)))'''),
+WHERE _X.cw_prenom=lulu AND NOT (EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, in_group_relation AS rel_in_group1, cw_CWGroup AS _G WHERE rel_owned_by0.eid_from=_X.cw_eid AND rel_in_group1.eid_from=rel_owned_by0.eid_to AND rel_in_group1.eid_to=_G.cw_eid AND ((_G.cw_name=lulufanclub) OR (_G.cw_name=managers))))'''),
 
 
 
@@ -276,7 +272,7 @@
     ('Any O WHERE NOT S ecrit_par O, S eid 1, S inline1 P, O inline2 P',
      '''SELECT _O.cw_eid
 FROM cw_Note AS _S, cw_Personne AS _O
-WHERE NOT EXISTS(SELECT 1 WHERE _S.cw_ecrit_par=_O.cw_eid) AND _S.cw_eid=1 AND _O.cw_inline2=_S.cw_inline1'''),
+WHERE NOT (_S.cw_ecrit_par=_O.cw_eid) AND _S.cw_eid=1 AND _O.cw_inline2=_S.cw_inline1'''),
 
     ('DISTINCT Any S ORDERBY stockproc(SI) WHERE NOT S ecrit_par O, S para SI',
      '''SELECT T1.C0 FROM (SELECT DISTINCT _S.cw_eid AS C0, STOCKPROC(_S.cw_para) AS C1
@@ -299,7 +295,7 @@
     (' Any X,U WHERE C owned_by U, NOT X owned_by U, C eid 1, X eid 2',
      '''SELECT 2, rel_owned_by0.eid_to
 FROM owned_by_relation AS rel_owned_by0
-WHERE rel_owned_by0.eid_from=1 AND NOT EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by1 WHERE rel_owned_by1.eid_from=2 AND rel_owned_by0.eid_to=rel_owned_by1.eid_to)'''),
+WHERE rel_owned_by0.eid_from=1 AND NOT (EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by1 WHERE rel_owned_by1.eid_from=2 AND rel_owned_by0.eid_to=rel_owned_by1.eid_to))'''),
 
     ('Any GN WHERE X in_group G, G name GN, (G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon")))',
      '''SELECT _G.cw_name
@@ -353,7 +349,7 @@
     ('Any L WHERE X login "admin", NOT X identity Y, Y login L',
      '''SELECT _Y.cw_login
 FROM cw_CWUser AS _X, cw_CWUser AS _Y
-WHERE _X.cw_login=admin AND NOT _X.cw_eid=_Y.cw_eid'''),
+WHERE _X.cw_login=admin AND NOT (_X.cw_eid=_Y.cw_eid)'''),
 
     ('Any L WHERE X login "admin", X identity Y?, Y login L',
      '''SELECT _Y.cw_login
@@ -391,31 +387,31 @@
     ('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT EXISTS(X read_permission Y)',
      '''SELECT DISTINCT _X.cw_eid, _Y.cw_eid
 FROM cw_CWEType AS _X, cw_CWGroup AS _Y
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
+WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT (EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid))
 UNION
 SELECT DISTINCT _X.cw_eid, _Y.cw_eid
 FROM cw_CWEType AS _X, cw_RQLExpression AS _Y
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)'''),
+WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT (EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid))'''),
 
     # should generate the same query as above
     ('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y',
      '''SELECT DISTINCT _X.cw_eid, _Y.cw_eid
 FROM cw_CWEType AS _X, cw_CWGroup AS _Y
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
+WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT (EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid))
 UNION
 SELECT DISTINCT _X.cw_eid, _Y.cw_eid
 FROM cw_CWEType AS _X, cw_RQLExpression AS _Y
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)'''),
+WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT (EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid))'''),
 
     # neged relation, can't be inveriant
     ('Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y',
      '''SELECT _X.cw_eid, _Y.cw_eid
 FROM cw_CWEType AS _X, cw_CWGroup AS _Y
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
+WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT (EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid))
 UNION ALL
 SELECT _X.cw_eid, _Y.cw_eid
 FROM cw_CWEType AS _X, cw_RQLExpression AS _Y
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)'''),
+WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT (EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid))'''),
 
     ('Any MAX(X)+MIN(X), N GROUPBY N WHERE X name N, X is IN (Basket, Folder, Tag);',
      '''SELECT (MAX(T1.C0) + MIN(T1.C0)), T1.C1 FROM (SELECT _X.cw_eid AS C0, _X.cw_name AS C1
@@ -552,7 +548,7 @@
      'EXISTS(A use_email O, EXISTS(A identity B, NOT B in_group D, D name "guests", D is CWGroup), A is CWUser), B eid 2',
      '''SELECT _O.cw_eid, _O.cw_address, _O.cw_alias, _O.cw_modification_date
 FROM cw_EmailAddress AS _O
-WHERE NOT EXISTS(SELECT 1 FROM use_email_relation AS rel_use_email0 WHERE rel_use_email0.eid_from=1 AND rel_use_email0.eid_to=_O.cw_eid) AND EXISTS(SELECT 1 FROM use_email_relation AS rel_use_email1 WHERE rel_use_email1.eid_to=_O.cw_eid AND EXISTS(SELECT 1 FROM cw_CWGroup AS _D WHERE rel_use_email1.eid_from=2 AND NOT EXISTS(SELECT 1 FROM in_group_relation AS rel_in_group2 WHERE rel_in_group2.eid_from=2 AND rel_in_group2.eid_to=_D.cw_eid) AND _D.cw_name=guests))
+WHERE NOT (EXISTS(SELECT 1 FROM use_email_relation AS rel_use_email0 WHERE rel_use_email0.eid_from=1 AND rel_use_email0.eid_to=_O.cw_eid)) AND EXISTS(SELECT 1 FROM use_email_relation AS rel_use_email1 WHERE rel_use_email1.eid_to=_O.cw_eid AND EXISTS(SELECT 1 FROM cw_CWGroup AS _D WHERE rel_use_email1.eid_from=2 AND NOT (EXISTS(SELECT 1 FROM in_group_relation AS rel_in_group2 WHERE rel_in_group2.eid_from=2 AND rel_in_group2.eid_to=_D.cw_eid)) AND _D.cw_name=guests))
 ORDER BY 4 DESC'''),
 
 
@@ -603,17 +599,17 @@
     ("Personne X WHERE NOT X evaluee Y;",
      '''SELECT _X.cw_eid
 FROM cw_Personne AS _X
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_X.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_X.cw_eid))'''),
 
     ("Note N WHERE NOT X evaluee N, X eid 0",
      '''SELECT _N.cw_eid
 FROM cw_Note AS _N
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=0 AND rel_evaluee0.eid_to=_N.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=0 AND rel_evaluee0.eid_to=_N.cw_eid))'''),
 
     ('Any X WHERE NOT X travaille S, X is Personne',
      '''SELECT _X.cw_eid
 FROM cw_Personne AS _X
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=_X.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=_X.cw_eid))'''),
 
     ("Personne P where not P datenaiss TODAY",
      '''SELECT _P.cw_eid
@@ -623,16 +619,16 @@
     ("Personne P where NOT P concerne A",
      '''SELECT _P.cw_eid
 FROM cw_Personne AS _P
-WHERE NOT EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_from=_P.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_from=_P.cw_eid))'''),
 
     ("Affaire A where not P concerne A",
      '''SELECT _A.cw_eid
 FROM cw_Affaire AS _A
-WHERE NOT EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_to=_A.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_to=_A.cw_eid))'''),
     ("Personne P where not P concerne A, A sujet ~= 'TEST%'",
      '''SELECT _P.cw_eid
 FROM cw_Affaire AS _A, cw_Personne AS _P
-WHERE NOT EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_from=_P.cw_eid AND rel_concerne0.eid_to=_A.cw_eid) AND _A.cw_sujet ILIKE TEST%'''),
+WHERE NOT (EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_from=_P.cw_eid AND rel_concerne0.eid_to=_A.cw_eid)) AND _A.cw_sujet ILIKE TEST%'''),
 
     ('Any S WHERE NOT T eid 28258, T tags S',
      '''SELECT rel_tags0.eid_to
@@ -660,33 +656,33 @@
     ('Note X WHERE NOT Y evaluee X',
      '''SELECT _X.cw_eid
 FROM cw_Note AS _X
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_to=_X.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_to=_X.cw_eid))'''),
 
     ('Any Y WHERE NOT Y evaluee X',
      '''SELECT _Y.cw_eid
 FROM cw_CWUser AS _Y
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_Y.cw_eid)
+WHERE NOT (EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_Y.cw_eid))
 UNION ALL
 SELECT _Y.cw_eid
 FROM cw_Division AS _Y
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_Y.cw_eid)
+WHERE NOT (EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_Y.cw_eid))
 UNION ALL
 SELECT _Y.cw_eid
 FROM cw_Personne AS _Y
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_Y.cw_eid)
+WHERE NOT (EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_Y.cw_eid))
 UNION ALL
 SELECT _Y.cw_eid
 FROM cw_Societe AS _Y
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_Y.cw_eid)
+WHERE NOT (EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_Y.cw_eid))
 UNION ALL
 SELECT _Y.cw_eid
 FROM cw_SubDivision AS _Y
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_Y.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=_Y.cw_eid))'''),
 
     ('Any X WHERE NOT Y evaluee X, Y is CWUser',
      '''SELECT _X.cw_eid
 FROM cw_Note AS _X
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0,cw_CWUser AS _Y WHERE rel_evaluee0.eid_from=_Y.cw_eid AND rel_evaluee0.eid_to=_X.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0, cw_CWUser AS _Y WHERE rel_evaluee0.eid_from=_Y.cw_eid AND rel_evaluee0.eid_to=_X.cw_eid))'''),
 
     ('Any X,RT WHERE X relation_type RT, NOT X is CWAttribute',
      '''SELECT _X.cw_eid, _X.cw_relation_type
@@ -701,17 +697,13 @@
     ('Any S WHERE NOT X in_state S, X is IN(Affaire, CWUser)',
      '''SELECT _S.cw_eid
 FROM cw_State AS _S
-WHERE NOT EXISTS(SELECT 1 FROM cw_Affaire AS _X WHERE _X.cw_in_state=_S.cw_eid)
-INTERSECT
-SELECT _S.cw_eid
-FROM cw_State AS _S
-WHERE NOT EXISTS(SELECT 1 FROM cw_CWUser AS _X WHERE _X.cw_in_state=_S.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM cw_Affaire AS _X WHERE _X.cw_in_state=_S.cw_eid UNION SELECT 1 FROM cw_CWUser AS _X WHERE _X.cw_in_state=_S.cw_eid))'''),
 
     ('Any S WHERE NOT(X in_state S, S name "somename"), X is CWUser',
      '''SELECT _S.cw_eid
 FROM cw_State AS _S
-WHERE NOT EXISTS(SELECT 1 FROM cw_CWUser AS _X WHERE _X.cw_in_state=_S.cw_eid AND _S.cw_name=somename)'''),
-   
+WHERE NOT (EXISTS(SELECT 1 FROM cw_CWUser AS _X WHERE _X.cw_in_state=_S.cw_eid AND _S.cw_name=somename))'''),
+
 # XXXFIXME fail
 #         ('Any X,RT WHERE X relation_type RT?, NOT X is CWAttribute',
 #      '''SELECT _X.cw_eid, _X.cw_relation_type
@@ -844,7 +836,7 @@
     ('Any O,AD  WHERE NOT S inline1 O, S eid 123, O todo_by AD?',
      '''SELECT _O.cw_eid, rel_todo_by0.eid_to
 FROM cw_Affaire AS _O LEFT OUTER JOIN todo_by_relation AS rel_todo_by0 ON (rel_todo_by0.eid_from=_O.cw_eid), cw_Note AS _S
-WHERE NOT EXISTS(SELECT 1 WHERE _S.cw_inline1=_O.cw_eid) AND _S.cw_eid=123''')
+WHERE NOT (_S.cw_inline1=_O.cw_eid) AND _S.cw_eid=123''')
     ]
 
 VIRTUAL_VARS = [
@@ -919,7 +911,7 @@
 FROM cw_Personne AS _P'''),
     ]
 
-SYMETRIC = [
+SYMMETRIC = [
     ('Any P WHERE X eid 0, X connait P',
      '''SELECT DISTINCT _P.cw_eid
 FROM connait_relation AS rel_connait0, cw_Personne AS _P
@@ -941,17 +933,17 @@
     ('Any P WHERE X eid 0, NOT X connait P',
      '''SELECT _P.cw_eid
 FROM cw_Personne AS _P
-WHERE NOT EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_from=0 AND rel_connait0.eid_to=_P.cw_eid OR rel_connait0.eid_to=0 AND rel_connait0.eid_from=_P.cw_eid))'''),
+WHERE NOT (EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_from=0 AND rel_connait0.eid_to=_P.cw_eid OR rel_connait0.eid_to=0 AND rel_connait0.eid_from=_P.cw_eid)))'''),
 
     ('Any P WHERE NOT X connait P',
     '''SELECT _P.cw_eid
 FROM cw_Personne AS _P
-WHERE NOT EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_to=_P.cw_eid OR rel_connait0.eid_from=_P.cw_eid))'''),
+WHERE NOT (EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_to=_P.cw_eid OR rel_connait0.eid_from=_P.cw_eid)))'''),
 
     ('Any X WHERE NOT X connait P',
     '''SELECT _X.cw_eid
 FROM cw_Personne AS _X
-WHERE NOT EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_from=_X.cw_eid OR rel_connait0.eid_to=_X.cw_eid))'''),
+WHERE NOT (EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_from=_X.cw_eid OR rel_connait0.eid_to=_X.cw_eid)))'''),
 
     ('Any P WHERE X connait P, P nom "nom"',
      '''SELECT DISTINCT _P.cw_eid
@@ -988,7 +980,12 @@
     ('Any N WHERE NOT N ecrit_par P, P nom "toto"',
      '''SELECT _N.cw_eid
 FROM cw_Note AS _N, cw_Personne AS _P
-WHERE NOT EXISTS(SELECT 1 WHERE _N.cw_ecrit_par=_P.cw_eid) AND _P.cw_nom=toto'''),
+WHERE NOT (_N.cw_ecrit_par=_P.cw_eid) AND _P.cw_nom=toto'''),
+
+    ('Any P WHERE NOT N ecrit_par P, P nom "toto"',
+     '''SELECT _P.cw_eid
+FROM cw_Personne AS _P
+WHERE NOT (EXISTS(SELECT 1 FROM cw_Note AS _N WHERE _N.cw_ecrit_par=_P.cw_eid)) AND _P.cw_nom=toto'''),
 
     ('Any P WHERE N ecrit_par P, N eid 0',
     '''SELECT _N.cw_ecrit_par
@@ -1003,7 +1000,7 @@
     ('Any P WHERE NOT N ecrit_par P, P is Personne, N eid 512',
      '''SELECT _P.cw_eid
 FROM cw_Note AS _N, cw_Personne AS _P
-WHERE NOT EXISTS(SELECT 1 WHERE _N.cw_ecrit_par=_P.cw_eid) AND _N.cw_eid=512'''),
+WHERE NOT (_N.cw_ecrit_par=_P.cw_eid) AND _N.cw_eid=512'''),
 
     ('Any S,ES,T WHERE S state_of ET, ET name "CWUser", ES allowed_transition T, T destination_state S',
      '''SELECT _T.cw_destination_state, rel_allowed_transition1.eid_from, _T.cw_eid
@@ -1025,7 +1022,7 @@
 
     ('Any X WHERE NOT Y for_user X, X eid 123',
      '''SELECT 123
-WHERE NOT EXISTS(SELECT 1 FROM cw_CWProperty AS _Y WHERE _Y.cw_for_user=123)
+WHERE NOT (EXISTS(SELECT 1 FROM cw_CWProperty AS _Y WHERE _Y.cw_for_user=123))
 '''),
 
     ]
@@ -1034,46 +1031,34 @@
     ('Any SN WHERE NOT X in_state S, S name SN',
      '''SELECT _S.cw_name
 FROM cw_State AS _S
-WHERE NOT EXISTS(SELECT 1 FROM cw_Affaire AS _X WHERE _X.cw_in_state=_S.cw_eid)
-INTERSECT
-SELECT _S.cw_name
-FROM cw_State AS _S
-WHERE NOT EXISTS(SELECT 1 FROM cw_CWUser AS _X WHERE _X.cw_in_state=_S.cw_eid)
-INTERSECT
-SELECT _S.cw_name
-FROM cw_State AS _S
-WHERE NOT EXISTS(SELECT 1 FROM cw_Note AS _X WHERE _X.cw_in_state=_S.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM cw_Affaire AS _X WHERE _X.cw_in_state=_S.cw_eid UNION SELECT 1 FROM cw_Note AS _X WHERE _X.cw_in_state=_S.cw_eid UNION SELECT 1 FROM cw_CWUser AS _X WHERE _X.cw_in_state=_S.cw_eid))'''),
 
     ('Any PN WHERE NOT X travaille S, X nom PN, S is IN(Division, Societe)',
      '''SELECT _X.cw_nom
 FROM cw_Personne AS _X
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,cw_Division AS _S WHERE rel_travaille0.eid_from=_X.cw_eid AND rel_travaille0.eid_to=_S.cw_eid)
-INTERSECT
-SELECT _X.cw_nom
-FROM cw_Personne AS _X
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,cw_Societe AS _S WHERE rel_travaille0.eid_from=_X.cw_eid AND rel_travaille0.eid_to=_S.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0, cw_Division AS _S WHERE rel_travaille0.eid_from=_X.cw_eid AND rel_travaille0.eid_to=_S.cw_eid UNION SELECT 1 FROM travaille_relation AS rel_travaille1, cw_Societe AS _S WHERE rel_travaille1.eid_from=_X.cw_eid AND rel_travaille1.eid_to=_S.cw_eid))'''),
 
     ('Any PN WHERE NOT X travaille S, S nom PN, S is IN(Division, Societe)',
      '''SELECT _S.cw_nom
 FROM cw_Division AS _S
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=_S.cw_eid)
+WHERE NOT (EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=_S.cw_eid))
 UNION ALL
 SELECT _S.cw_nom
 FROM cw_Societe AS _S
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=_S.cw_eid)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=_S.cw_eid))'''),
 
     ('Personne X WHERE NOT X travaille S, S nom "chouette"',
      '''SELECT _X.cw_eid
 FROM cw_Division AS _S, cw_Personne AS _X
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=_X.cw_eid AND rel_travaille0.eid_to=_S.cw_eid) AND _S.cw_nom=chouette
+WHERE NOT (EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=_X.cw_eid AND rel_travaille0.eid_to=_S.cw_eid)) AND _S.cw_nom=chouette
 UNION ALL
 SELECT _X.cw_eid
 FROM cw_Personne AS _X, cw_Societe AS _S
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=_X.cw_eid AND rel_travaille0.eid_to=_S.cw_eid) AND _S.cw_nom=chouette
+WHERE NOT (EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=_X.cw_eid AND rel_travaille0.eid_to=_S.cw_eid)) AND _S.cw_nom=chouette
 UNION ALL
 SELECT _X.cw_eid
 FROM cw_Personne AS _X, cw_SubDivision AS _S
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=_X.cw_eid AND rel_travaille0.eid_to=_S.cw_eid) AND _S.cw_nom=chouette'''),
+WHERE NOT (EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=_X.cw_eid AND rel_travaille0.eid_to=_S.cw_eid)) AND _S.cw_nom=chouette'''),
 
     ('Any X WHERE X is ET, ET eid 2',
      '''SELECT rel_is0.eid_from
@@ -1345,7 +1330,7 @@
         self.assertRaises(BadRQLQuery, self.o.generate, rqlst)
 
     def test_symmetric(self):
-        for t in self._parse(SYMETRIC):
+        for t in self._parse(SYMMETRIC):
             yield t
 
     def test_inline(self):
@@ -1393,7 +1378,7 @@
 WHERE EXISTS(SELECT 1 FROM cw_CWGroup AS _T WHERE _T.cw_name=managers)'''),
                    ('Any X,Y WHERE NOT X created_by Y, X eid 5, Y eid 6',
                     '''SELECT 5, 6
-WHERE NOT EXISTS(SELECT 1 FROM created_by_relation AS rel_created_by0 WHERE rel_created_by0.eid_from=5 AND rel_created_by0.eid_to=6)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM created_by_relation AS rel_created_by0 WHERE rel_created_by0.eid_from=5 AND rel_created_by0.eid_to=6))'''),
                    ]
         for t in self._parse(queries):
             yield t
@@ -1439,7 +1424,7 @@
         self.o = SQLGenerator(schema, dbhelper)
 
     def _norm_sql(self, sql):
-        return sql.strip().replace(' ILIKE ', ' LIKE ').replace('\nINTERSECT ALL\n', '\nINTERSECT\n')
+        return sql.strip().replace(' ILIKE ', ' LIKE ')
 
     def test_date_extraction(self):
         self._check("Any MONTH(D) WHERE P is Personne, P creation_date D",
@@ -1571,7 +1556,7 @@
                    ('Any X,Y WHERE NOT X created_by Y, X eid 5, Y eid 6',
                     '''SELECT 5, 6
 FROM (SELECT 1) AS _T
-WHERE NOT EXISTS(SELECT 1 FROM created_by_relation AS rel_created_by0 WHERE rel_created_by0.eid_from=5 AND rel_created_by0.eid_to=6)'''),
+WHERE NOT (EXISTS(SELECT 1 FROM created_by_relation AS rel_created_by0 WHERE rel_created_by0.eid_from=5 AND rel_created_by0.eid_to=6))'''),
                    ]
         for t in self._parse(queries):
             yield t
--- a/test/unittest_entity.py	Tue May 25 12:21:17 2010 +0200
+++ b/test/unittest_entity.py	Wed May 26 10:28:48 2010 +0200
@@ -251,7 +251,7 @@
         email = self.execute('Any X WHERE X eid %(x)s', {'x': email.eid}).get_entity(0, 0)
         rql = email.unrelated_rql('use_email', 'CWUser', 'object')[0]
         self.assertEquals(rql, 'Any S,AA,AB,AC,AD ORDERBY AA '
-                          'WHERE NOT S use_email O, O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD, '
+                          'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD, '
                           'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
         #rql = email.unrelated_rql('use_email', 'Person', 'object')[0]
         #self.assertEquals(rql, '')
--- a/test/unittest_rqlrewrite.py	Tue May 25 12:21:17 2010 +0200
+++ b/test/unittest_rqlrewrite.py	Wed May 26 10:28:48 2010 +0200
@@ -24,7 +24,7 @@
 from rql import parse, nodes, RQLHelper
 
 from cubicweb import Unauthorized
-from cubicweb.schema import RRQLExpression
+from cubicweb.schema import RRQLExpression, ERQLExpression
 from cubicweb.rqlrewrite import RQLRewriter
 from cubicweb.devtools import repotest, TestServerConfiguration
 
@@ -350,6 +350,20 @@
         self.failUnlessEqual(rqlst.as_string(),
                              u"Any C WHERE C is Card, EXISTS(C owned_by A, A is CWUser)")
 
+    def test_rqlexpr_not_relation1(self):
+        constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X')
+        rqlst = parse('Affaire A WHERE NOT EXISTS(A documented_by C)')
+        rewrite(rqlst, {('C', 'X'): (constraint,)}, {}, 'X')
+        self.failUnlessEqual(rqlst.as_string(),
+                             u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire')
+
+    def test_rqlexpr_not_relation2(self):
+        constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X')
+        rqlst = rqlhelper.parse('Affaire A WHERE NOT A documented_by C', annotate=False)
+        rewrite(rqlst, {('C', 'X'): (constraint,)}, {}, 'X')
+        self.failUnlessEqual(rqlst.as_string(),
+                             u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire')
+
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_views_searchrestriction.py	Tue May 25 12:21:17 2010 +0200
+++ b/web/test/unittest_views_searchrestriction.py	Wed May 26 10:28:48 2010 +0200
@@ -85,7 +85,7 @@
         try:
             self.assertEquals(self._generate(select, 'in_state', 'subject', 'name'),
                               "DISTINCT Any A,B ORDERBY B WHERE V is CWUser, "
-                              "NOT V in_state VS, VS name 'published', "
+                              "NOT EXISTS(V in_state VS), VS name 'published', "
                               "V in_state A, A name B")
         finally:
             for rdefs in rschema.rdefs.values():