[pkg] Use setuptools
authorJulien Cristau <julien.cristau@logilab.fr>
Thu, 28 May 2015 01:13:28 +0200
changeset 761 d9762b5a4604
parent 760 5b44acfd687b
child 762 5aa92d744e2d
[pkg] Use setuptools Move sources to a 'rql' subdirectory, drop the NO_SETUPTOOLS code path (essentially rewriting all of setup.py). This avoids a conflict between our parser.py and the stdlib when running setup.py. Closes #278637.
__init__.py
__pkginfo__.py
_exceptions.py
analyze.py
base.py
compare.py
editextensions.py
gecode_solver.cpp
interfaces.py
nodes.py
parser.g
parser.py
parser_main.py
pygments_ext.py
rql/__init__.py
rql/_exceptions.py
rql/analyze.py
rql/base.py
rql/compare.py
rql/editextensions.py
rql/gecode_solver.cpp
rql/interfaces.py
rql/nodes.py
rql/parser.g
rql/parser.py
rql/parser_main.py
rql/pygments_ext.py
rql/rqlgen.py
rql/stcheck.py
rql/stmts.py
rql/undo.py
rql/utils.py
rqlgen.py
setup.py
stcheck.py
stmts.py
undo.py
utils.py
--- a/__init__.py	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,257 +0,0 @@
-# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of rql.
-#
-# rql is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# rql is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with rql. If not, see <http://www.gnu.org/licenses/>.
-"""RQL library (implementation independant)."""
-__docformat__ = "restructuredtext en"
-
-from rql.__pkginfo__ import version as __version__
-from math import log
-
-import sys
-import threading
-
-from six import StringIO
-
-from rql._exceptions import *
-
-#REQUIRED_TYPES = ['String', 'Float', 'Int', 'Boolean', 'Date']
-
-class RQLHelper(object):
-    """Helper class for RQL handling
-
-    give access to methods for :
-      - parsing RQL strings
-      - variables type resolving
-      - comparison of two queries
-    """
-    def __init__(self, schema, uid_func_mapping=None, special_relations=None,
-                 resolver_class=None, backend=None):
-        # chech schema
-        #for e_type in REQUIRED_TYPES:
-        #    if not schema.has_entity(e_type):
-        #        raise MissingType(e_type)
-        # create helpers
-        from rql.stcheck import RQLSTChecker, RQLSTAnnotator
-        special_relations = special_relations or {}
-        if uid_func_mapping:
-            for key in uid_func_mapping:
-                special_relations[key] = 'uid'
-        self._checker = RQLSTChecker(schema, special_relations, backend)
-        self._annotator = RQLSTAnnotator(schema, special_relations)
-        self._analyser_lock = threading.Lock()
-        if resolver_class is None:
-            from rql.analyze import ETypeResolver
-            resolver_class = ETypeResolver
-        self._analyser = resolver_class(schema, uid_func_mapping)
-        # IgnoreTypeRestriction analyser
-        from rql.analyze import ETypeResolverIgnoreTypeRestriction
-        self._itr_analyser_lock = threading.Lock()
-        self._itr_analyser = ETypeResolverIgnoreTypeRestriction(schema, uid_func_mapping)
-        self.set_schema(schema)
-
-    def set_schema(self, schema):
-        from rql.utils import is_keyword
-        for etype in schema.entities():
-            etype = str(etype)
-            if is_keyword(etype) or etype.capitalize() == 'Any':
-                raise UsesReservedWord(etype)
-        for rtype in schema.relations():
-            rtype = str(rtype)
-            if is_keyword(rtype):
-                raise UsesReservedWord(rtype)
-        self._checker.schema = schema
-        self._annotator.schema = schema
-        self._analyser.set_schema(schema)
-
-    def get_backend(self):
-        return self._checker.backend
-    def set_backend(self, backend):
-        self._checker.backend = backend
-    backend = property(get_backend, set_backend)
-
-    def parse(self, rqlstring, annotate=True):
-        """Return a syntax tree created from a RQL string."""
-        rqlst = parse(rqlstring, False)
-        self._checker.check(rqlst)
-        if annotate:
-            self.annotate(rqlst)
-        rqlst.schema = self._annotator.schema
-        return rqlst
-
-    def annotate(self, rqlst):
-        self._annotator.annotate(rqlst)
-
-    def compute_solutions(self, rqlst, uid_func_mapping=None, kwargs=None,
-                          debug=False):
-        """Set solutions for variables of the syntax tree.
-
-        Each solution is a dictionary with variable's name as key and
-        variable's type as value.
-        """
-        self._analyser_lock.acquire()
-        try:
-            return self._analyser.visit(rqlst, uid_func_mapping, kwargs,
-                                        debug)
-        finally:
-            self._analyser_lock.release()
-
-    def compute_all_solutions(self, rqlst, uid_func_mapping=None, kwargs=None,
-                          debug=False):
-        """compute syntaxe tree solutions with all types restriction (eg
-        is/instance_of relations) ignored
-        """
-        self._itr_analyser_lock.acquire()
-        try:
-            self._itr_analyser.visit(rqlst, uid_func_mapping, kwargs,
-                                 debug)
-        finally:
-            self._itr_analyser_lock.release()
-
-
-    def simplify(self, rqlst):
-        """Simplify `rqlst` by rewriting non-final variables associated to a const
-        node (if annotator say we can...)
-
-        The tree is modified in-place.
-        """
-        #print 'simplify', rqlst.as_string(encoding='UTF8')
-        if rqlst.TYPE == 'select':
-            from rql import nodes
-            for select in rqlst.children:
-                self._simplify(select)
-
-    def _simplify(self, select):
-        # recurse on subqueries first
-        for subquery in select.with_:
-            for subselect in subquery.query.children:
-                self._simplify(subselect)
-        rewritten = False
-        for var in list(select.defined_vars.values()):
-            stinfo = var.stinfo
-            if stinfo['constnode'] and not stinfo.get('blocsimplification'):
-                uidrel = stinfo['uidrel']
-                var = uidrel.children[0].variable
-                vconsts = []
-                rhs = uidrel.children[1].children[0]
-                for vref in var.references():
-                    rel = vref.relation()
-                    if rel is None:
-                        term = vref
-                        while not term.parent is select:
-                            term = term.parent
-                        if term in select.selection:
-                            rhs = copy_uid_node(select, rhs, vconsts)
-                            if vref is term:
-                                select.selection[select.selection.index(vref)] = rhs
-                                rhs.parent = select
-                            else:
-                                vref.parent.replace(vref, rhs)
-                        elif term in select.orderby:
-                            # remove from orderby
-                            select.remove(term)
-                        elif not select.having:
-                            # remove from groupby if no HAVING clause
-                            select.remove(term)
-                        else:
-                            rhs = copy_uid_node(select, rhs, vconsts)
-                            select.groupby[select.groupby.index(vref)] = rhs
-                            rhs.parent = select
-                    elif rel is uidrel:
-                        uidrel.parent.remove(uidrel)
-                    elif rel.is_types_restriction():
-                        stinfo['typerel'] = None
-                        rel.parent.remove(rel)
-                    else:
-                        rhs = copy_uid_node(select, rhs, vconsts)
-                        vref.parent.replace(vref, rhs)
-                del select.defined_vars[var.name]
-                stinfo['uidrel'] = None
-                rewritten = True
-                if vconsts:
-                    select.stinfo['rewritten'][var.name] = vconsts
-        if rewritten and select.solutions:
-            select.clean_solutions()
-
-    def compare(self, rqlstring1, rqlstring2):
-        """Compare 2 RQL requests.
-
-        Return True if both requests would return the same results.
-        """
-        from rql.compare import compare_tree
-        return compare_tree(self.parse(rqlstring1), self.parse(rqlstring2))
-
-
-def copy_uid_node(select, node, vconsts):
-    node = node.copy(select)
-    node.uid = True
-    vconsts.append(node)
-    return node
-
-
-def parse(rqlstring, print_errors=True):
-    """Return a syntax tree created from a RQL string."""
-    from yapps.runtime import print_error, SyntaxError, NoMoreTokens
-    from rql.parser import Hercule, HerculeScanner
-    # make sure rql string ends with a semi-colon
-    rqlstring = rqlstring.strip()
-    if rqlstring and not rqlstring.endswith(';') :
-        rqlstring += ';'
-    # parse the RQL string
-    parser = Hercule(HerculeScanner(rqlstring))
-    try:
-        return parser.goal()
-    except SyntaxError as ex:
-        if not print_errors:
-            if ex.pos is not None:
-                multi_lines_rql = rqlstring.splitlines()
-                nb_lines = len(multi_lines_rql)
-                if nb_lines > 5:
-                    width = log(nb_lines, 10)+1
-                    template = " %%%ii: %%s" % width
-                    rqlstring = '\n'.join( template % (idx + 1, line) for idx, line in enumerate(multi_lines_rql))
-
-
-                msg = '%s\nat: %r\n%s' % (rqlstring, ex.pos,  ex.msg)
-            else:
-                msg = '%s\n%s' % (rqlstring, ex.msg)
-            exc = RQLSyntaxError(msg)
-            exc.__traceback__ = sys.exc_info()[-1]
-            raise exc
-        # try to get error message from yapps
-        try:
-            out = sys.stdout
-            sys.stdout = stream = StringIO()
-            try:
-                print_error(ex, parser._scanner)
-            finally:
-                sys.stdout = out
-            exc = RQLSyntaxError(stream.getvalue())
-            exc.__traceback__ = sys.exc_info()[-1]
-            raise exc
-        except ImportError: # duh?
-            sys.stdout = out
-            exc = RQLSyntaxError('Syntax Error', ex.msg, 'on line',
-                                 1 + pinput.count('\n', 0, ex.pos))
-            exc.__traceback__ = sys.exc_info()[-1]
-            raise exc
-    except NoMoreTokens:
-        msg = 'Could not complete parsing; stopped around here: \n%s'
-        exc = RQLSyntaxError(msg  % parser._scanner)
-        exc.__traceback__ = sys.exc_info()[-1]
-        raise exc
-
-pyparse = parse
--- a/__pkginfo__.py	Mon Jul 28 11:12:58 2014 +0200
+++ b/__pkginfo__.py	Thu May 28 01:13:28 2015 +0200
@@ -60,15 +60,15 @@
 GECODE_VERSION = encode_version(*gecode_version())
 
 if sys.platform != 'win32':
-    ext_modules = [Extension('rql_solve',
-                             ['gecode_solver.cpp'],
+    ext_modules = [Extension('rql.rql_solve',
+                             ['rql/gecode_solver.cpp'],
                               libraries=['gecodeint', 'gecodekernel', 'gecodesearch',],
                              extra_compile_args=['-DGE_VERSION=%s' % GECODE_VERSION],
                          )
                    ]
 else:
-    ext_modules = [ Extension('rql_solve',
-                              ['gecode_solver.cpp'],
+    ext_modules = [ Extension('rql.rql_solve',
+                              ['rql/gecode_solver.cpp'],
                               libraries=['GecodeInt-3-3-1-r-x86',
                                          'GecodeKernel-3-3-1-r-x86',
                                          'GecodeSearch-3-3-1-r-x86',
@@ -85,4 +85,5 @@
     'yapps >= 2.2.0', # XXX to ensure we don't use the broken pypi version
     'logilab-constraint >= 0.5.0', # fallback if the gecode compiled module is missing
     'six >= 1.4.0',
+    'setuptools',
     ]
--- a/_exceptions.py	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of rql.
-#
-# rql is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# rql is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with rql. If not, see <http://www.gnu.org/licenses/>.
-"""Exceptions used in the RQL package."""
-
-__docformat__ = "restructuredtext en"
-
-class RQLException(Exception):
-    """Base exception for exceptions of the rql module."""
-
-class MissingType(RQLException):
-    """Raised when there is some expected type missing from a schema."""
-
-class UsesReservedWord(RQLException):
-    """Raised when the schema uses a reserved word as type or relation."""
-
-class RQLSyntaxError(RQLException):
-    """Raised when there is a syntax error in the rql string."""
-
-class TypeResolverException(RQLException):
-    """Raised when we are unable to guess variables' type."""
-
-class BadRQLQuery(RQLException):
-    """Raised when there is a no sense in the rql query."""
-
-class CoercionError(RQLException):
-    """Failed to infer type of a math expression."""
--- a/analyze.py	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,588 +0,0 @@
-# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of rql.
-#
-# rql is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# rql is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with rql. If not, see <http://www.gnu.org/licenses/>.
-"""Analyze of the RQL syntax tree to get possible types for RQL variables.
-
-"""
-from __future__ import print_function
-
-__docformat__ = "restructuredtext en"
-
-import os
-
-from six import StringIO, string_types
-from six.moves import zip
-
-from rql import TypeResolverException, nodes
-
-
-try:
-    pure = bool(os.environ.get('RQL_USE_PURE_PYTHON_ANALYSE', 0))
-    if pure:
-        raise ImportError
-    from rql import rql_solve
-except ImportError:
-    rql_solve = None
-    import warnings
-    warnings.filterwarnings(action='ignore', module='logilab.constraint.propagation')
-    from logilab.constraint import Repository, Solver, fd
-
-    # Gecode solver not available
-#rql_solve = None # uncomment to force using logilab-constraint
-
-class ConstraintCSPProblem(object):
-    def __init__(self):
-        self.constraints = []
-        self.domains = {}
-        self.scons = []
-        self.output = StringIO()
-
-    def debug(self):
-        print("Domains:", self.domains)
-        print("Constraints:", self.constraints)
-        print("Scons:", self.scons)
-
-    def get_output(self):
-        return self.output.getvalue()
-
-    def printer(self, *msgs):
-        self.output.write(' '.join(str(msg) for msg in msgs))
-        self.output.write('\n')
-
-    def solve(self):
-        repo = Repository(self.domains.keys(), self.domains, self.get_constraints())
-        solver = Solver(printer=self.printer)
-        # used for timing 
-        #import time
-        #t0=time.time()
-        sols = solver.solve(repo, verbose=(True or self.debug))
-        #print "RUNTIME:", time.time()-t0
-        return sols
-
-    def add_var(self, name, values):
-        self.domains[name] = fd.FiniteDomain(values)
-
-    def end_domain_definition(self):
-        pass
-
-    def get_domains(self):
-        return self.domains
-
-    def get_constraints(self):
-        return self.constraints
-
-    def add_expr( self, vars, expr ):
-        self.constraints.append( fd.make_expression( vars, expr ) )
-        self.scons.append(expr)
-
-    def var_has_type(self, var, etype):
-        assert isinstance(etype, string_types)
-        self.add_expr( (var,), '%s == %r' % (var, etype) )
-
-    def var_has_types(self, var, etypes):
-        etypes = tuple(etypes)
-        for t in etypes:
-            assert isinstance(t, string_types)
-        if len(etypes) == 1:
-            cstr = '%s == "%s"' % (var, etypes[0])
-        else:
-            cstr = '%s in %s ' % (var, etypes)
-        self.add_expr( (var,), cstr)
-
-    def vars_have_same_types(self, varnames, types):
-        self.add_expr( varnames, '%s in %s' % ( '=='.join(varnames), types))
-
-    def or_and(self, equalities):
-        orred = set()
-        variables = set()
-        for orred_expr in equalities:
-            anded = set()
-            for vars, types in orred_expr:
-                types=tuple(types)
-                for t in types:
-                    assert isinstance(t, string_types)
-                if len(types)==1:
-                    anded.add( '%s == "%s"' % ( '=='.join(vars), types[0]) )
-                else:
-                    anded.add( '%s in %s' % ( '=='.join(vars), types) )
-                for var in vars:
-                    variables.add(var)
-            orred.add( '(' + ' and '.join( list(anded) ) + ')' )
-        expr = " or ".join( list(orred) )
-        self.add_expr( tuple(variables), expr )
-
-# GECODE based constraint solver
-_AND = 0 # symbolic values
-_OR = 1
-_EQ = 2
-_EQV = 3
-
-OPSYM={
-    _AND:"and",
-    _OR:"or",
-    _EQ:"eq",
-    _EQV:"eqv"
-}
-
-class GecodeCSPProblem(object):
-    """Builds an internal representation of the constraint
-    that will be passed to the rql_solve module which implements
-    a gecode-based solver
-
-    The internal representation is a tree builds with lists of lists
-    the first item of the list is the node type (_AND,_OR,_EQ,_EQV)
-
-    an example : ["and", [ "eq",0,0 ], ["or", ["eq", 1, 1], ["eq", 1, 2] ] ]
-
-    means Var(0) == Value(0) and ( Var(1)==Val(1) or Var(1) == Val(2)
-
-    TODO: at the moment the solver makes no type checking on the structure
-    of the tree thus can crash badly if something wrong is handled to it
-    this should not happend as the building of the tree is done internally
-    but it should be fixed anyways.
-    When fixing that we should also replace string nodes by integers
-    """
-    def __init__(self):
-        self.constraints = []
-        self.op = [ _AND ]
-        self.domains = {}       # maps var name -> var value
-        self.variables = {}     # maps var name -> var index
-        self.ivariables = []    # maps var index-> var name
-        self.values = {}        # maps val name -> val index
-        self.all_values = set() # this gets turned into a list later
-        self.idx_domains = []   # maps var index -> list of val index
-        self.ivalues = {}       # only used for debugging
-
-    def debug(self):
-        self.ivalues = {}
-        for val_name, val_num in self.values.items():
-            self.ivalues[val_num] = val_name
-        print("Domains:", self.domains)
-        print("Ops:", self.pretty_print_ops(self.op))
-        print("Variables:", self.variables)
-        print("Values:", self.values)
-
-
-    def pretty_print_ops(self, ops):
-        if ops[0] in (_AND, _OR):
-            res = [ OPSYM[ops[0]], '(' ]
-            for op in ops[1:]:
-                res.append(self.pretty_print_ops(op))
-                res.append(',')
-            res.append( ')' )
-            return "".join(res)
-        elif ops[0] == _EQ:
-            return "%s==%s" % (self.ivariables[ops[1]], self.ivalues[ops[2]])
-        elif ops[0] == _EQV:
-            res = [ self.ivariables[k] for k in ops[1:] ]
-            return '~='.join(res)
-
-    def get_output(self):
-        return ""
-
-    def solve(self):
-        constraints = self.op
-
-        # used for timing
-        #import time
-        #t0=time.time()
-
-        sols = rql_solve.solve( self.idx_domains, len(self.all_values), constraints )
-        rql_sols = []
-        for s in sols:
-            r={}
-            for var, val in zip(self.ivariables, s):
-                r[var] = self.all_values[val]
-            rql_sols.append(r)
-        #print "RUNTIME:", time.time()-t0
-        return rql_sols
-
-    def add_var(self, name, values):
-        assert name not in self.variables
-        self.all_values.update( values )
-        self.variables[name] = len(self.variables)
-        self.ivariables.append(name)
-        self.domains[name] = values
-
-    def end_domain_definition(self):
-        # maps integer->value
-        self.all_values = list(self.all_values)
-        # maps value->integer
-        self.values = dict( [ (v,i) for i,v in enumerate(self.all_values)] )
-        #print self.values
-        #print self.domains
-        for var_name in self.ivariables:
-            val_domain = self.domains[var_name]
-            idx_domain = [ self.values[val] for val in val_domain ]
-            self.idx_domains.append( idx_domain )
-
-    def and_eq( self, var, value ):
-        self.op.append( [_EQ, self.variables[var], self.values[value] ] )
-
-    def equal_vars(self, varnames):
-        if len(varnames)>1:
-            self.op.append( [ _EQV] + [ self.variables[v] for v in varnames ] )
-
-    def var_has_type(self, var, etype):
-        self.and_eq( var, etype)
-
-    def var_has_types(self, var, etypes):
-        for t in etypes:
-            assert isinstance(t, string_types)
-        if len(etypes) == 1:
-            self.and_eq( var, tuple(etypes)[0] )
-        else:
-            orred = [ _OR ]
-            for t in etypes:
-                try:
-                    orred.append( [ _EQ, self.variables[var], self.values[t] ] )
-                except KeyError:
-                    # key error may be raised by self.values[t] if self.values
-                    # reflects constraints from subqueries
-                    continue
-            self.op.append( orred )
-
-    def vars_have_same_types(self, varnames, types):
-        self.equal_vars( varnames )
-        for var in varnames:
-            self.var_has_types( var, types )
-
-    def or_and(self, equalities):
-        orred = [ _OR ]
-        for orred_expr in equalities:
-            anded = [ _AND ]
-            for vars, types in orred_expr:
-                self.equal_vars( vars )
-                for t in types:
-                    assert isinstance(t, string_types)
-                for var in vars:
-                    if len(types)==1:
-                        anded.append( [ _EQ, self.variables[var], self.values[types[0]] ] )
-                    else:
-                        or2 = [ _OR ]
-                        for t in types:
-                            or2.append(  [_EQ, self.variables[var], self.values[t] ] )
-                        anded.append( or2 )
-            orred.append(anded)
-        self.op.append(orred)
-
-if rql_solve is None:
-    CSPProblem = ConstraintCSPProblem
-else:
-    CSPProblem = GecodeCSPProblem
-
-#CSPProblem = ConstraintCSPProblem
-
-
-class ETypeResolver(object):
-    """Resolve variables types according to the schema.
-
-    CSP modelisation:
-     * variable    <-> RQL variable
-     * domains     <-> different entity's types defined in the schema
-     * constraints <-> relations between (RQL) variables
-    """
-    var_solkey = 'possibletypes'
-
-    def __init__(self, schema, uid_func_mapping=None):
-        """
-        :Parameters:
-         * `schema`: an object describing entities and relations that implements
-           the ISchema interface.
-         * `uid_func_mapping`: a dictionary where keys are strings representing an
-           attribute used as a Unique IDentifier and values are methods that
-           accept attribute values and return entity's types.
-           [mapping from relation to function taking rhs value as argument
-           and returning an entity type].
-        """
-        self.debug = 0
-        self.set_schema(schema)
-        if uid_func_mapping is None:
-            self.uid_func_mapping = {}
-            self.uid_func = None
-        else:
-            self.uid_func_mapping = uid_func_mapping
-            self.uid_func = next(iter(uid_func_mapping.values()))
-
-    def set_schema(self, schema):
-        self.schema = schema
-        # default domains for a variable
-        self._base_domain = set(str(etype) for etype in schema.entities())
-        self._nonfinal_domain = set(str(etype) for etype in schema.entities()
-                                    if not etype.final)
-
-    def solve(self, node, constraints):
-        # debug info
-        if self.debug > 1:
-            print("- AN1 -"+'-'*80)
-            print(node)
-            print("CONSTRAINTS:")
-            constraints.debug()
-
-        sols = constraints.solve()
-
-        if not sols:
-            rql = node.as_string('utf8', self.kwargs)
-            ex_msg = 'Unable to resolve variables types in "%s"' % (rql,)
-            if True or self.debug:
-                ex_msg += '\n%s' % (constraints.get_output(),)
-            raise TypeResolverException(ex_msg)
-        node.set_possible_types(sols, self.kwargs, self.var_solkey)
-
-    def _visit(self, node, constraints=None):
-        """Recurse down the tree.
-
-            * node: rql node to process
-            * constraints: a XxxCSPProblem object.
-        """
-        func = getattr(self, 'visit_%s' % node.__class__.__name__.lower())
-        if constraints is None:
-            func(node)
-        elif func(node, constraints) is None:
-            for c in node.children:
-                self._visit(c, constraints)
-
-    def _uid_node_types(self, valnode):
-        types = set()
-        for cst in valnode.iget_nodes(nodes.Constant):
-            assert cst.type
-            if cst.type == 'Substitute':
-                eid = self.kwargs[cst.value]
-                self.deambiguifiers.add(cst.value)
-            else:
-                eid = cst.value
-            cst.uidtype = self.uid_func(cst.eval(self.kwargs))
-            types.add(cst.uidtype)
-        return types
-
-    def _init_stmt(self, node):
-        pb = CSPProblem()
-        # set domain for all the variables
-        for var in node.defined_vars.values():
-            pb.add_var( var.name, self._base_domain )
-        # no variable short cut
-        return pb
-
-    def _extract_constraint(self, constraints, var, term, get_target_types):
-        if self.uid_func:
-            alltypes = set()
-            for etype in self._uid_node_types(term):
-                for targettypes in get_target_types(etype):
-                    alltypes.add(targettypes)
-        else:
-            alltypes = get_target_types()
-        domain = constraints.domains[var]
-        constraints.var_has_types( var, [str(t) for t in alltypes if t in domain] )
-
-    def visit(self, node, uid_func_mapping=None, kwargs=None, debug=False):
-        # FIXME: not thread safe
-        self.debug = debug
-        if uid_func_mapping is not None:
-            assert len(uid_func_mapping) <= 1
-            self.uid_func_mapping = uid_func_mapping
-            self.uid_func = next(iter(uid_func_mapping.values()))
-        self.kwargs = kwargs
-        self.deambiguifiers = set()
-        self._visit(node)
-        if uid_func_mapping is not None:
-            self.uid_func_mapping = None
-            self.uid_func = None
-        return self.deambiguifiers
-
-    def visit_union(self, node):
-        for select in node.children:
-            self._visit(select)
-
-    def visit_insert(self, node):
-        if not node.defined_vars:
-            node.set_possible_types([{}])
-            return
-        constraints = self._init_stmt(node)
-        constraints.end_domain_definition()
-        for etype, variable in node.main_variables:
-            if node.TYPE == 'delete' and etype == 'Any':
-                continue
-            assert etype in self.schema, etype
-            var = variable.name
-            constraints.var_has_type( var, etype )
-        for relation in node.main_relations:
-            self._visit(relation, constraints)
-        # get constraints from the restriction subtree
-        if node.where is not None:
-            self._visit(node.where, constraints)
-        self.solve(node, constraints)
-
-    visit_delete = visit_insert
-
-    def visit_set(self, node):
-        if not node.defined_vars:
-            node.set_possible_types([{}])
-            return
-        constraints = self._init_stmt(node)
-        constraints.end_domain_definition()
-        for relation in node.main_relations:
-            self._visit(relation, constraints)
-        # get constraints from the restriction subtree
-        if node.where is not None:
-            self._visit(node.where, constraints)
-        self.solve(node, constraints)
-
-    def visit_select(self, node):
-        if not (node.defined_vars or node.aliases):
-            node.set_possible_types([{}])
-            return
-        for subquery in node.with_: # resolve subqueries first
-            self.visit_union(subquery.query)
-        constraints = self._init_stmt(node)
-        for ca in node.aliases.values():
-            etypes = set(stmt.selection[ca.colnum].get_type(sol, self.kwargs)
-                         for stmt in ca.query.children for sol in stmt.solutions)
-            constraints.add_var( ca.name, etypes )
-        constraints.end_domain_definition()
-        if self.uid_func:
-            # check rewritten uid const
-            for consts in node.stinfo['rewritten'].values():
-                if not consts:
-                    continue
-                uidtype = self.uid_func(consts[0].eval(self.kwargs))
-                for const in consts:
-                    const.uidtype = uidtype
-        # get constraints from the restriction subtree
-        if node.where is not None:
-            self._visit(node.where, constraints)
-        elif not node.with_:
-            varnames = [v.name for v in node.get_selected_variables()]
-            if varnames:
-                # add constraint on real relation types if no restriction
-                types = [eschema.type for eschema in self.schema.entities()
-                         if not eschema.final]
-                constraints.vars_have_same_types( varnames, types )
-        self.solve(node, constraints)
-
-    def visit_relation(self, relation, constraints):
-        """extract constraints for an relation according to it's  type"""
-        if relation.is_types_restriction():
-            self.visit_type_restriction(relation, constraints)
-            return None
-        rtype = relation.r_type
-        lhs, rhs = relation.get_parts()
-        if rtype == 'identity' and relation.neged(strict=True):
-            return None
-        if rtype in self.uid_func_mapping:
-            if isinstance(relation.parent, nodes.Not) or relation.operator() != '=':
-                # non final entity types
-                etypes = self._nonfinal_domain
-            else:
-                etypes = self._uid_node_types(rhs)
-            if etypes:
-                constraints.var_has_types( lhs.name, etypes )
-                return None
-        if isinstance(rhs, nodes.Comparison):
-            rhs = rhs.children[0]
-        rschema = self.schema.rschema(rtype)
-        if isinstance(lhs, nodes.Constant): # lhs is a constant node (simplified tree)
-            if not isinstance(rhs, nodes.VariableRef):
-                return None
-            self._extract_constraint(constraints, rhs.name, lhs, rschema.objects)
-        elif isinstance(rhs, nodes.Constant) and not rschema.final:
-            # rhs.type is None <-> NULL
-            if not isinstance(lhs, nodes.VariableRef) or rhs.type is None:
-                return None
-            self._extract_constraint(constraints, lhs.name, rhs, rschema.subjects)
-        elif not isinstance(lhs, nodes.VariableRef):
-            # XXX: check relation is valid
-            return None
-        elif isinstance(rhs, nodes.VariableRef):
-            lhsvar = lhs.name
-            rhsvar = rhs.name
-            lhsdomain = constraints.domains[lhsvar]
-            # filter according to domain necessary for column aliases
-            rhsdomain = constraints.domains[rhsvar]
-            res = []
-            var_types = []
-            same_var = (rhsvar == lhsvar)
-
-            for frometype, toetypes in rschema.associations():
-                fromtype = str(frometype)
-                if fromtype in lhsdomain:
-                    totypes = set(str(t) for t in toetypes)
-                    ptypes = totypes & rhsdomain
-                    res.append( [ ([lhsvar], [str(fromtype)]),
-                                  ([rhsvar], list(ptypes)) ] )
-                    if same_var and (fromtype in totypes): #ptypes ?
-                        var_types.append(fromtype)
-            constraints.or_and(res)
-            if same_var:
-                constraints.var_has_types( lhsvar, var_types)
-        else:
-            # XXX consider rhs.get_type?
-            lhsdomain = constraints.domains[lhs.name]
-            ptypes = [str(subj) for subj in rschema.subjects()
-                      if subj in lhsdomain]
-            constraints.var_has_types( lhs.name, ptypes )
-        return None
-
-    def visit_type_restriction(self, relation, constraints):
-        lhs, rhs = relation.get_parts()
-        etypes = set(c.value for c in rhs.iget_nodes(nodes.Constant)
-                     if c.type == 'etype')
-        if relation.r_type == 'is_instance_of':
-            for etype in tuple(etypes):
-                for specialization in self.schema.eschema(etype).specialized_by():
-                    etypes.add(specialization.type)
-        if relation.neged(strict=True):
-            etypes = frozenset(t for t in self._nonfinal_domain if not t in etypes)
-
-        constraints.var_has_types( lhs.name, [ str(t) for t in etypes ] )
-
-    def visit_and(self, et, constraints):
-        pass
-    def visit_or(self, ou, constraints):
-        pass
-    def visit_not(self, et, constraints):
-        pass
-    def visit_comparison(self, comparison, constraints):
-        pass
-    def visit_mathexpression(self, mathexpression, constraints):
-        pass
-    def visit_function(self, function, constraints):
-        pass
-    def visit_variableref(self, variableref, constraints):
-        pass
-    def visit_constant(self, constant, constraints):
-        pass
-    def visit_keyword(self, keyword, constraints):
-        pass
-    def visit_exists(self, exists, constraints):
-        pass
-
-
-class ETypeResolverIgnoreTypeRestriction(ETypeResolver):
-    """same as ETypeResolver but ignore type restriction relation
-
-    results are stored in as the 'allpossibletypes' key in variable'stinfo
-    """
-    var_solkey = 'allpossibletypes'
-
-    def visit_type_restriction(self, relation, constraints):
-        pass
-
-    def visit_not(self, et, constraints):
-        child = et.children[0]
-        if isinstance(child, nodes.Relation) and \
-           not self.schema.rschema(child.r_type).final:
-            return True
--- a/base.py	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,201 +0,0 @@
-# copyright 2004-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of rql.
-#
-# rql is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# rql is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with rql. If not, see <http://www.gnu.org/licenses/>.
-"""Base classes for RQL syntax tree nodes.
-
-Note: this module uses __slots__ to limit memory usage.
-"""
-
-__docformat__ = "restructuredtext en"
-
-from rql.utils import VisitableMixIn
-
-
-class BaseNode(VisitableMixIn):
-    __slots__ = ('parent',)
-
-    def __str__(self):
-        return self.as_string(encoding='utf-8')
-
-    def as_string(self, encoding=None, kwargs=None):
-        """Return the tree as an encoded rql string."""
-        raise NotImplementedError()
-
-    def initargs(self, stmt):
-        """Return list of arguments to give to __init__ to clone this node.
-
-        I don't use __getinitargs__ because I'm not sure it should interfer with
-        copy/pickle
-        """
-        return ()
-
-    @property
-    def root(self):
-        """Return the root node of the tree"""
-        return self.parent.root
-
-    @property
-    def stmt(self):
-        """Return the Select node to which this node belong"""
-        return self.parent.stmt
-
-    @property
-    def scope(self):
-        """Return the scope node to which this node belong (eg Select or Exists
-        node)
-        """
-        return self.parent.scope
-
-    def get_nodes(self, klass):
-        """Return the list of nodes of a given class in the subtree.
-
-        :type klass: a node class (Relation, Constant, etc.)
-        :param klass: the class of nodes to return
-
-        :rtype: list
-        """
-        stack = [self]
-        result = []
-        while stack:
-            node = stack.pop()
-            if isinstance(node, klass):
-                result.append(node)
-            else:
-                stack += node.children
-        return result
-
-    def iget_nodes(self, klass):
-        """Return an iterator over nodes of a given class in the subtree.
-
-        :type klass: a node class (Relation, Constant, etc.)
-        :param klass: the class of nodes to return
-
-        :rtype: iterator
-        """
-        stack = [self]
-        while stack:
-            node = stack.pop()
-            if isinstance(node, klass):
-                yield node
-            else:
-                stack += node.children
-
-    def is_equivalent(self, other):
-        if not other.__class__ is self.__class__:
-            return False
-        for i, child in enumerate(self.children):
-            try:
-                if not child.is_equivalent(other.children[i]):
-                    return False
-            except IndexError:
-                return False
-        return True
-
-    def index_path(self):
-        if self.parent is None:
-            return []
-        myindex = self.parent.children.index(self)
-        parentindexpath = self.parent.index_path()
-        parentindexpath.append(myindex)
-        return parentindexpath
-
-    def go_to_index_path(self, path):
-        if not path:
-            return self
-        return self.children[path[0]].go_to_index_path(path[1:])
-
-    def copy(self, stmt):
-        """Create and return a copy of this node and its descendant.
-
-        stmt is the root node, which should be use to get new variables
-        """
-        new = self.__class__(*self.initargs(stmt))
-        for child in self.children:
-            new.append(child.copy(stmt))
-        return new
-
-
-class Node(BaseNode):
-    """Class for nodes of the tree which may have children (almost all...)"""
-    __slots__ = ('children',)
-
-    def __init__(self) :
-        self.parent = None
-        self.children = []
-
-    def append(self, child):
-        """add a node to children"""
-        self.children.append(child)
-        child.parent = self
-
-    def remove(self, child):
-        """Remove a child node. Return the removed node, its old parent and
-        index in the children list.
-        """
-        index = self.children.index(child)
-        del self.children[index]
-        parent = child.parent
-        child.parent = None
-        return child, parent, index
-
-    def insert(self, index, child):
-        """insert a child node"""
-        self.children.insert(index, child)
-        child.parent = self
-
-    def replace(self, old_child, new_child):
-        """replace a child node with another"""
-        i = self.children.index(old_child)
-        self.children.pop(i)
-        self.children.insert(i, new_child)
-        new_child.parent = self
-        return old_child, self, i
-
-class BinaryNode(Node):
-    __slots__ = ()
-
-    def __init__(self, lhs=None, rhs=None):
-        Node.__init__(self)
-        if not lhs is None:
-            self.append(lhs)
-        if not rhs is None:
-            self.append(rhs)
-
-    def remove(self, child):
-        """Remove the child and replace this node with the other child."""
-        index = self.children.index(child)
-        return self.parent.replace(self, self.children[not index])
-
-    def get_parts(self):
-        """Return the left hand side and the right hand side of this node."""
-        return self.children[0], self.children[1]
-
-
-class LeafNode(BaseNode):
-    """Class optimized for leaf nodes."""
-    __slots__ = ()
-
-    @property
-    def children(self):
-        return ()
-
-    def copy(self, stmt):
-        """Create and return a copy of this node and its descendant.
-
-        stmt is the root node, which should be use to get new variables.
-        """
-        return self.__class__(*self.initargs(stmt))
--- a/compare.py	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,216 +0,0 @@
-# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of rql.
-#
-# rql is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# rql is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with rql. If not, see <http://www.gnu.org/licenses/>.
-"""Comparing syntax trees.
-
-"""
-__docformat__ = "restructuredtext en"
-
-
-from six.moves import range
-
-from rql.nodes import VariableRef, Variable, Function, Relation, Comparison
-
-def compare_tree(request1, request2):
-    """Compares 2 RQL requests.
-
-    :rtype: bool
-    :return: True if both requests would return the same results.
-    """
-    return make_canon_dict(request1) == make_canon_dict(request2)
-
-def make_canon_dict(rql_tree, verbose=0):
-    """Return a canonical representation of the request as a dictionnary."""
-    allvars = {}
-    canon = {
-        'all_variables' : allvars,
-        'selected' : [],
-        'restriction' : {},
-        }
-
-    canon = RQLCanonizer().visit(rql_tree, canon)
-
-    # forge variable name
-    for var, name_parts in allvars.values():
-        name_parts.sort()
-        var.name = ':'.join(name_parts)
-    sort(canon)
-    if verbose:
-        print 'CANON FOR', rql_tree
-        from pprint import pprint
-        pprint(canon)
-    return canon
-
-def sort(canon_dict):
-    """Remove the all_variables entry and sort other entries in place."""
-    del canon_dict['all_variables']
-    canon_dict['selection'].sort()
-    for values in canon_dict['restriction'].values():
-        values.sort()
-
-class SkipChildren(Exception):
-    """Signal indicating to ignore the current child."""
-
-class RQLCanonizer(object):
-    """Build a dictionnary which represents a RQL syntax tree."""
-
-    def visit(self, node, canon):
-        try:
-            node.accept(self, canon)
-        except SkipChildren:
-            return canon
-        for c in node.children:
-            self.visit(c, canon)
-        return canon
-
-    def visit_select(self, select, canon):
-        allvars = canon['all_variables']
-        for var in select.defined_vars.values():
-            allvars[var] = (Variable(var.name), [])
-        canon['selection'] = l = []
-        selected = select.selected
-        for i in range(len(selected)):
-            node = selected[i]
-            if isinstance(node, VariableRef):
-                node = node.variable
-                allvars[node][1].append(str(i))
-                l.append(allvars[node][0])
-            else:  # Function
-                l.append(node)
-                for var in node.iget_nodes(VariableRef):
-                    var.parent.replace(var, allvars[var.variable][0])
-
-    def visit_group(self, group, canon):
-        canon['group'] = group
-
-    def visit_sort(self, sort, canon):
-        canon['sort'] = sort
-
-    def visit_sortterm(self, sortterm, canon):
-        pass
-
-    def visit_and(self, et, canon):
-        pass
-
-    def visit_or(self, ou, canon):
-        canon_dict = {}
-        keys = []
-        for expr in ou.get_nodes(Relation):
-            key = '%s%s' % (expr.r_type, expr._not)
-            canon_dict.setdefault(key, []).append(expr)
-            keys.append(key)
-        keys.sort()
-        r_type = ':'.join(keys)
-        r_list = canon['restriction'].setdefault(r_type, [])
-        done = {}
-        for key in keys:
-            if key in done:
-                continue
-            done[key] = None
-            for expr in canon_dict[key]:
-                self.manage_relation(expr, canon, r_list)
-        raise SkipChildren()
-
-    def manage_relation(self, relation, canon, r_list):
-        lhs, rhs = relation.get_parts()
-        # handle special case of the IN function
-        func = rhs.children[0]
-        if isinstance(func, Function) and func.name == 'IN':
-            if not relation._not:
-                base_key = '%s%s' % (relation.r_type, relation._not)
-                if not canon['restriction'][base_key]:
-                    del canon['restriction'][base_key]
-                key = ':'.join([base_key] * len(func.children))
-                r_list = canon['restriction'].setdefault(key, [])
-            for e in func.children:
-                eq_expr = Relation(relation.r_type, relation._not)
-                eq_expr.append(lhs)
-                eq_expr.append(Comparison('=', e))
-                self.manage_relation(eq_expr, canon, r_list)
-                # restaure parent attribute to avoid problem later
-                e.parent = func
-                lhs.parent = relation
-            return
-        # build a canonical representation for this relation
-        lhs_expr_reminder = make_lhs_reminder(lhs, canon)
-        rhs_expr_reminder = make_rhs_reminder(rhs, canon)
-        reminder = (lhs_expr_reminder, rhs_expr_reminder)
-        # avoid duplicate
-        if reminder in r_list:
-            return
-        r_list.append(reminder)
-        # make a string which represents this relation (we'll use it later
-        # to build variables' name)
-        expr_reminder = relation.r_type
-        lhs_vars = lhs.get_nodes(VariableRef)
-        if not lhs_vars:
-            expr_reminder = "%s_%s" % (lhs, expr_reminder)
-        rhs_vars = rhs.get_nodes(VariableRef)
-        if not rhs_vars:
-            expr_reminder = "%s_%s" % (expr_reminder, rhs)
-
-        for var in lhs_vars + rhs_vars:
-            var = var.variable
-            canon['all_variables'][var][1].append(expr_reminder)
-
-
-    def visit_relation(self, relation, canon):
-        key = '%s%s' % (relation.r_type, relation._not)
-        r_list = canon['restriction'].setdefault(key, [])
-        self.manage_relation(relation, canon, r_list)
-
-
-    def visit_comparison(self, comparison, canon):
-        """do nothing for this node type"""
-
-    def visit_mathexpression(self, mathexpression, canon):
-        """do nothing for this node type"""
-
-    def visit_function(self, function, canon):
-        """do nothing for this node type"""
-
-    def visit_variableref(self, varref, canon):
-        varref.parent.replace(varref,
-                              canon['all_variables'][varref.variable][0])
-
-    def visit_constant(self, constante, canon):
-        """do nothing for this node type"""
-
-    def visit_union(self, *args):
-        raise NotImplementedError('union comparison not implemented')
-
-
-def make_lhs_reminder(lhs, canon):
-    """Return a reminder for a relation's left hand side
-    (i.e a VariableRef object).
-    """
-    try:
-        lhs = canon['all_variables'][lhs.variable][0]
-    except (KeyError, IndexError):
-        pass
-    return ('=', lhs)
-
-def make_rhs_reminder(rhs, canon):
-    """Return a reminder for a relation's right hand side
-    (i.e a Comparison object).
-    """
-    child = rhs.children[0]
-    try:
-        child = canon['all_variables'][child.variable][0]
-    except AttributeError:
-        pass
-    return (rhs.operator, child)
--- a/editextensions.py	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of rql.
-#
-# rql is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# rql is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with rql. If not, see <http://www.gnu.org/licenses/>.
-"""RQL functions for manipulating syntax trees."""
-
-__docformat__ = "restructuredtext en"
-
-from rql.nodes import Constant, Variable, VariableRef, Relation, make_relation
-
-def switch_selection(rqlst, new_var, old_var):
-    """Switch the select variable from old_var (VariableRef instance) to
-    new_var (Variable instance).
-    """
-    rqlst.remove_selected(old_var)
-    rqlst.add_selected(new_var, 0)
-
-def add_main_restriction(rqlst, new_type, r_type, direction):
-    """The result_tree must represent the same restriction as 'rqlst' and :
-       - 'new_varname' IS <new_type>
-       - 'old_main_var' <r_type> 'new_varname'
-    """
-    new_var = rqlst.make_variable(new_type)
-    # new_var IS new_type
-    rqlst.add_restriction(make_relation(new_var, 'is', (new_type, 'etype'),
-                                        Constant))
-    # new_var REL old_var (ou l'inverse)
-    old_var = rqlst.selected[0]
-    if direction == 'subject':
-        rel_rest = make_relation(old_var.variable, r_type, (new_var, 1),
-                                 VariableRef)
-    else:
-        rel_rest = make_relation(new_var, r_type, (old_var.variable, 1),
-                                 VariableRef)
-    rqlst.add_restriction(rel_rest)
-    return new_var
-
-def remove_has_text_relation(node):
-    """Remove has_text relation."""
-    for rel in node.iget_nodes(Relation):
-        if rel.r_type == 'has_text':
-            node.remove_node(rel)
-            return
-
-def get_vars_relations(node):
-    """Return a dict with 'var_names' as keys, and the list of relations which
-    concern them.
-    """
-    exp_concerns = {}
-    for exp in node.iget_nodes(Relation):
-        for vref in exp.iget_nodes(VariableRef):
-            exp_concerns.setdefault(vref.name, []).append(exp)
-    return exp_concerns
--- a/gecode_solver.cpp	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,544 +0,0 @@
-#include <Python.h>
-#include <iostream>
-#include <string.h>
-#include <exception>
-#include "gecode/kernel.hh"
-#include "gecode/int.hh"
-#include "gecode/search.hh"
-
-#if 1
-#define debug(fmt,...)
-#else
-#define debug(fmt,...) printf(fmt, ##__VA_ARGS__)
-#endif
-
-
-#define PM_VERSION(a,b,c) ((a<<16)+(b<<8)+(c))
-// There is no easy way to test for gecode version here
-// so the build system must pass GE_VERSION accordingly
-// by default we build for 3.1.0 if GECODE_VERSION exists
-
-#ifndef GE_VERSION
-#ifndef GECODE_VERSION
-#define GE_VERSION PM_VERSION(2,1,2)
-#else
-#define GE_VERSION PM_VERSION(3,1,0)
-#endif
-#endif
-
-#if GE_VERSION < PM_VERSION(2,0,0)
-#define SELF this
-#define INT_VAR_NONE BVAR_NONE
-#define INT_VAL_MIN BVAL_MIN
-
-#elif GE_VERSION < PM_VERSION(3,0,0)
-#define SELF this
-#define SET_VAR_SIZE_MAX SET_VAR_MAX_CARD
-#define SET_VAL_MIN_INC SET_VAL_MIN
-#else
-#define SELF (*this)
-#define convexHull convex
-#endif
-
-#if GE_VERSION >= PM_VERSION(4, 0, 0)
-#define INT_VAR_NONE INT_VAR_NONE()
-#define INT_VAL_MIN INT_VAL_MIN()
-#endif
-
-using namespace std;
-using namespace Gecode;
-
-#define USE_CLOCK
-#ifdef USE_CLOCK
-#include <ctime>
-
-/// Timer interface stolen from gecode examples
-class Timer {
-private:
-    clock_t t0;
-public:
-    void start(void);
-    double stop(void);
-};
-
-forceinline void
-Timer::start(void) {
-    t0 = clock();
-}
-
-forceinline double
-Timer::stop(void) {
-    return (static_cast<double>(clock()-t0) / CLOCKS_PER_SEC) * 1000.0;
-}
-#else
-#include <sys/time.h>
-#include <unistd.h>
-
-/// Timer interface stolen from gecode examples
-class Timer {
-private:
-    struct timeval t0;
-public:
-    void start(void);
-    double stop(void);
-};
-
-forceinline void
-Timer::start(void) {
-    gettimeofday( &t0, NULL );
-}
-forceinline double
-Timer::stop(void) {
-    struct timeval t1;
-    gettimeofday( &t1, NULL );
-    return (t1.tv_sec - t0.tv_sec)+1e-6*(t1.tv_usec - t0.tv_usec);
-}
-#endif
-
-enum {
-    _AND = 0,
-    _OR  = 1,
-    _EQ  = 2,
-    _EQV = 3
-};
-
-class RqlError : public exception {
-};
-
-class RqlContext {
-    /** Context holding the problem for solving Rql constraints
-	we keep the info as Python objects and parse the problem
-	during the creation of the Gecode problem
-    */
-public:
-    RqlContext(long nvars, PyObject* domains,
-	       long nvalues, PyObject* constraints, PyObject* sols):
-	solutions(-1), // return every solutions
-	time(-1),    // time limit in case the problem is too big
-	fails(-1),     // ?? used by GecodeStop ...
-	nvars(nvars),  // Number of variables
-	nvalues(nvalues), // Number of values
-	constraints(constraints), // A python list, holding the root of the problem
-	sols(sols),    // an empty list that will receive the solutions
-	domains(domains), // A PyList of PyList, one for each var,
-	                  // holding the allowable integer values
-	verbosity(false)  // can help debugging
-    {
-    }
-
-    long solutions;
-    long time;
-    long fails;
-    long nvars;
-    long nvalues;
-    PyObject* constraints;
-    PyObject* sols;
-    PyObject* domains;
-    bool verbosity;
-};
-
-class RqlSolver : public Space {
-/* A gecode Space
-   this is a strange beast that requires special methods and
-   behavior (mostly, copy and (bool,share,space) constructor
-*/
-protected:
-    /* The variables we try to find values for
-       these are the only 'public' variable of the
-       problem.
-
-       we use a lot more intermediate variables but
-       they shouldn't be member of the space
-     */
-    IntVarArray  variables;
-
-public:
-    RqlSolver(const RqlContext& pb):
-	variables(SELF,     // all gecode variable keep a reference to the space
-		  pb.nvars, // number of variables
-		  0,        // minimum domain value
-		  pb.nvalues-1) // max domain value (included)
-    {
-	/* Since we manipulate Boolean expression and
-	   we need to assign truth value to subexpression
-	   eg (a+b)*(c+d) will be translated as :
-	   root = x1 * x2
-	   x1 = a+b
-	   x2 = c+d
-	   root = True
-	*/
-	BoolVar root(SELF, 1,1);
-	
-	set_domains( pb.domains );
-	add_constraints( pb.constraints, root );
-
-	/* the branching strategy, there must be one,
-	   changing it might improve performance, but
-	   in out case, we almost never propagate (ie
-	   gecode solves the problem during its creation)
-	*/
-	branch(SELF, variables, INT_VAR_NONE, INT_VAL_MIN);
-    }
-
-    ~RqlSolver() {};
-
-    RqlSolver(bool share, RqlSolver& s) : Space(share,s)
-    {
-	/* this is necessary for the solver to fork space
-	   while branching
-	*/
-	variables.update(SELF, share, s.variables);
-    }
-
-    void set_domains( PyObject* domains )
-    {
-	PyObject* ovalues;
-	if (!PyList_Check(domains)) {
-	    throw RqlError();
-	}
-	int n = PyList_Size( domains );
-	for(int var=0 ;var<n; ++var) {
-	    /* iterate of domains which should contains
-	       list of values
-	       domains[0] contains possible values for var[0]...
-	    */
-	    int i, nval;
-	    ovalues = PyList_GetItem( domains, var );
-	    if (!PyList_Check(ovalues)) {
-		throw RqlError();
-	    }
-	    nval = PyList_Size( ovalues );
-
-	    /* It's a bit cumbersome to construct an IntSet, but
-	       it's the only way to reduce an integer domain to
-	       a discrete set
-	    */
-	    int* vals = new int[nval];
-	    for(i=0;i<nval;++i) {
-		//refcount ok, borrowed ref
-		vals[i] = PyInt_AsLong( PyList_GetItem( ovalues, i ) );
-		if (vals[i]<0) {
-		    /* we don't have negative values and PyInt_AsLong returns
-		       -1 if the object is not an Int */
-		    delete [] vals;
-		    throw RqlError();
-		}
-	    }
-	    IntSet gvalues(vals,nval);
-	    dom(SELF, variables[var], gvalues);
-	    delete [] vals;
-	}
-    }
-
-    /* Dispatch method from Node to specific node type */
-    void add_constraints( PyObject* desc, BoolVar& var )
-    {
-	long type;
-
-	if (!PyList_Check(desc)) {
-	    throw RqlError();
-	}
-	/* the first element of each list (node) is 
-	   a symbolic Int from _AND, _OR, _EQ, _EQV
-	*/
-	type = PyInt_AsLong( PyList_GetItem( desc, 0 ) );
-	
-	switch(type) {
-	case _AND:
-	    add_and( desc, var );
-	    break;
-	case _OR:
-	    add_or( desc, var );
-	    break;
-	case _EQ:
-	    add_equality( desc, var );
-	    break;
-	case _EQV:
-	    add_equivalence( desc, var );
-	    break;
-	default:
-	    throw RqlError();
-	}
-    }
-
-    /* retrieve an int from a list, throw error if int is <0 */
-    long get_uint( PyObject* lst, int index ) {
-	PyObject* val;
-	val = PyList_GetItem(lst, index);
-	if (val<0) {
-	    throw RqlError();
-	}
-	return PyInt_AsLong(val);
-    }
-
-    /* post gecode condition for Var == Value
-       we can't use domain restriction since this
-       condition can be part of an OR clause
-
-       so we post  (var == value) <=> expr_value
-    */
-    void add_equality( PyObject* desc, BoolVar& expr_value ) {
-	long variable, value;
-	
-	variable = get_uint( desc, 1 );
-	value = get_uint( desc, 2 );
-	if (variable==1) {
-	    debug("RQL:%ld == %ld ***\n", variable, value);
-	} else {
-	    debug("RQL:%ld == %ld\n", variable, value);
-	}
-	rel(SELF, variables[variable], IRT_EQ, value, expr_value);
-    }
-
-    /* post gecode condition for Var[i] == Var[j] ... == Var[k]
-
-       there's no operator for assigning chained equality to boolean
-      
-       so we post for 1<=i<=N (var[0] == var[i]) <=> bool[i]
-                  and    bool[1] & ... & bool[N] <=> expr_value
-       that means if all vars are equal expr_value is true
-       if all vars are different from var[0] expr_value is false
-       if some are equals and some false, the constraint is unsatisfiable
-    */
-    void add_equivalence( PyObject* desc, BoolVar& expr_value ) {
-	int len = PyList_Size(desc);
-	int var0 = get_uint( desc, 1 );
-	BoolVarArray terms(SELF, len-2,0,1);
-	debug("RQL:EQV(%d",var0);
-	for (int i=1;i<len-1;++i) {
-	    int var1 = get_uint(desc, i+1);
-	    debug(",%d",var1);
-	    rel(SELF, variables[var0], IRT_EQ, variables[var1], terms[i-1] );
-	}
-	debug(")\n");
-#if GE_VERSION<PM_VERSION(2,0,0)
-    BoolVarArgs terms_args(terms);
-    bool_and(SELF, terms_args, expr_value);
-#else
-	rel(SELF, BOT_AND, terms, expr_value);
-#endif
-    }
-
-    /* simple and relation between nodes */
-    void add_and( PyObject* desc, BoolVar& var ) {
-	int len = PyList_Size(desc);
-	BoolVarArray terms(SELF, len-1,0,1);
-
-	debug("RQL:AND(\n");
-	for(int i=0;i<len-1;++i) {
-	    PyObject* expr = PyList_GetItem(desc, i+1);
-	    add_constraints( expr, terms[i] );
-	}
-	debug("RQL:)\n");
-#if GE_VERSION<PM_VERSION(2,0,0)
-    BoolVarArgs terms_args(terms);
-    bool_and(SELF, terms_args, var);
-#else
-    rel(SELF, BOT_AND, terms, var);
-#endif
-    }
-
-    /* simple or relation between nodes */
-    void add_or( PyObject* desc, BoolVar& var ) {
-	int len = PyList_Size(desc);
-	BoolVarArray terms(SELF, len-1,0,1);
-
-	debug("RQL:OR(\n");
-	for(int i=0;i<len-1;++i) {
-	    PyObject* expr = PyList_GetItem(desc, i+1);
-	    add_constraints( expr, terms[i] );
-	}
-	debug("RQL:)\n");
-#if GE_VERSION<PM_VERSION(2,0,0)
-    BoolVarArgs terms_args(terms);
-    bool_or(SELF, terms_args, var);
-#else
-	rel(SELF, BOT_OR, terms, var);
-#endif
-
-    }
-
-    template <template<class> class Engine>
-    static void run( RqlContext& pb, Search::Stop* stop )
-    {
-	double t0 = 0;
-	int i = pb.solutions;
-	Timer t;
-	RqlSolver* s = new RqlSolver( pb );
-	t.start();
-	unsigned int n_p = 0;
-	unsigned int n_b = 0;
-	if (s->status() != SS_FAILED) {
-	    n_p = s->propagators();
-#if GE_VERSION<PM_VERSION(3,2,0)
-	    n_b = s->branchings();
-#else
-	    n_b = s->branchers();
-#endif
-	}
-#if GE_VERSION<PM_VERSION(2,0,0)
-    Engine<RqlSolver> e(s);
-#else
-    Search::Options opts;
-	//opts.c_d = pb.c_d;
-	//opts.a_d = pb.a_d;
-	opts.stop = stop;
-	Engine<RqlSolver> e(s, opts);
-#endif
-	delete s;
-	do {
-	    RqlSolver* ex = e.next();
-	    if (ex == NULL)
-		break;
-
-	    ex->add_new_solution(pb);
-
-	    delete ex;
-	    t0 = t0 + t.stop();
-	} while (--i != 0 && (pb.time<0 || t0 < pb.time));
-	Search::Statistics stat = e.statistics();
-	if (pb.verbosity) {
-	    cout << endl;
-	    cout << "Initial" << endl
-		 << "\tpropagators:   " << n_p << endl
-		 << "\tbranchings:    " << n_b << endl
-		 << endl
-		 << "Summary" << endl
-		 << "\truntime:       " << t.stop() << endl
-		 << "\tsolutions:     " << abs(static_cast<int>(pb.solutions) - i) << endl
-		 << "\tpropagations:  " << stat.propagate << endl
-		 << "\tfailures:      " << stat.fail << endl
-#if GE_VERSION < PM_VERSION(3,0,0)
-		 << "\tclones:        " << stat.clone << endl
-		 << "\tcommits:       " << stat.commit << endl
-#else
-		 << "\tdepth:        " << stat.depth << endl
-		 << "\tnode:       " << stat.node << endl
-#endif
-#if GE_VERSION < PM_VERSION(4,2,0)
-		 << "\tpeak memory:   "
-		 << static_cast<int>((stat.memory+1023) / 1024) << " KB"
-		 << endl
-#endif
-		    ;
-	}
-    }
-
-    /* We append each solutions to `sols` as a
-       tuple `t` of the values assigned to each var
-       that is t[i] = solution for var[i]
-    */
-    virtual void add_new_solution(RqlContext& pb) {
-	PyObject *tuple, *ival;
-
-	tuple = PyTuple_New( pb.nvars );
-
-	for(int i=0;i<pb.nvars;++i) {
-	    ival = PyInt_FromLong( variables[i].val() );
-	    PyTuple_SetItem( tuple, i, ival );
-	}
-	PyList_Append( pb.sols, tuple );
-    }
-
-    /* another function need by gecode kernel */
-    virtual Space* copy(bool share) {
-	return new RqlSolver(share, *this);
-    }
-
-};
-
-class FailTimeStop : public Search::Stop {
-private:
-    Search::TimeStop *ts;
-    Search::FailStop *fs;
-public:
-    FailTimeStop(int fails, int time):ts(0L),fs(0L) {
-	if (time>=0)
-	    ts = new Search::TimeStop(time);
-	if (fails>=0) {
-	    fs = new Search::FailStop(fails);
-	}
-    }
-#if GE_VERSION < PM_VERSION(3,1,0)
-    bool stop(const Search::Statistics& s) {
-	int sigs = PyErr_CheckSignals();
-	bool fs_stop = false;
-	bool ts_stop = false;
-	if (fs) {
-	    fs_stop = fs->stop(s);
-	}
-	if (ts) {
-	    ts_stop = ts->stop(s);
-	}
-	return sigs || fs_stop || ts_stop;
-    }
-#else
-    /* from gecode 3.1.0 */
-    bool stop(const Search::Statistics& s, const Search::Options &o) {
-	int sigs = PyErr_CheckSignals();
-	bool fs_stop = false;
-	bool ts_stop = false;
-	if (fs) {
-	    fs_stop = fs->stop(s,o);
-	}
-	if (ts) {
-	    ts_stop = ts->stop(s,o);
-	}
-	return sigs || fs_stop || ts_stop;
-    }
-#endif
-
-    /// Create appropriate stop-object
-    static Search::Stop* create(int fails, int time) {
-	return new FailTimeStop(fails, time);
-    }
-};
-
-static void _solve( RqlContext& ctx )
-{
-    Search::Stop *stop = FailTimeStop::create(ctx.fails, ctx.time);
-
-    RqlSolver::run<DFS>( ctx, stop );
-}
-
-
-static PyObject *
-rql_solve(PyObject *self, PyObject *args)
-{
-    PyObject* sols = 0L;
-    PyObject* constraints;
-    PyObject* domains;
-    long nvars, nvalues;
-    if (!PyArg_ParseTuple(args, "OiO", &domains, &nvalues, &constraints))
-        return NULL;
-    sols = PyList_New(0);
-    try {
-	if (!PyList_Check(domains)) {
-	    throw RqlError();
-	}
-	nvars = PyList_Size(domains);
-	RqlContext ctx(nvars, domains, nvalues, constraints, sols );
-	_solve( ctx );
-    } catch(RqlError& e) {
-	Py_DECREF(sols);
-	PyErr_SetString(PyExc_RuntimeError, "Error parsing constraints");
-	return NULL;
-    };
-    return sols;
-}
-
-static PyMethodDef SolveRqlMethods[] = {
-    {"solve",  rql_solve, METH_VARARGS,
-     "Solve RQL variable types problem."},
-    {NULL, NULL, 0, NULL}        /* Sentinel */
-};
-
-
-PyMODINIT_FUNC
-initrql_solve(void)
-{
-    PyObject* m;
-    m = Py_InitModule("rql_solve", SolveRqlMethods);
-    if (m == NULL)
-        return;
-}
--- a/interfaces.py	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of rql.
-#
-# rql is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# rql is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with rql. If not, see <http://www.gnu.org/licenses/>.
-"""Interfaces used by the RQL package.
-
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab.common.interface import Interface
-
-class ISchema(Interface):
-    """RQL expects some base types to exists: String, Float, Int, Boolean, Date
-    and a base relation : is
-    """
-
-    def has_entity(self, etype):
-        """Return true if the given type is defined in the schema.
-        """
-
-    def has_relation(self, rtype):
-        """Return true if the given relation's type is defined in the schema.
-        """
-
-    def entities(self, schema=None):
-        """Return the list of possible types.
-
-        If schema is not None, return a list of schemas instead of types.
-        """
-
-    def relations(self, schema=None):
-        """Return the list of possible relations.
-
-        If schema is not None, return a list of schemas instead of relation's
-        types.
-        """
-
-    def relation_schema(self, rtype):
-        """Return the relation schema for the given relation type.
-        """
-
-
-class IRelationSchema(Interface):
-    """Interface for Relation schema (a relation is a named oriented link
-    between two entities).
-    """
-
-    def associations(self):
-        """Return a list of (fromtype, [totypes]) defining between which types
-        this relation may exists.
-        """
-
-    def subjects(self):
-        """Return a list of types which can be subject of this relation.
-        """
-
-    def objects(self):
-        """Return a list of types which can be object of this relation.
-        """
-
-class IEntitySchema(Interface):
-    """Interface for Entity schema."""
-
-    def is_final(self):
-        """Return true if the entity is a final entity (ie cannot be used
-        as subject of a relation).
-        """
-
--- a/nodes.py	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1123 +0,0 @@
-# copyright 2004-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of rql.
-#
-# rql is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# rql is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with rql. If not, see <http://www.gnu.org/licenses/>.
-"""RQL syntax tree nodes.
-
-This module defines all the nodes we can find in a RQL Syntax tree, except
-root nodes, defined in the `stmts` module.
-"""
-
-__docformat__ = "restructuredtext en"
-
-import sys
-from itertools import chain
-from decimal import Decimal
-from datetime import datetime, date, time, timedelta
-from time import localtime
-
-from logilab.database import DYNAMIC_RTYPE
-
-from rql import CoercionError, RQLException
-from rql.base import BaseNode, Node, BinaryNode, LeafNode
-from rql.utils import (function_description, quote, uquote, common_parent,
-                       VisitableMixIn)
-
-CONSTANT_TYPES = frozenset((None, 'Date', 'Datetime', 'Boolean', 'Float', 'Int',
-                            'String', 'Substitute', 'etype'))
-
-
-ETYPE_PYOBJ_MAP = { bool: 'Boolean',
-                    int: 'Int',
-                    float: 'Float',
-                    Decimal: 'Decimal',
-                    str: 'String',
-                    datetime: 'Datetime',
-                    date: 'Date',
-                    time: 'Time',
-                    timedelta: 'Interval',
-                    }
-if sys.version_info < (3,):
-    ETYPE_PYOBJ_MAP[long] = 'Int'
-    ETYPE_PYOBJ_MAP[unicode] = 'String'
-
-KEYWORD_MAP = {'NOW' : datetime.now,
-               'TODAY': date.today}
-
-def etype_from_pyobj(value):
-    """guess yams type from python value"""
-    # note:
-    # * Password is not selectable so no problem
-    # * use type(value) and not value.__class__ since C instances may have no
-    #   __class__ attribute
-    return ETYPE_PYOBJ_MAP[type(value)]
-
-def variable_ref(var):
-    """get a VariableRef"""
-    if isinstance(var, Variable):
-        return VariableRef(var, noautoref=1)
-    assert isinstance(var, VariableRef)
-    return var
-
-def variable_refs(node):
-    for vref in node.iget_nodes(VariableRef):
-        if isinstance(vref.variable, Variable):
-            yield vref
-
-
-class OperatorExpressionMixin(object):
-
-    def initargs(self, stmt):
-        """return list of arguments to give to __init__ to clone this node"""
-        return (self.operator,)
-
-    def is_equivalent(self, other):
-        if not Node.is_equivalent(self, other):
-            return False
-        return self.operator == other.operator
-
-    def get_description(self, mainindex, tr):
-        """if there is a variable in the math expr used as rhs of a relation,
-        return the name of this relation, else return the type of the math
-        expression
-        """
-        try:
-            return tr(self.get_type())
-        except CoercionError:
-            for vref in self.iget_nodes(VariableRef):
-                vtype = vref.get_description(mainindex, tr)
-                if vtype != 'Any':
-                    return tr(vtype)
-
-
-class HSMixin(object):
-    """mixin class for classes which may be the lhs or rhs of an expression"""
-    __slots__ = ()
-
-    def relation(self):
-        """return the parent relation where self occurs or None"""
-        try:
-            return self.parent.relation()
-        except AttributeError:
-            return None
-
-    def get_description(self, mainindex, tr):
-        mytype = self.get_type()
-        if mytype != 'Any':
-            return tr(mytype)
-        return 'Any'
-
-
-# rql st edition utilities ####################################################
-
-def make_relation(var, rel, rhsargs, rhsclass, operator='='):
-    """build an relation equivalent to '<var> rel = <cst>'"""
-    cmpop = Comparison(operator)
-    cmpop.append(rhsclass(*rhsargs))
-    relation = Relation(rel)
-    if hasattr(var, 'variable'):
-        var = var.variable
-    relation.append(VariableRef(var))
-    relation.append(cmpop)
-    return relation
-
-def make_constant_restriction(var, rtype, value, ctype, operator='='):
-    if ctype is None:
-        ctype = etype_from_pyobj(value)
-    if isinstance(value, (set, frozenset, tuple, list, dict)):
-        if len(value) > 1:
-            rel = make_relation(var, rtype, ('IN',), Function, operator)
-            infunc = rel.children[1].children[0]
-            for atype in sorted(value):
-                infunc.append(Constant(atype, ctype))
-            return rel
-        value = next(iter(value))
-    return make_relation(var, rtype, (value, ctype), Constant, operator)
-
-
-class EditableMixIn(object):
-    """mixin class to add edition functionalities to some nodes, eg root nodes
-    (statement) and Exists nodes
-    """
-    __slots__ = ()
-
-    @property
-    def undo_manager(self):
-        return self.root.undo_manager
-
-    @property
-    def should_register_op(self):
-        root = self.root
-        # root is None during parsing
-        return root is not None and root.should_register_op
-
-    def remove_node(self, node, undefine=False):
-        """remove the given node from the tree
-
-        USE THIS METHOD INSTEAD OF .remove to get correct variable references
-        handling
-        """
-        # unregister variable references in the removed subtree
-        parent = node.parent
-        stmt = parent.stmt
-        for varref in node.iget_nodes(VariableRef):
-            varref.unregister_reference()
-            if undefine and not varref.variable.stinfo['references']:
-                stmt.undefine_variable(varref.variable)
-        # remove return actually removed node and its parent
-        node, parent, index = parent.remove(node)
-        if self.should_register_op:
-            from rql.undo import RemoveNodeOperation
-            self.undo_manager.add_operation(RemoveNodeOperation(node, parent, stmt, index))
-
-    def add_restriction(self, relation):
-        """add a restriction relation"""
-        r = self.where
-        if r is not None:
-            newnode = And(r, relation)
-            self.set_where(newnode)
-            if self.should_register_op:
-                from rql.undo import ReplaceNodeOperation
-                self.undo_manager.add_operation(ReplaceNodeOperation(r, newnode))
-        else:
-            self.set_where(relation)
-            if self.should_register_op:
-                from rql.undo import AddNodeOperation
-                self.undo_manager.add_operation(AddNodeOperation(relation))
-        return relation
-
-    def add_constant_restriction(self, var, rtype, value, ctype,
-                                 operator='='):
-        """builds a restriction node to express a constant restriction:
-
-        variable rtype = value
-        """
-        restr = make_constant_restriction(var, rtype, value, ctype, operator)
-        return self.add_restriction(restr)
-
-    def add_relation(self, lhsvar, rtype, rhsvar):
-        """builds a restriction node to express '<var> eid <eid>'"""
-        return self.add_restriction(make_relation(lhsvar, rtype, (rhsvar,),
-                                                  VariableRef))
-
-    def add_eid_restriction(self, var, eid, c_type='Int'):
-        """builds a restriction node to express '<var> eid <eid>'"""
-        assert c_type in ('Int', 'Substitute'), "Error got c_type=%r in eid restriction" % c_type
-        return self.add_constant_restriction(var, 'eid', eid, c_type)
-
-    def add_type_restriction(self, var, etype):
-        """builds a restriction node to express : variable is etype"""
-        typerel = var.stinfo.get('typerel', None)
-        if typerel:
-            if typerel.r_type == 'is':
-                istarget = typerel.children[1].children[0]
-                if isinstance(istarget, Constant):
-                    etypes = (istarget.value,)
-                else: # Function (IN)
-                    etypes = [et.value for et in istarget.children]
-                if etype not in etypes:
-                    raise RQLException('%r not in %r' % (etype, etypes))
-                if len(etypes) > 1:
-                    # iterate a copy of children because it's modified inplace
-                    for child in istarget.children[:]:
-                        if child.value != etype:
-                            typerel.stmt.remove_node(child)
-                return typerel
-            else:
-                assert typerel.r_type == 'is_instance_of'
-                typerel.stmt.remove_node(typerel)
-        return self.add_constant_restriction(var, 'is', etype, 'etype')
-
-
-# base RQL nodes ##############################################################
-
-class SubQuery(BaseNode):
-    """WITH clause"""
-    __slots__ = ('aliases', 'query')
-    def __init__(self, aliases=None, query=None):
-        if aliases is not None:
-            self.set_aliases(aliases)
-        if query is not None:
-            self.set_query(query)
-
-    def set_aliases(self, aliases):
-        self.aliases = aliases
-        for node in aliases:
-            node.parent = self
-
-    def set_query(self, node):
-        self.query = node
-        node.parent = self
-
-    def copy(self, stmt):
-        return SubQuery([v.copy(stmt) for v in self.aliases], self.query.copy())
-
-    @property
-    def children(self):
-        return self.aliases + [self.query]
-
-    def as_string(self, encoding=None, kwargs=None):
-        return '%s BEING (%s)' % (','.join(v.name for v in self.aliases),
-                                  self.query.as_string(encoding, kwargs))
-    def __repr__(self):
-        return '%s BEING (%s)' % (','.join(repr(v) for v in self.aliases),
-                                  repr(self.query))
-
-class And(BinaryNode):
-    """a logical AND node (binary)"""
-    __slots__ = ()
-
-    def as_string(self, encoding=None, kwargs=None):
-        """return the tree as an encoded rql string"""
-        return '%s, %s' % (self.children[0].as_string(encoding, kwargs),
-                           self.children[1].as_string(encoding, kwargs))
-    def __repr__(self):
-        return '%s AND %s' % (repr(self.children[0]), repr(self.children[1]))
-
-    def ored(self, traverse_scope=False, _fromnode=None):
-        return self.parent.ored(traverse_scope, _fromnode or self)
-
-    def neged(self, traverse_scope=False, _fromnode=None):
-        return self.parent.neged(traverse_scope, _fromnode or self)
-
-
-class Or(BinaryNode):
-    """a logical OR node (binary)"""
-    __slots__ = ()
-
-    def as_string(self, encoding=None, kwargs=None):
-        return '(%s) OR (%s)' % (self.children[0].as_string(encoding, kwargs),
-                                 self.children[1].as_string(encoding, kwargs))
-
-    def __repr__(self):
-        return '%s OR %s' % (repr(self.children[0]), repr(self.children[1]))
-
-    def ored(self, traverse_scope=False, _fromnode=None):
-        return self
-
-    def neged(self, traverse_scope=False, _fromnode=None):
-        return self.parent.neged(traverse_scope, _fromnode or self)
-
-
-class Not(Node):
-    """a logical NOT node (unary)"""
-    __slots__ = ()
-    def __init__(self, expr=None):
-        Node.__init__(self)
-        if expr is not None:
-            self.append(expr)
-
-    def as_string(self, encoding=None, kwargs=None):
-        if isinstance(self.children[0], (Exists, Relation)):
-            return 'NOT %s' % self.children[0].as_string(encoding, kwargs)
-        return 'NOT (%s)' % self.children[0].as_string(encoding, kwargs)
-
-    def __repr__(self, encoding=None, kwargs=None):
-        return 'NOT (%s)' % repr(self.children[0])
-
-    def ored(self, traverse_scope=False, _fromnode=None):
-        # XXX consider traverse_scope ?
-        return self.parent.ored(traverse_scope, _fromnode or self)
-
-    def neged(self, traverse_scope=False, _fromnode=None, strict=False):
-        return self
-
-    def remove(self, child):
-        return self.parent.remove(self)
-
-# def parent_scope_property(attr):
-#     def _get_parent_attr(self, attr=attr):
-#         return getattr(self.parent.scope, attr)
-#     return property(_get_parent_attr)
-# # editable compatibility
-# for method in ('remove_node', 'add_restriction', 'add_constant_restriction',
-#                'add_relation', 'add_eid_restriction', 'add_type_restriction'):
-#     setattr(Not, method, parent_scope_property(method))
-
-
-class Exists(EditableMixIn, BaseNode):
-    """EXISTS sub query"""
-    __slots__ = ('query',)
-
-    def __init__(self, restriction=None):
-        if restriction is not None:
-            self.set_where(restriction)
-        else:
-            self.query = None
-
-    def copy(self, stmt):
-        new = self.query.copy(stmt)
-        return Exists(new)
-
-    @property
-    def children(self):
-        return (self.query,)
-
-    def append(self, node):
-        assert self.query is None
-        self.query = node
-        node.parent = self
-
-    def is_equivalent(self, other):
-        raise NotImplementedError
-
-    def as_string(self, encoding=None, kwargs=None):
-        content = self.query and self.query.as_string(encoding, kwargs)
-        return 'EXISTS(%s)' % content
-
-    def __repr__(self):
-        return 'EXISTS(%s)' % repr(self.query)
-
-    def set_where(self, node):
-        self.query = node
-        node.parent = self
-
-    @property
-    def where(self):
-        return self.query
-
-    def replace(self, oldnode, newnode):
-        assert oldnode is self.query
-        self.query = newnode
-        newnode.parent = self
-        return oldnode, self, None
-
-    def remove(self, child):
-        return self.parent.remove(self)
-
-    @property
-    def scope(self):
-        return self
-
-    def ored(self, traverse_scope=False, _fromnode=None):
-        if not traverse_scope:
-            if _fromnode is not None: # stop here
-                return False
-            return self.parent.ored(traverse_scope, self)
-        return self.parent.ored(traverse_scope, _fromnode)
-
-    def neged(self, traverse_scope=False, _fromnode=None, strict=False):
-        if not traverse_scope:
-            if _fromnode is not None: # stop here
-                return False
-            return self.parent.neged(self)
-        elif strict:
-            return isinstance(self.parent, Not)
-        return self.parent.neged(traverse_scope, _fromnode)
-
-
-class Relation(Node):
-    """a RQL relation"""
-    __slots__ = ('r_type', 'optional',
-                 '_q_sqltable', '_q_needcast') # XXX cubicweb specific
-
-    def __init__(self, r_type, optional=None):
-        Node.__init__(self)
-        self.r_type = r_type.encode()
-        self.optional = None
-        self.set_optional(optional)
-
-    def initargs(self, stmt):
-        """return list of arguments to give to __init__ to clone this node"""
-        return self.r_type, self.optional
-
-    def is_equivalent(self, other):
-        if not Node.is_equivalent(self, other):
-            return False
-        if self.r_type != other.r_type:
-            return False
-        return True
-
-    def as_string(self, encoding=None, kwargs=None):
-        """return the tree as an encoded rql string"""
-        try:
-            lhs = self.children[0].as_string(encoding, kwargs)
-            if self.optional in ('left', 'both'):
-                lhs += '?'
-            rhs = self.children[1].as_string(encoding, kwargs)
-            if self.optional in ('right', 'both'):
-                rhs += '?'
-        except IndexError:
-            return repr(self) # not fully built relation
-        return '%s %s %s' % (lhs, self.r_type, rhs)
-
-    def __repr__(self):
-        if self.optional:
-            rtype = '%s[%s]' % (self.r_type, self.optional)
-        else:
-            rtype = self.r_type
-        try:
-            return 'Relation(%r %s %r)' % (self.children[0], rtype,
-                                           self.children[1])
-        except IndexError:
-            return 'Relation(%s)' % self.r_type
-
-    def set_optional(self, optional):
-        assert optional in (None, 'left', 'right')
-        if optional is not None:
-            if self.optional and self.optional != optional:
-                self.optional = 'both'
-            else:
-                self.optional = optional
-
-    def relation(self):
-        """return the parent relation where self occurs or None"""
-        return self
-
-    def ored(self, traverse_scope=False, _fromnode=None):
-        return self.parent.ored(traverse_scope, _fromnode or self)
-
-    def neged(self, traverse_scope=False, _fromnode=None, strict=False):
-        if strict:
-            return isinstance(self.parent, Not)
-        return self.parent.neged(traverse_scope, _fromnode or self)
-
-    def is_types_restriction(self):
-        if self.r_type not in ('is', 'is_instance_of'):
-            return False
-        rhs = self.children[1]
-        if isinstance(rhs, Comparison):
-            rhs = rhs.children[0]
-        # else: relation used in SET OR DELETE selection
-        return ((isinstance(rhs, Constant) and rhs.type == 'etype')
-                or (isinstance(rhs, Function) and rhs.name == 'IN'))
-
-    def operator(self):
-        """return the operator of the relation <, <=, =, >=, > and LIKE
-
-        (relations used in SET, INSERT and DELETE definitions don't have
-         an operator as rhs)
-        """
-        rhs = self.children[1]
-        if isinstance(rhs, Comparison):
-            return rhs.operator
-        return '='
-
-    def get_parts(self):
-        """return the left hand side and the right hand side of this relation
-        """
-        lhs = self.children[0]
-        rhs = self.children[1]
-        return lhs, rhs
-
-    def get_variable_parts(self):
-        """return the left hand side and the right hand side of this relation,
-        ignoring comparison
-        """
-        lhs = self.children[0]
-        rhs = self.children[1].children[0]
-        return lhs, rhs
-
-    def change_optional(self, value):
-        root = self.root
-        if root is not None and root.should_register_op and value != self.optional:
-            from rql.undo import SetOptionalOperation
-            root.undo_manager.add_operation(SetOptionalOperation(self, self.optional))
-        self.set_optional(value)
-
-
-CMP_OPERATORS = frozenset(('=', '!=', '<', '<=', '>=', '>', 'ILIKE', 'LIKE', 'REGEXP'))
-
-class Comparison(HSMixin, Node):
-    """handle comparisons:
-
-     <, <=, =, >=, > LIKE and ILIKE operators have a unique children.
-    """
-    __slots__ = ('operator', 'optional')
-
-    def __init__(self, operator, value=None, optional=None):
-        Node.__init__(self)
-        if operator == '~=':
-            operator = 'ILIKE'
-        assert operator in CMP_OPERATORS, operator
-        self.operator = operator.encode()
-        self.optional = optional
-        if value is not None:
-            self.append(value)
-
-    def initargs(self, stmt):
-        """return list of arguments to give to __init__ to clone this node"""
-        return (self.operator, None, self.optional)
-
-    def set_optional(self, left, right):
-        if left and right:
-            self.optional = 'both'
-        elif left:
-            self.optional = 'left'
-        elif right:
-            self.optional = 'right'
-
-    def is_equivalent(self, other):
-        if not Node.is_equivalent(self, other):
-            return False
-        return self.operator == other.operator
-
-    def as_string(self, encoding=None, kwargs=None):
-        """return the tree as an encoded rql string"""
-        if len(self.children) == 0:
-            return self.operator
-        if len(self.children) == 2:
-            lhsopt = rhsopt = ''
-            if self.optional in ('left', 'both'):
-                lhsopt = '?'
-            if self.optional in ('right', 'both'):
-                rhsopt = '?'
-            return '%s%s %s %s%s' % (self.children[0].as_string(encoding, kwargs),
-                                     lhsopt, self.operator.encode(),
-                                     self.children[1].as_string(encoding, kwargs), rhsopt)
-        if self.operator == '=':
-            return self.children[0].as_string(encoding, kwargs)
-        return '%s %s' % (self.operator.encode(),
-                          self.children[0].as_string(encoding, kwargs))
-
-    def __repr__(self):
-        return '%s %s' % (self.operator, ', '.join(repr(c) for c in self.children))
-
-
-class MathExpression(OperatorExpressionMixin, HSMixin, BinaryNode):
-    """Mathematical Operators"""
-    __slots__ = ('operator',)
-
-    def __init__(self, operator, lhs=None, rhs=None):
-        BinaryNode.__init__(self, lhs, rhs)
-        self.operator = operator.encode()
-
-    def as_string(self, encoding=None, kwargs=None):
-        """return the tree as an encoded rql string"""
-        return '(%s %s %s)' % (self.children[0].as_string(encoding, kwargs),
-                               self.operator.encode(),
-                               self.children[1].as_string(encoding, kwargs))
-
-    def __repr__(self):
-        return '(%r %s %r)' % (self.children[0], self.operator,
-                               self.children[1])
-
-    def get_type(self, solution=None, kwargs=None):
-        """return the type of object returned by this function if known
-
-        solution is an optional variable/etype mapping
-        """
-        lhstype = self.children[0].get_type(solution, kwargs)
-        rhstype = self.children[1].get_type(solution, kwargs)
-        key = (self.operator, lhstype, rhstype)
-        try:
-            return {('-', 'Date', 'Datetime'):     'Interval',
-                    ('-', 'Datetime', 'Datetime'): 'Interval',
-                    ('-', 'Date', 'Date'):         'Interval',
-                    ('-', 'Date', 'Time'):     'Datetime',
-                    ('+', 'Date', 'Time'):     'Datetime',
-                    ('-', 'Datetime', 'Time'): 'Datetime',
-                    ('+', 'Datetime', 'Time'): 'Datetime',
-                    }[key]
-        except KeyError:
-            if lhstype == rhstype:
-                return rhstype
-            if sorted((lhstype, rhstype)) == ['Float', 'Int']:
-                return 'Float'
-            raise CoercionError(key)
-
-
-class UnaryExpression(OperatorExpressionMixin, Node):
-    """Unary Operators"""
-    __slots__ = ('operator',)
-
-    def __init__(self, operator, child=None):
-        Node.__init__(self)
-        self.operator = operator.encode()
-        if child is not None:
-            self.append(child)
-
-    def as_string(self, encoding=None, kwargs=None):
-        """return the tree as an encoded rql string"""
-        return '%s%s' % (self.operator.encode(),
-                         self.children[0].as_string(encoding, kwargs))
-
-    def __repr__(self):
-        return '%s%r' % (self.operator, self.children[0])
-
-    def get_type(self, solution=None, kwargs=None):
-        """return the type of object returned by this expression if known
-
-        solution is an optional variable/etype mapping
-        """
-        return self.children[0].get_type(solution, kwargs)
-
-
-class Function(HSMixin, Node):
-    """Class used to deal with aggregat functions (sum, min, max, count, avg)
-    and latter upper(), lower() and other RQL transformations functions
-    """
-    __slots__ = ('name',)
-
-    def __init__(self, name):
-        Node.__init__(self)
-        self.name = name.strip().upper().encode()
-
-    def initargs(self, stmt):
-        """return list of arguments to give to __init__ to clone this node"""
-        return (self.name,)
-
-    def is_equivalent(self, other):
-        if not Node.is_equivalent(self, other):
-            return False
-        return self.name == other.name
-
-    def as_string(self, encoding=None, kwargs=None):
-        """return the tree as an encoded rql string"""
-        return '%s(%s)' % (self.name, ', '.join(c.as_string(encoding, kwargs)
-                                                for c in self.children))
-
-    def __repr__(self):
-        return '%s(%s)' % (self.name, ', '.join(repr(c) for c in self.children))
-
-    def get_type(self, solution=None, kwargs=None):
-        """return the type of object returned by this function if known
-
-        solution is an optional variable/etype mapping
-        """
-        func_descr = self.descr()
-        rtype = func_descr.rql_return_type(self)
-        if rtype is None:
-            # XXX support one variable ref child
-            try:
-                rtype = solution and solution.get(self.children[0].name)
-            except AttributeError:
-                pass
-        return rtype or 'Any'
-
-    def get_description(self, mainindex, tr):
-        return self.descr().st_description(self, mainindex, tr)
-
-    def descr(self):
-        """return the type of object returned by this function if known"""
-        return function_description(self.name)
-
-
-class Constant(HSMixin, LeafNode):
-    """String, Int, TRUE, FALSE, TODAY, NULL..."""
-    __slots__ = ('value', 'type', 'uid', 'uidtype')
-
-    def __init__(self, value, c_type, _uid=False, _uidtype=None):
-        assert c_type in CONSTANT_TYPES, "Error got c_type="+repr(c_type)
-        LeafNode.__init__(self) # don't care about Node attributes
-        self.value = value
-        self.type = c_type
-        # updated by the annotator/analyzer if necessary
-        self.uid = _uid
-        self.uidtype = _uidtype
-
-    def initargs(self, stmt):
-        """return list of arguments to give to __init__ to clone this node"""
-        return (self.value, self.type, self.uid, self.uidtype)
-
-    def is_equivalent(self, other):
-        if not LeafNode.is_equivalent(self, other):
-            return False
-        return self.type == other.type and self.value == other.value
-
-    def as_string(self, encoding=None, kwargs=None):
-        """return the tree as an encoded rql string (an unicode string is
-        returned if encoding is None)
-        """
-        if self.type is None:
-            return 'NULL'
-        if self.type in ('etype', 'Date', 'Datetime', 'Int', 'Float'):
-            return str(self.value)
-        if self.type == 'Boolean':
-            return self.value and 'TRUE' or 'FALSE'
-        if self.type == 'Substitute':
-            # XXX could get some type information from self.root().schema()
-            #     and linked relation
-            if kwargs is not None:
-                value = kwargs.get(self.value, '???')
-                if isinstance(value, unicode):
-                    if encoding:
-                        value = quote(value.encode(encoding))
-                    else:
-                        value = uquote(value)
-                elif isinstance(value, str):
-                    value = quote(value)
-                else:
-                    value = repr(value)
-                return value
-            return '%%(%s)s' % self.value
-        if isinstance(self.value, unicode):
-            if encoding is not None:
-                return quote(self.value.encode(encoding))
-            return uquote(self.value)
-        return repr(self.value)
-
-    def __repr__(self):
-        return self.as_string('utf8')
-
-    def eval(self, kwargs):
-        if self.type == 'Substitute':
-            return kwargs[self.value]
-        if self.type in ('Date', 'Datetime'): # TODAY, NOW
-            return KEYWORD_MAP[self.value]()
-        return self.value
-
-    def get_type(self, solution=None, kwargs=None):
-        if self.uid:
-            return self.uidtype
-        if self.type == 'Substitute':
-            if kwargs is not None:
-                return etype_from_pyobj(self.eval(kwargs))
-            return 'String'
-        return self.type
-
-
-class VariableRef(HSMixin, LeafNode):
-    """a reference to a variable in the syntax tree"""
-    __slots__ = ('variable', 'name')
-
-    def __init__(self, variable, noautoref=None):
-        LeafNode.__init__(self) # don't care about Node attributes
-        self.variable = variable
-        self.name = variable.name
-        if noautoref is None:
-            self.register_reference()
-
-    def initargs(self, stmt):
-        """return list of arguments to give to __init__ to clone this node"""
-        var = self.variable
-        if isinstance(var, ColumnAlias):
-            newvar = stmt.get_variable(self.name, var.colnum)
-        else:
-            newvar = stmt.get_variable(self.name)
-        newvar.init_copy(var)
-        return (newvar,)
-
-    def is_equivalent(self, other):
-        if not LeafNode.is_equivalent(self, other):
-            return False
-        return self.name == other.name
-
-    def as_string(self, encoding=None, kwargs=None):
-        """return the tree as an encoded rql string"""
-        return self.name
-
-    def __repr__(self):
-        return 'VarRef(%r)' % self.variable
-
-    def __cmp__(self, other):
-        return not self.is_equivalent(other)
-
-    def register_reference(self):
-        self.variable.register_reference(self)
-
-    def unregister_reference(self):
-        self.variable.unregister_reference(self)
-
-    def get_type(self, solution=None, kwargs=None):
-        return self.variable.get_type(solution, kwargs)
-
-    def get_description(self, mainindex, tr):
-        return self.variable.get_description(mainindex, tr)
-
-    def root_selection_index(self):
-        """return the index of this variable reference *in the root selection*
-        if it's selected, else None
-        """
-        myidx = self.variable.selected_index()
-        if myidx is None:
-            return None
-        stmt = self.stmt
-        union = stmt.parent
-        if union.parent is None:
-            return myidx
-        # first .parent is the SubQuery node, we want the Select node
-        parentselect = union.parent.parent
-        for ca in parentselect.aliases.itervalues():
-            if ca.query is union and ca.colnum == myidx:
-                caidx = ca.selected_index()
-                if caidx is None:
-                    return None
-                return parentselect.selection[caidx].root_selection_index()
-
-
-class SortTerm(Node):
-    """a sort term bind a variable to the boolean <asc>
-    if <asc> ascendant sort
-    else descendant sort
-    """
-    __slots__ = ('asc',)
-
-    def __init__(self, variable, asc=1, copy=None):
-        Node.__init__(self)
-        self.asc = asc
-        if copy is None:
-            self.append(variable)
-
-    def initargs(self, stmt):
-        """return list of arguments to give to __init__ to clone this node"""
-        return (None, self.asc, True)
-
-    def is_equivalent(self, other):
-        if not Node.is_equivalent(self, other):
-            return False
-        return self.asc == other.asc
-
-    def as_string(self, encoding=None, kwargs=None):
-        if self.asc:
-            return '%s' % self.term
-        return '%s DESC' % self.term
-
-    def __repr__(self):
-        if self.asc:
-            return '%r ASC' % self.term
-        return '%r DESC' % self.term
-
-    @property
-    def term(self):
-        return self.children[0]
-
-
-
-###############################################################################
-
-class Referenceable(VisitableMixIn):
-    __slots__ = ('name', 'stinfo', 'stmt')
-
-    def __init__(self, name):
-        self.name = name.strip().encode()
-        # used to collect some global information about the syntax tree
-        self.stinfo = {
-            # link to VariableReference objects in the syntax tree
-            'references': set(),
-            }
-        # reference to the selection
-        self.stmt = None
-
-    @property
-    def schema(self):
-        return self.stmt.root.schema
-
-    def init_copy(self, old):
-        # should copy variable's possibletypes on copy
-        if not self.stinfo.get('possibletypes'):
-            self.stinfo['possibletypes'] = old.stinfo.get('possibletypes')
-
-    def as_string(self, encoding=None, kwargs=None):
-        """return the tree as an encoded rql string"""
-        return self.name
-
-    def register_reference(self, vref):
-        """add a reference to this variable"""
-        self.stinfo['references'].add(vref)
-
-    def unregister_reference(self, vref):
-        """remove a reference to this variable"""
-        try:
-            self.stinfo['references'].remove(vref)
-        except KeyError:
-            # this may occur on hairy undoing
-            pass
-
-    def references(self):
-        """return all references on this variable"""
-        return tuple(self.stinfo['references'])
-
-    def prepare_annotation(self):
-        self.stinfo.update({
-            'scope': None,
-            # relations where this variable is used on the lhs/rhs
-            'relations': set(),
-            'rhsrelations': set(),
-            # selection indexes if any
-            'selected': set(),
-            # type restriction (e.g. "is" / "is_instance_of") where this
-            # variable is used on the lhs
-            'typerel': None,
-            # uid relations (e.g. "eid") where this variable is used on the lhs
-            'uidrel': None,
-            # if this variable is an attribute variable (ie final entity), link
-            # to the (prefered) attribute owner variable
-            'attrvar': None,
-            # constant node linked to an uid variable if any
-            'constnode': None,
-            })
-        # remove optional st infos
-        for key in ('optrelations', 'blocsimplification', 'ftirels'):
-            self.stinfo.pop(key, None)
-
-    def _set_scope(self, key, scopenode):
-        if scopenode is self.stmt or self.stinfo[key] is None:
-            self.stinfo[key] = scopenode
-        elif not (self.stinfo[key] is self.stmt or scopenode is self.stinfo[key]):
-            self.stinfo[key] = common_parent(self.stinfo[key], scopenode).scope
-
-    def set_scope(self, scopenode):
-        self._set_scope('scope', scopenode)
-    def get_scope(self):
-        return self.stinfo['scope']
-    scope = property(get_scope, set_scope)
-
-    def add_optional_relation(self, relation):
-        try:
-            self.stinfo['optrelations'].add(relation)
-        except KeyError:
-            self.stinfo['optrelations'] = set((relation,))
-
-    def get_type(self, solution=None, kwargs=None):
-        """return entity type of this object, 'Any' if not found"""
-        if solution:
-            return solution[self.name]
-        if self.stinfo['typerel']:
-            rhs = self.stinfo['typerel'].children[1].children[0]
-            if isinstance(rhs, Constant):
-                return str(rhs.value)
-        schema = self.schema
-        if schema is not None:
-            for rel in self.stinfo['rhsrelations']:
-                try:
-                    lhstype = rel.children[0].get_type(solution, kwargs)
-                    return schema.eschema(lhstype).destination(rel.r_type)
-                except: # CoertionError, AssertionError :(
-                    pass
-        return 'Any'
-
-    def get_description(self, mainindex, tr, none_allowed=False):
-        """return :
-        * the name of a relation where this variable is used as lhs,
-        * the entity type of this object if specified by a 'is' relation,
-        * 'Any' if nothing nicer has been found...
-
-        give priority to relation name
-        """
-        if mainindex is not None:
-            if mainindex in self.stinfo['selected']:
-                return ', '.join(sorted(
-                    tr(etype) for etype in self.stinfo['possibletypes']))
-        rtype = frtype = None
-        schema = self.schema
-        for rel in self.stinfo['relations']:
-            if schema is not None:
-                rschema = schema.rschema(rel.r_type)
-                if rschema.final:
-                    if self.name == rel.children[0].name:
-                        # ignore final relation where this variable is used as subject
-                        continue
-                    # final relation where this variable is used as object
-                    frtype = rel.r_type
-            rtype = rel.r_type
-            lhs, rhs = rel.get_variable_parts()
-            # use getattr, may not be a variable ref (rewritten, constant...)
-            rhsvar = getattr(rhs, 'variable', None)
-            if mainindex is not None:
-                # relation to the main variable, stop searching
-                lhsvar = getattr(lhs, 'variable', None)
-                context = None
-                if lhsvar is not None and mainindex in lhsvar.stinfo['selected']:
-                    if len(lhsvar.stinfo['possibletypes']) == 1:
-                        context = next(iter(lhsvar.stinfo['possibletypes']))
-                    return tr(rtype, context=context)
-                if rhsvar is not None and mainindex in rhsvar.stinfo['selected']:
-                    if len(rhsvar.stinfo['possibletypes']) == 1:
-                        context = next(iter(rhsvar.stinfo['possibletypes']))
-                    if schema is not None and rschema.symmetric:
-                        return tr(rtype, context=context)
-                    return tr(rtype + '_object', context=context)
-            if rhsvar is self:
-                rtype += '_object'
-        if frtype is not None:
-            return tr(frtype)
-        if mainindex is None and rtype is not None:
-            return tr(rtype)
-        if none_allowed:
-            return None
-        return ', '.join(sorted(
-            tr(etype) for etype in self.stinfo['possibletypes']))
-
-    def selected_index(self):
-        """return the index of this variable in the selection if it's selected,
-        else None
-        """
-        if not self.stinfo['selected']:
-            return None
-        return next(iter(self.stinfo['selected']))
-
-    def main_relation(self):
-        """Return the relation where this variable is used in the rhs.
-
-        It is useful for cases where this variable is final and we are
-        looking for the entity to which it belongs.
-        """
-        for ref in self.references():
-            rel = ref.relation()
-            if rel is None:
-                continue
-            if rel.r_type != 'is' and self.name != rel.children[0].name:
-                return rel
-        return None
-
-    def valuable_references(self):
-        """return the number of "valuable" references :
-        references is in selection or in a non type (is) relations
-        """
-        stinfo = self.stinfo
-        return len(stinfo['selected']) + len(stinfo['relations'])
-
-
-class ColumnAlias(Referenceable):
-    __slots__ = ('colnum', 'query',
-                 '_q_sql', '_q_sqltable') # XXX cubicweb specific
-    def __init__(self, alias, colnum, query=None):
-        super(ColumnAlias, self).__init__(alias)
-        self.colnum = int(colnum)
-        self.query = query
-
-    def __repr__(self):
-        return 'alias %s' % self.name
-
-    def get_type(self, solution=None, kwargs=None):
-        """return entity type of this object, 'Any' if not found"""
-        vtype = super(ColumnAlias, self).get_type(solution, kwargs)
-        if vtype == 'Any':
-            for select in self.query.children:
-                vtype = select.selection[self.colnum].get_type(solution, kwargs)
-                if vtype != 'Any':
-                    return vtype
-        return vtype
-
-    def get_description(self, mainindex, tr):
-        """return entity type of this object, 'Any' if not found"""
-        vtype = super(ColumnAlias, self).get_description(mainindex, tr,
-                                                         none_allowed=True)
-        if vtype is None:
-            vtypes = set()
-            for select in self.query.children:
-                vtype = select.selection[self.colnum].get_description(mainindex, tr)
-                if vtype is not None:
-                    vtypes.add(vtype)
-            if vtypes:
-                return ', '.join(sorted(vtype for vtype in vtypes))
-        return vtype
-
-
-class Variable(Referenceable):
-    """
-    a variable definition, should not be directly added to the syntax tree (use
-    VariableRef instead)
-
-    collects information about a variable use in a syntax tree
-    """
-    __slots__ = ('_q_invariant', '_q_sql', '_q_sqltable') # XXX ginco specific
-
-    def __repr__(self):
-        return '%s' % self.name
-
-
--- a/parser.g	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,362 +0,0 @@
-"""yapps input grammar for RQL.
-
-:organization: Logilab
-:copyright: 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-
-
-Select statement grammar
-------------------------
-
-query = <squery> | <union>
-
-union = (<squery>) UNION (<squery>) [UNION (<squery>)]*
-
-squery = Any <selection>
-        [GROUPBY <variables>]
-        [ORDERBY <sortterms>]
-        [LIMIT <nb> OFFSET <nb>]
-        [WHERE <restriction>]
-        [HAVING <aggregat restriction>]
-        [WITH <subquery> [,<subquery]*]
-
-subquery = <variables> BEING (<query>)
-
-variables = <variable> [, <variable>]*
-
-
-Abbreviations in this code
---------------------------
-
-rules:
-* rel -> relation
-* decl -> declaration
-* expr -> expression
-* restr -> restriction
-* var -> variable
-* func -> function
-* const -> constant
-* cmp -> comparison
-
-variables:
-* R -> syntax tree root
-* S -> select node
-* P -> parent node
-
-"""
-%%
-
-parser Hercule:
-
-    ignore:            r'\s+'
-    # C-like comments
-    ignore:            r'/\*(?:[^*]|\*(?!/))*\*/'
-
-    token DELETE:      r'(?i)DELETE'
-    token SET:         r'(?i)SET'
-    token INSERT:      r'(?i)INSERT'
-    token UNION:       r'(?i)UNION'
-    token DISTINCT:    r'(?i)DISTINCT'
-    token WITH:        r'(?i)WITH'
-    token WHERE:       r'(?i)WHERE'
-    token BEING:       r'(?i)BEING'
-    token OR:          r'(?i)OR'
-    token AND:         r'(?i)AND'
-    token NOT:         r'(?i)NOT'
-    token GROUPBY:     r'(?i)GROUPBY'
-    token HAVING:      r'(?i)HAVING'
-    token ORDERBY:     r'(?i)ORDERBY'
-    token SORT_ASC:    r'(?i)ASC'
-    token SORT_DESC:   r'(?i)DESC'
-    token LIMIT:       r'(?i)LIMIT'
-    token OFFSET:      r'(?i)OFFSET'
-    token DATE:        r'(?i)TODAY'
-    token DATETIME:    r'(?i)NOW'
-    token TRUE:        r'(?i)TRUE'
-    token FALSE:       r'(?i)FALSE'
-    token NULL:        r'(?i)NULL'
-    token EXISTS:      r'(?i)EXISTS'
-    token CMP_OP:      r'(?i)<=|<|>=|>|!=|=|~=|LIKE|ILIKE|REGEXP'
-    token ADD_OP:      r'\+|-|\||#'
-    token MUL_OP:      r'\*|/|%|&'
-    token POW_OP:      r'\^|>>|<<'
-    token UNARY_OP:    r'-|~'
-    token FUNCTION:    r'[A-Za-z_]+\s*(?=\()'
-    token R_TYPE:      r'[a-z_][a-z0-9_]*'
-    token E_TYPE:      r'[A-Z][A-Za-z0-9]*[a-z]+[A-Z0-9]*'
-    token VARIABLE:    r'[A-Z][A-Z0-9_]*'
-    token COLALIAS:    r'[A-Z][A-Z0-9_]*\.\d+'
-    token QMARK:       r'\?'
-
-    token STRING:      r"'([^\'\\]|\\.)*'|\"([^\\\"\\]|\\.)*\""
-    token FLOAT:       r'-?\d+\.\d*'
-    token INT:         r'-?\d+'
-    token SUBSTITUTE:  r'%\([A-Za-z_0-9]+\)s'
-
-
-#// Grammar entry ###############################################################
-
-
-rule goal: DELETE _delete<<Delete()>> ';'             {{ return _delete }}
-
-         | INSERT _insert<<Insert()>> ';'             {{ return _insert }}
-
-         | SET update<<Set()>> ';'                    {{ return update }}
-
-         | union<<Union()>> ';'                       {{ return union }}
-
-#// Deletion  ###################################################################
-
-rule _delete<<R>>: decl_rels<<R>> where<<R>> having<<R>> {{ return R }}
-
-                 | decl_vars<<R>> where<<R>> having<<R>> {{ return R }}
-
-
-#// Insertion  ##################################################################
-
-rule _insert<<R>>: decl_vars<<R>> insert_rels<<R>> {{ return R }}
-
-
-rule insert_rels<<R>>: ":" decl_rels<<R>> where<<R>> having<<R>> {{ return R }}
-
-                     |
-
-
-#// Update  #####################################################################
-
-rule update<<R>>: decl_rels<<R>> where<<R>> having<<R>> {{ return R }}
-
-
-#// Selection  ##################################################################
-
-rule union<<R>>: select<<Select()>>               {{ R.append(select); return R }}
-
-               | r"\(" select<<Select()>> r"\)"   {{ R.append(select) }}
-                 ( UNION
-                   r"\(" select<<Select()>> r"\)" {{ R.append(select) }}
-                 )*                               {{ return R }}
-
-rule select<<S>>: DISTINCT select_<<S>>  {{ S.distinct = True ; return S }}
-                 | select_<<S>>          {{ return S }}
-
-rule select_<<S>>: E_TYPE selection<<S>>
-                   groupby<<S>>
-                   orderby<<S>>
-                   limit_offset<<S>>
-                   where<<S>>
-                   having<<S>>
-                   with_<<S>>             {{ S.set_statement_type(E_TYPE); return S }}
-
-rule selection<<S>>: expr_add<<S>>        {{ S.append_selected(expr_add) }}
-                     (  ',' expr_add<<S>> {{ S.append_selected(expr_add) }}
-                     )*
-
-
-
-#// other clauses (groupby, orderby, with, having) ##############################
-
-rule groupby<<S>>: GROUPBY              {{ nodes = [] }}
-                   expr_add<<S>>        {{ nodes.append(expr_add) }}
-                   ( ',' expr_add<<S>>  {{ nodes.append(expr_add) }}
-                   )*                   {{ S.set_groupby(nodes); return True }}
-                 |
-
-rule having<<S>>: HAVING logical_expr<<S>> {{ S.set_having([logical_expr]) }}
-                |
-
-rule orderby<<S>>: ORDERBY              {{ nodes = [] }}
-                   sort_term<<S>>       {{ nodes.append(sort_term) }}
-                   ( ',' sort_term<<S>> {{ nodes.append(sort_term) }}
-                   )*                   {{ S.set_orderby(nodes); return True }}
-                 |
-
-rule with_<<S>>: WITH                {{ nodes = [] }}
-                 subquery<<S>>       {{ nodes.append(subquery) }}
-                 ( ',' subquery<<S>> {{ nodes.append(subquery) }}
-                 )*                  {{ S.set_with(nodes) }}
-               |
-
-rule subquery<<S>>: variables<<S>>                     {{ node = SubQuery() ; node.set_aliases(variables) }}
-                    BEING r"\(" union<<Union()>> r"\)" {{ node.set_query(union); return node }}
-
-
-rule sort_term<<S>>: expr_add<<S>> sort_meth {{ return SortTerm(expr_add, sort_meth) }}
-
-
-rule sort_meth: SORT_DESC {{ return 0 }}
-
-              | SORT_ASC  {{ return 1 }}
-
-              |           {{ return 1 # default to SORT_ASC }}
-
-
-#// Limit and offset ############################################################
-
-rule limit_offset<<R>> :  limit<<R>> offset<<R>> {{ return limit or offset }}
-
-rule limit<<R>> : LIMIT INT {{ R.set_limit(int(INT)); return True }}
-                |
-
-rule offset<<R>> : OFFSET INT {{ R.set_offset(int(INT)); return True }}
-  		         |
-
-
-#// Restriction statements ######################################################
-
-rule where<<S>>: WHERE restriction<<S>> {{ S.set_where(restriction) }}
-               |
-
-rule restriction<<S>>: rels_or<<S>>       {{ node = rels_or }}
-                       ( ',' rels_or<<S>> {{ node = And(node, rels_or) }}
-                       )*                 {{ return node }}
-
-rule rels_or<<S>>: rels_and<<S>>      {{ node = rels_and }}
-                   ( OR rels_and<<S>> {{ node = Or(node, rels_and) }}
-                   )*                 {{ return node }}
-
-rule rels_and<<S>>: rels_not<<S>>        {{ node = rels_not }}
-                    ( AND rels_not<<S>>  {{ node = And(node, rels_not) }}
-                    )*                   {{ return node }}
-
-rule rels_not<<S>>: NOT rel<<S>> {{ return Not(rel) }}
-                  | rel<<S>>     {{ return rel }}
-
-rule rel<<S>>: rel_base<<S>>                {{ return rel_base }}
-             | r"\(" restriction<<S>> r"\)" {{ return restriction }}
-
-
-rule rel_base<<S>>: var<<S>> opt_left<<S>> rtype        {{ rtype.append(var) ; rtype.set_optional(opt_left) }}
-                    expr<<S>> opt_right<<S>>            {{ rtype.append(expr) ; rtype.set_optional(opt_right) ; return rtype }}
-                  | EXISTS r"\(" restriction<<S>> r"\)" {{ return Exists(restriction) }}
-
-rule rtype: R_TYPE {{ return Relation(R_TYPE) }}
-
-rule opt_left<<S>>: QMARK  {{ return 'left' }}
-                  |
-rule opt_right<<S>>: QMARK  {{ return 'right' }}
-                   |
-
-#// restriction expressions ####################################################
-
-rule logical_expr<<S>>: exprs_or<<S>>       {{ node = exprs_or }}
-                        ( ',' exprs_or<<S>> {{ node = And(node, exprs_or) }}
-                        )*                  {{ return node }}
-
-rule exprs_or<<S>>: exprs_and<<S>>      {{ node = exprs_and }}
-                    ( OR exprs_and<<S>> {{ node = Or(node, exprs_and) }}
-                    )*                  {{ return node }}
-
-rule exprs_and<<S>>: exprs_not<<S>>        {{ node = exprs_not }}
-                     ( AND exprs_not<<S>>  {{ node = And(node, exprs_not) }}
-                     )*                    {{ return node }}
-
-rule exprs_not<<S>>: NOT balanced_expr<<S>> {{ return Not(balanced_expr) }}
-                   | balanced_expr<<S>>     {{ return balanced_expr }}
-
-#// XXX ambiguity, expr_add may also have '(' as first token. Hence
-#// put "(" logical_expr<<S>> ")" rule first. We can then parse:
-#//
-#//   Any T2 WHERE T1 relation T2 HAVING (1 < COUNT(T1));
-#//
-#// but not
-#//
-#//   Any T2 WHERE T1 relation T2 HAVING (1+2) < COUNT(T1);
-rule balanced_expr<<S>>: r"\(" logical_expr<<S>> r"\)" {{ return logical_expr }}
-                       | expr_add<<S>> opt_left<<S>>
-                         expr_op<<S>> opt_right<<S>> {{ expr_op.insert(0, expr_add); expr_op.set_optional(opt_left, opt_right); return expr_op }}
-
-# // cant use expr<<S>> without introducing some ambiguities
-rule expr_op<<S>>: CMP_OP expr_add<<S>> {{ return Comparison(CMP_OP.upper(), expr_add) }}
-                 | in_expr<<S>>         {{ return Comparison('=', in_expr) }}
-
-
-#// common statements ###########################################################
-
-rule variables<<S>>:                   {{ vars = [] }}
-                       var<<S>>        {{ vars.append(var) }}
-                       (  ',' var<<S>> {{ vars.append(var) }}
-                       )*              {{ return vars }}
-
-rule decl_vars<<R>>: E_TYPE var<<R>> (     {{ R.add_main_variable(E_TYPE, var) }}
-                     ',' E_TYPE var<<R>>)* {{ R.add_main_variable(E_TYPE, var) }}
-
-
-rule decl_rels<<R>>: simple_rel<<R>> (     {{ R.add_main_relation(simple_rel) }}
-                     ',' simple_rel<<R>>)* {{ R.add_main_relation(simple_rel) }}
-
-
-rule simple_rel<<R>>: var<<R>> R_TYPE  {{ e = Relation(R_TYPE) ; e.append(var) }}
-                      expr_add<<R>>    {{ e.append(Comparison('=', expr_add)) ; return e }}
-
-
-rule expr<<S>>: CMP_OP expr_add<<S>> {{ return Comparison(CMP_OP.upper(), expr_add) }}
-
-                | expr_add<<S>>      {{ return Comparison('=', expr_add) }}
-
-
-rule expr_add<<S>>: expr_mul<<S>>          {{ node = expr_mul }}
-                    ( ADD_OP expr_mul<<S>> {{ node = MathExpression( ADD_OP, node, expr_mul ) }}
-                    )*                     {{ return node }}
-                  | UNARY_OP expr_mul<<S>> {{ node = UnaryExpression( UNARY_OP, expr_mul ) }}
-                    ( ADD_OP expr_mul<<S>> {{ node = MathExpression( ADD_OP, node, expr_mul ) }}
-                    )*                     {{ return node }}
-
-
-rule expr_mul<<S>>: expr_pow<<S>>          {{ node = expr_pow }}
-                    ( MUL_OP expr_pow<<S>> {{ node = MathExpression( MUL_OP, node, expr_pow) }}
-                    )*                     {{ return node }}
-
-rule expr_pow<<S>>: expr_base<<S>>          {{ node = expr_base }}
-                    ( POW_OP expr_base<<S>> {{ node = MathExpression( MUL_OP, node, expr_base) }}
-                    )*                      {{ return node }}
-
-
-rule expr_base<<S>>: const                     {{ return const }}
-                   | var<<S>>                  {{ return var }}
-                   | etype<<S>>                {{ return etype }}
-                   | func<<S>>                 {{ return func }}
-                   | r"\(" expr_add<<S>> r"\)" {{ return expr_add }}
-
-
-rule func<<S>>: FUNCTION r"\("        {{ F = Function(FUNCTION) }}
-                   ( expr_add<<S>> (     {{ F.append(expr_add) }}
-                      ',' expr_add<<S>>
-                     )*                  {{ F.append(expr_add) }}
-                   )?
-                r"\)"                 {{ return F }}
-
-rule in_expr<<S>>: 'IN' r"\("        {{ F = Function('IN') }}
-                   ( expr_add<<S>> (     {{ F.append(expr_add) }}
-                      ',' expr_add<<S>>
-                     )*                  {{ F.append(expr_add) }}
-                   )?
-                r"\)"                 {{ return F }}
-
-
-rule var<<S>>: VARIABLE {{ return VariableRef(S.get_variable(VARIABLE)) }}
-
-rule etype<<S>>: E_TYPE {{ return S.get_etype(E_TYPE) }}
-
-
-rule const: NULL       {{ return Constant(None, None) }}
-          | DATE       {{ return Constant(DATE.upper(), 'Date') }}
-          | DATETIME   {{ return Constant(DATETIME.upper(), 'Datetime') }}
-          | TRUE       {{ return Constant(True, 'Boolean') }}
-          | FALSE      {{ return Constant(False, 'Boolean') }}
-          | FLOAT      {{ return Constant(float(FLOAT), 'Float') }}
-          | INT        {{ return Constant(int(INT), 'Int') }}
-          | STRING     {{ return Constant(unquote(STRING), 'String') }}
-          | SUBSTITUTE {{ return Constant(SUBSTITUTE[2:-2], 'Substitute') }}
-%%
-
-from warnings import warn
-from rql.stmts import Union, Select, Delete, Insert, Set
-from rql.nodes import *
-
-
-def unquote(string):
-    """Remove quotes from a string."""
-    if string.startswith('"'):
-        return string[1:-1].replace('\\\\', '\\').replace('\\"', '"')
-    elif string.startswith("'"):
-        return string[1:-1].replace('\\\\', '\\').replace("\\'", "'")
--- a/parser.py	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,708 +0,0 @@
-"""yapps input grammar for RQL.
-
-:organization: Logilab
-:copyright: 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-
-
-Select statement grammar
-------------------------
-
-query = <squery> | <union>
-
-union = (<squery>) UNION (<squery>) [UNION (<squery>)]*
-
-squery = Any <selection>
-        [GROUPBY <variables>]
-        [ORDERBY <sortterms>]
-        [LIMIT <nb> OFFSET <nb>]
-        [WHERE <restriction>]
-        [HAVING <aggregat restriction>]
-        [WITH <subquery> [,<subquery]*]
-
-subquery = <variables> BEING (<query>)
-
-variables = <variable> [, <variable>]*
-
-
-Abbreviations in this code
---------------------------
-
-rules:
-* rel -> relation
-* decl -> declaration
-* expr -> expression
-* restr -> restriction
-* var -> variable
-* func -> function
-* const -> constant
-* cmp -> comparison
-
-variables:
-* R -> syntax tree root
-* S -> select node
-* P -> parent node
-
-"""
-
-# Begin -- grammar generated by Yapps
-from __future__ import print_function
-import sys, re
-from yapps import runtime
-
-class HerculeScanner(runtime.Scanner):
-    patterns = [
-        ("'IN'", re.compile('IN')),
-        ("','", re.compile(',')),
-        ('r"\\)"', re.compile('\\)')),
-        ('r"\\("', re.compile('\\(')),
-        ('":"', re.compile(':')),
-        ("';'", re.compile(';')),
-        ('\\s+', re.compile('\\s+')),
-        ('/\\*(?:[^*]|\\*(?!/))*\\*/', re.compile('/\\*(?:[^*]|\\*(?!/))*\\*/')),
-        ('DELETE', re.compile('(?i)DELETE')),
-        ('SET', re.compile('(?i)SET')),
-        ('INSERT', re.compile('(?i)INSERT')),
-        ('UNION', re.compile('(?i)UNION')),
-        ('DISTINCT', re.compile('(?i)DISTINCT')),
-        ('WITH', re.compile('(?i)WITH')),
-        ('WHERE', re.compile('(?i)WHERE')),
-        ('BEING', re.compile('(?i)BEING')),
-        ('OR', re.compile('(?i)OR')),
-        ('AND', re.compile('(?i)AND')),
-        ('NOT', re.compile('(?i)NOT')),
-        ('GROUPBY', re.compile('(?i)GROUPBY')),
-        ('HAVING', re.compile('(?i)HAVING')),
-        ('ORDERBY', re.compile('(?i)ORDERBY')),
-        ('SORT_ASC', re.compile('(?i)ASC')),
-        ('SORT_DESC', re.compile('(?i)DESC')),
-        ('LIMIT', re.compile('(?i)LIMIT')),
-        ('OFFSET', re.compile('(?i)OFFSET')),
-        ('DATE', re.compile('(?i)TODAY')),
-        ('DATETIME', re.compile('(?i)NOW')),
-        ('TRUE', re.compile('(?i)TRUE')),
-        ('FALSE', re.compile('(?i)FALSE')),
-        ('NULL', re.compile('(?i)NULL')),
-        ('EXISTS', re.compile('(?i)EXISTS')),
-        ('CMP_OP', re.compile('(?i)<=|<|>=|>|!=|=|~=|LIKE|ILIKE|REGEXP')),
-        ('ADD_OP', re.compile('\\+|-|\\||#')),
-        ('MUL_OP', re.compile('\\*|/|%|&')),
-        ('POW_OP', re.compile('\\^|>>|<<')),
-        ('UNARY_OP', re.compile('-|~')),
-        ('FUNCTION', re.compile('[A-Za-z_]+\\s*(?=\\()')),
-        ('R_TYPE', re.compile('[a-z_][a-z0-9_]*')),
-        ('E_TYPE', re.compile('[A-Z][A-Za-z0-9]*[a-z]+[A-Z0-9]*')),
-        ('VARIABLE', re.compile('[A-Z][A-Z0-9_]*')),
-        ('COLALIAS', re.compile('[A-Z][A-Z0-9_]*\\.\\d+')),
-        ('QMARK', re.compile('\\?')),
-        ('STRING', re.compile('\'([^\\\'\\\\]|\\\\.)*\'|\\"([^\\\\\\"\\\\]|\\\\.)*\\"')),
-        ('FLOAT', re.compile('-?\\d+\\.\\d*')),
-        ('INT', re.compile('-?\\d+')),
-        ('SUBSTITUTE', re.compile('%\\([A-Za-z_0-9]+\\)s')),
-    ]
-    def __init__(self, str,*args,**kw):
-        runtime.Scanner.__init__(self,None,{'\\s+':None,'/\\*(?:[^*]|\\*(?!/))*\\*/':None,},str,*args,**kw)
-
-class Hercule(runtime.Parser):
-    Context = runtime.Context
-    def goal(self, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'goal', [])
-        _token = self._peek('DELETE', 'INSERT', 'SET', 'r"\\("', 'DISTINCT', 'E_TYPE', context=_context)
-        if _token == 'DELETE':
-            DELETE = self._scan('DELETE', context=_context)
-            _delete = self._delete(Delete(), _context)
-            self._scan("';'", context=_context)
-            return _delete
-        elif _token == 'INSERT':
-            INSERT = self._scan('INSERT', context=_context)
-            _insert = self._insert(Insert(), _context)
-            self._scan("';'", context=_context)
-            return _insert
-        elif _token == 'SET':
-            SET = self._scan('SET', context=_context)
-            update = self.update(Set(), _context)
-            self._scan("';'", context=_context)
-            return update
-        else: # in ['r"\\("', 'DISTINCT', 'E_TYPE']
-            union = self.union(Union(), _context)
-            self._scan("';'", context=_context)
-            return union
-
-    def _delete(self, R, _parent=None):
-        _context = self.Context(_parent, self._scanner, '_delete', [R])
-        _token = self._peek('E_TYPE', 'VARIABLE', context=_context)
-        if _token == 'VARIABLE':
-            decl_rels = self.decl_rels(R, _context)
-            where = self.where(R, _context)
-            having = self.having(R, _context)
-            return R
-        else: # == 'E_TYPE'
-            decl_vars = self.decl_vars(R, _context)
-            where = self.where(R, _context)
-            having = self.having(R, _context)
-            return R
-
-    def _insert(self, R, _parent=None):
-        _context = self.Context(_parent, self._scanner, '_insert', [R])
-        decl_vars = self.decl_vars(R, _context)
-        insert_rels = self.insert_rels(R, _context)
-        return R
-
-    def insert_rels(self, R, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'insert_rels', [R])
-        _token = self._peek('":"', "';'", context=_context)
-        if _token == '":"':
-            self._scan('":"', context=_context)
-            decl_rels = self.decl_rels(R, _context)
-            where = self.where(R, _context)
-            having = self.having(R, _context)
-            return R
-        else: # == "';'"
-            pass
-
-    def update(self, R, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'update', [R])
-        decl_rels = self.decl_rels(R, _context)
-        where = self.where(R, _context)
-        having = self.having(R, _context)
-        return R
-
-    def union(self, R, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'union', [R])
-        _token = self._peek('r"\\("', 'DISTINCT', 'E_TYPE', context=_context)
-        if _token != 'r"\\("':
-            select = self.select(Select(), _context)
-            R.append(select); return R
-        else: # == 'r"\\("'
-            self._scan('r"\\("', context=_context)
-            select = self.select(Select(), _context)
-            self._scan('r"\\)"', context=_context)
-            R.append(select)
-            while self._peek('UNION', "';'", 'r"\\)"', context=_context) == 'UNION':
-                UNION = self._scan('UNION', context=_context)
-                self._scan('r"\\("', context=_context)
-                select = self.select(Select(), _context)
-                self._scan('r"\\)"', context=_context)
-                R.append(select)
-            return R
-
-    def select(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'select', [S])
-        _token = self._peek('DISTINCT', 'E_TYPE', context=_context)
-        if _token == 'DISTINCT':
-            DISTINCT = self._scan('DISTINCT', context=_context)
-            select_ = self.select_(S, _context)
-            S.distinct = True ; return S
-        else: # == 'E_TYPE'
-            select_ = self.select_(S, _context)
-            return S
-
-    def select_(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'select_', [S])
-        E_TYPE = self._scan('E_TYPE', context=_context)
-        selection = self.selection(S, _context)
-        groupby = self.groupby(S, _context)
-        orderby = self.orderby(S, _context)
-        limit_offset = self.limit_offset(S, _context)
-        where = self.where(S, _context)
-        having = self.having(S, _context)
-        with_ = self.with_(S, _context)
-        S.set_statement_type(E_TYPE); return S
-
-    def selection(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'selection', [S])
-        expr_add = self.expr_add(S, _context)
-        S.append_selected(expr_add)
-        while self._peek("','", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
-            self._scan("','", context=_context)
-            expr_add = self.expr_add(S, _context)
-            S.append_selected(expr_add)
-
-    def groupby(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'groupby', [S])
-        _token = self._peek('GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
-        if _token == 'GROUPBY':
-            GROUPBY = self._scan('GROUPBY', context=_context)
-            nodes = []
-            expr_add = self.expr_add(S, _context)
-            nodes.append(expr_add)
-            while self._peek("','", 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
-                self._scan("','", context=_context)
-                expr_add = self.expr_add(S, _context)
-                nodes.append(expr_add)
-            S.set_groupby(nodes); return True
-        else:
-            pass
-
-    def having(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'having', [S])
-        _token = self._peek('HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
-        if _token == 'HAVING':
-            HAVING = self._scan('HAVING', context=_context)
-            logical_expr = self.logical_expr(S, _context)
-            S.set_having([logical_expr])
-        else: # in ['WITH', "';'", 'r"\\)"']
-            pass
-
-    def orderby(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'orderby', [S])
-        _token = self._peek('ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
-        if _token == 'ORDERBY':
-            ORDERBY = self._scan('ORDERBY', context=_context)
-            nodes = []
-            sort_term = self.sort_term(S, _context)
-            nodes.append(sort_term)
-            while self._peek("','", 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
-                self._scan("','", context=_context)
-                sort_term = self.sort_term(S, _context)
-                nodes.append(sort_term)
-            S.set_orderby(nodes); return True
-        else:
-            pass
-
-    def with_(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'with_', [S])
-        _token = self._peek('WITH', 'r"\\)"', "';'", context=_context)
-        if _token == 'WITH':
-            WITH = self._scan('WITH', context=_context)
-            nodes = []
-            subquery = self.subquery(S, _context)
-            nodes.append(subquery)
-            while self._peek("','", 'r"\\)"', "';'", context=_context) == "','":
-                self._scan("','", context=_context)
-                subquery = self.subquery(S, _context)
-                nodes.append(subquery)
-            S.set_with(nodes)
-        else: # in ['r"\\)"', "';'"]
-            pass
-
-    def subquery(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'subquery', [S])
-        variables = self.variables(S, _context)
-        node = SubQuery() ; node.set_aliases(variables)
-        BEING = self._scan('BEING', context=_context)
-        self._scan('r"\\("', context=_context)
-        union = self.union(Union(), _context)
-        self._scan('r"\\)"', context=_context)
-        node.set_query(union); return node
-
-    def sort_term(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'sort_term', [S])
-        expr_add = self.expr_add(S, _context)
-        sort_meth = self.sort_meth(_context)
-        return SortTerm(expr_add, sort_meth)
-
-    def sort_meth(self, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'sort_meth', [])
-        _token = self._peek('SORT_DESC', 'SORT_ASC', "','", 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
-        if _token == 'SORT_DESC':
-            SORT_DESC = self._scan('SORT_DESC', context=_context)
-            return 0
-        elif _token == 'SORT_ASC':
-            SORT_ASC = self._scan('SORT_ASC', context=_context)
-            return 1
-        else:
-            return 1 # default to SORT_ASC
-
-    def limit_offset(self, R, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'limit_offset', [R])
-        limit = self.limit(R, _context)
-        offset = self.offset(R, _context)
-        return limit or offset
-
-    def limit(self, R, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'limit', [R])
-        _token = self._peek('LIMIT', 'OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
-        if _token == 'LIMIT':
-            LIMIT = self._scan('LIMIT', context=_context)
-            INT = self._scan('INT', context=_context)
-            R.set_limit(int(INT)); return True
-        else: # in ['OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"']
-            pass
-
-    def offset(self, R, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'offset', [R])
-        _token = self._peek('OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
-        if _token == 'OFFSET':
-            OFFSET = self._scan('OFFSET', context=_context)
-            INT = self._scan('INT', context=_context)
-            R.set_offset(int(INT)); return True
-        else: # in ['WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"']
-            pass
-
-    def where(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'where', [S])
-        _token = self._peek('WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
-        if _token == 'WHERE':
-            WHERE = self._scan('WHERE', context=_context)
-            restriction = self.restriction(S, _context)
-            S.set_where(restriction)
-        else: # in ['HAVING', 'WITH', "';'", 'r"\\)"']
-            pass
-
-    def restriction(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'restriction', [S])
-        rels_or = self.rels_or(S, _context)
-        node = rels_or
-        while self._peek("','", 'r"\\)"', 'HAVING', 'WITH', "';'", context=_context) == "','":
-            self._scan("','", context=_context)
-            rels_or = self.rels_or(S, _context)
-            node = And(node, rels_or)
-        return node
-
-    def rels_or(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'rels_or', [S])
-        rels_and = self.rels_and(S, _context)
-        node = rels_and
-        while self._peek('OR', "','", 'r"\\)"', 'HAVING', 'WITH', "';'", context=_context) == 'OR':
-            OR = self._scan('OR', context=_context)
-            rels_and = self.rels_and(S, _context)
-            node = Or(node, rels_and)
-        return node
-
-    def rels_and(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'rels_and', [S])
-        rels_not = self.rels_not(S, _context)
-        node = rels_not
-        while self._peek('AND', 'OR', "','", 'r"\\)"', 'HAVING', 'WITH', "';'", context=_context) == 'AND':
-            AND = self._scan('AND', context=_context)
-            rels_not = self.rels_not(S, _context)
-            node = And(node, rels_not)
-        return node
-
-    def rels_not(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'rels_not', [S])
-        _token = self._peek('NOT', 'r"\\("', 'EXISTS', 'VARIABLE', context=_context)
-        if _token == 'NOT':
-            NOT = self._scan('NOT', context=_context)
-            rel = self.rel(S, _context)
-            return Not(rel)
-        else: # in ['r"\\("', 'EXISTS', 'VARIABLE']
-            rel = self.rel(S, _context)
-            return rel
-
-    def rel(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'rel', [S])
-        _token = self._peek('r"\\("', 'EXISTS', 'VARIABLE', context=_context)
-        if _token != 'r"\\("':
-            rel_base = self.rel_base(S, _context)
-            return rel_base
-        else: # == 'r"\\("'
-            self._scan('r"\\("', context=_context)
-            restriction = self.restriction(S, _context)
-            self._scan('r"\\)"', context=_context)
-            return restriction
-
-    def rel_base(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'rel_base', [S])
-        _token = self._peek('EXISTS', 'VARIABLE', context=_context)
-        if _token == 'VARIABLE':
-            var = self.var(S, _context)
-            opt_left = self.opt_left(S, _context)
-            rtype = self.rtype(_context)
-            rtype.append(var) ; rtype.set_optional(opt_left)
-            expr = self.expr(S, _context)
-            opt_right = self.opt_right(S, _context)
-            rtype.append(expr) ; rtype.set_optional(opt_right) ; return rtype
-        else: # == 'EXISTS'
-            EXISTS = self._scan('EXISTS', context=_context)
-            self._scan('r"\\("', context=_context)
-            restriction = self.restriction(S, _context)
-            self._scan('r"\\)"', context=_context)
-            return Exists(restriction)
-
-    def rtype(self, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'rtype', [])
-        R_TYPE = self._scan('R_TYPE', context=_context)
-        return Relation(R_TYPE)
-
-    def opt_left(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'opt_left', [S])
-        _token = self._peek('QMARK', 'R_TYPE', 'CMP_OP', "'IN'", context=_context)
-        if _token == 'QMARK':
-            QMARK = self._scan('QMARK', context=_context)
-            return 'left'
-        else: # in ['R_TYPE', 'CMP_OP', "'IN'"]
-            pass
-
-    def opt_right(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'opt_right', [S])
-        _token = self._peek('QMARK', 'AND', 'OR', "','", 'r"\\)"', 'WITH', "';'", 'HAVING', context=_context)
-        if _token == 'QMARK':
-            QMARK = self._scan('QMARK', context=_context)
-            return 'right'
-        else: # in ['AND', 'OR', "','", 'r"\\)"', 'WITH', "';'", 'HAVING']
-            pass
-
-    def logical_expr(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'logical_expr', [S])
-        exprs_or = self.exprs_or(S, _context)
-        node = exprs_or
-        while self._peek("','", 'r"\\)"', 'WITH', "';'", context=_context) == "','":
-            self._scan("','", context=_context)
-            exprs_or = self.exprs_or(S, _context)
-            node = And(node, exprs_or)
-        return node
-
-    def exprs_or(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'exprs_or', [S])
-        exprs_and = self.exprs_and(S, _context)
-        node = exprs_and
-        while self._peek('OR', "','", 'r"\\)"', 'WITH', "';'", context=_context) == 'OR':
-            OR = self._scan('OR', context=_context)
-            exprs_and = self.exprs_and(S, _context)
-            node = Or(node, exprs_and)
-        return node
-
-    def exprs_and(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'exprs_and', [S])
-        exprs_not = self.exprs_not(S, _context)
-        node = exprs_not
-        while self._peek('AND', 'OR', "','", 'r"\\)"', 'WITH', "';'", context=_context) == 'AND':
-            AND = self._scan('AND', context=_context)
-            exprs_not = self.exprs_not(S, _context)
-            node = And(node, exprs_not)
-        return node
-
-    def exprs_not(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'exprs_not', [S])
-        _token = self._peek('NOT', 'r"\\("', 'UNARY_OP', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
-        if _token == 'NOT':
-            NOT = self._scan('NOT', context=_context)
-            balanced_expr = self.balanced_expr(S, _context)
-            return Not(balanced_expr)
-        else:
-            balanced_expr = self.balanced_expr(S, _context)
-            return balanced_expr
-
-    def balanced_expr(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'balanced_expr', [S])
-        _token = self._peek('r"\\("', 'UNARY_OP', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
-        if _token == 'r"\\("':
-            self._scan('r"\\("', context=_context)
-            logical_expr = self.logical_expr(S, _context)
-            self._scan('r"\\)"', context=_context)
-            return logical_expr
-        elif 1:
-            expr_add = self.expr_add(S, _context)
-            opt_left = self.opt_left(S, _context)
-            expr_op = self.expr_op(S, _context)
-            opt_right = self.opt_right(S, _context)
-            expr_op.insert(0, expr_add); expr_op.set_optional(opt_left, opt_right); return expr_op
-        else:
-            raise runtime.SyntaxError(_token[0], 'Could not match balanced_expr')
-
-    def expr_op(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'expr_op', [S])
-        _token = self._peek('CMP_OP', "'IN'", context=_context)
-        if _token == 'CMP_OP':
-            CMP_OP = self._scan('CMP_OP', context=_context)
-            expr_add = self.expr_add(S, _context)
-            return Comparison(CMP_OP.upper(), expr_add)
-        else: # == "'IN'"
-            in_expr = self.in_expr(S, _context)
-            return Comparison('=', in_expr)
-
-    def variables(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'variables', [S])
-        vars = []
-        var = self.var(S, _context)
-        vars.append(var)
-        while self._peek("','", 'BEING', context=_context) == "','":
-            self._scan("','", context=_context)
-            var = self.var(S, _context)
-            vars.append(var)
-        return vars
-
-    def decl_vars(self, R, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'decl_vars', [R])
-        E_TYPE = self._scan('E_TYPE', context=_context)
-        var = self.var(R, _context)
-        while self._peek("','", 'R_TYPE', 'QMARK', 'WHERE', '":"', 'CMP_OP', 'HAVING', "'IN'", "';'", 'POW_OP', 'BEING', 'WITH', 'MUL_OP', 'r"\\)"', 'ADD_OP', 'SORT_DESC', 'SORT_ASC', 'GROUPBY', 'ORDERBY', 'LIMIT', 'OFFSET', 'AND', 'OR', context=_context) == "','":
-            R.add_main_variable(E_TYPE, var)
-            self._scan("','", context=_context)
-            E_TYPE = self._scan('E_TYPE', context=_context)
-            var = self.var(R, _context)
-        R.add_main_variable(E_TYPE, var)
-
-    def decl_rels(self, R, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'decl_rels', [R])
-        simple_rel = self.simple_rel(R, _context)
-        while self._peek("','", 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
-            R.add_main_relation(simple_rel)
-            self._scan("','", context=_context)
-            simple_rel = self.simple_rel(R, _context)
-        R.add_main_relation(simple_rel)
-
-    def simple_rel(self, R, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'simple_rel', [R])
-        var = self.var(R, _context)
-        R_TYPE = self._scan('R_TYPE', context=_context)
-        e = Relation(R_TYPE) ; e.append(var)
-        expr_add = self.expr_add(R, _context)
-        e.append(Comparison('=', expr_add)) ; return e
-
-    def expr(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'expr', [S])
-        _token = self._peek('CMP_OP', 'UNARY_OP', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
-        if _token == 'CMP_OP':
-            CMP_OP = self._scan('CMP_OP', context=_context)
-            expr_add = self.expr_add(S, _context)
-            return Comparison(CMP_OP.upper(), expr_add)
-        else:
-            expr_add = self.expr_add(S, _context)
-            return Comparison('=', expr_add)
-
-    def expr_add(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'expr_add', [S])
-        _token = self._peek('UNARY_OP', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
-        if _token != 'UNARY_OP':
-            expr_mul = self.expr_mul(S, _context)
-            node = expr_mul
-            while self._peek('ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'ADD_OP':
-                ADD_OP = self._scan('ADD_OP', context=_context)
-                expr_mul = self.expr_mul(S, _context)
-                node = MathExpression( ADD_OP, node, expr_mul )
-            return node
-        else: # == 'UNARY_OP'
-            UNARY_OP = self._scan('UNARY_OP', context=_context)
-            expr_mul = self.expr_mul(S, _context)
-            node = UnaryExpression( UNARY_OP, expr_mul )
-            while self._peek('ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'ADD_OP':
-                ADD_OP = self._scan('ADD_OP', context=_context)
-                expr_mul = self.expr_mul(S, _context)
-                node = MathExpression( ADD_OP, node, expr_mul )
-            return node
-
-    def expr_mul(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'expr_mul', [S])
-        expr_pow = self.expr_pow(S, _context)
-        node = expr_pow
-        while self._peek('MUL_OP', 'ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'MUL_OP':
-            MUL_OP = self._scan('MUL_OP', context=_context)
-            expr_pow = self.expr_pow(S, _context)
-            node = MathExpression( MUL_OP, node, expr_pow)
-        return node
-
-    def expr_pow(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'expr_pow', [S])
-        expr_base = self.expr_base(S, _context)
-        node = expr_base
-        while self._peek('POW_OP', 'MUL_OP', 'ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'POW_OP':
-            POW_OP = self._scan('POW_OP', context=_context)
-            expr_base = self.expr_base(S, _context)
-            node = MathExpression( MUL_OP, node, expr_base)
-        return node
-
-    def expr_base(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'expr_base', [S])
-        _token = self._peek('r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
-        if _token not in ['r"\\("', 'VARIABLE', 'E_TYPE', 'FUNCTION']:
-            const = self.const(_context)
-            return const
-        elif _token == 'VARIABLE':
-            var = self.var(S, _context)
-            return var
-        elif _token == 'E_TYPE':
-            etype = self.etype(S, _context)
-            return etype
-        elif _token == 'FUNCTION':
-            func = self.func(S, _context)
-            return func
-        else: # == 'r"\\("'
-            self._scan('r"\\("', context=_context)
-            expr_add = self.expr_add(S, _context)
-            self._scan('r"\\)"', context=_context)
-            return expr_add
-
-    def func(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'func', [S])
-        FUNCTION = self._scan('FUNCTION', context=_context)
-        self._scan('r"\\("', context=_context)
-        F = Function(FUNCTION)
-        if self._peek('UNARY_OP', 'r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"':
-            expr_add = self.expr_add(S, _context)
-            while self._peek("','", 'QMARK', 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == "','":
-                F.append(expr_add)
-                self._scan("','", context=_context)
-                expr_add = self.expr_add(S, _context)
-            F.append(expr_add)
-        self._scan('r"\\)"', context=_context)
-        return F
-
-    def in_expr(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'in_expr', [S])
-        self._scan("'IN'", context=_context)
-        self._scan('r"\\("', context=_context)
-        F = Function('IN')
-        if self._peek('UNARY_OP', 'r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"':
-            expr_add = self.expr_add(S, _context)
-            while self._peek("','", 'QMARK', 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == "','":
-                F.append(expr_add)
-                self._scan("','", context=_context)
-                expr_add = self.expr_add(S, _context)
-            F.append(expr_add)
-        self._scan('r"\\)"', context=_context)
-        return F
-
-    def var(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'var', [S])
-        VARIABLE = self._scan('VARIABLE', context=_context)
-        return VariableRef(S.get_variable(VARIABLE))
-
-    def etype(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'etype', [S])
-        E_TYPE = self._scan('E_TYPE', context=_context)
-        return S.get_etype(E_TYPE)
-
-    def const(self, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'const', [])
-        _token = self._peek('NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', context=_context)
-        if _token == 'NULL':
-            NULL = self._scan('NULL', context=_context)
-            return Constant(None, None)
-        elif _token == 'DATE':
-            DATE = self._scan('DATE', context=_context)
-            return Constant(DATE.upper(), 'Date')
-        elif _token == 'DATETIME':
-            DATETIME = self._scan('DATETIME', context=_context)
-            return Constant(DATETIME.upper(), 'Datetime')
-        elif _token == 'TRUE':
-            TRUE = self._scan('TRUE', context=_context)
-            return Constant(True, 'Boolean')
-        elif _token == 'FALSE':
-            FALSE = self._scan('FALSE', context=_context)
-            return Constant(False, 'Boolean')
-        elif _token == 'FLOAT':
-            FLOAT = self._scan('FLOAT', context=_context)
-            return Constant(float(FLOAT), 'Float')
-        elif _token == 'INT':
-            INT = self._scan('INT', context=_context)
-            return Constant(int(INT), 'Int')
-        elif _token == 'STRING':
-            STRING = self._scan('STRING', context=_context)
-            return Constant(unquote(STRING), 'String')
-        else: # == 'SUBSTITUTE'
-            SUBSTITUTE = self._scan('SUBSTITUTE', context=_context)
-            return Constant(SUBSTITUTE[2:-2], 'Substitute')
-
-
-def parse(rule, text):
-    P = Hercule(HerculeScanner(text))
-    return runtime.wrap_error_reporter(P, rule)
-
-# End -- grammar generated by Yapps
-
-
-
-from warnings import warn
-from rql.stmts import Union, Select, Delete, Insert, Set
-from rql.nodes import *
-
-
-def unquote(string):
-    """Remove quotes from a string."""
-    if string.startswith('"'):
-        return string[1:-1].replace('\\\\', '\\').replace('\\"', '"')
-    elif string.startswith("'"):
-        return string[1:-1].replace('\\\\', '\\').replace("\\'", "'")
--- a/parser_main.py	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of rql.
-#
-# rql is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# rql is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with rql. If not, see <http://www.gnu.org/licenses/>.
-"""Main parser command.
-
-"""
-__docformat__ = "restructuredtext en"
-
-if __name__ == '__main__':
-    from sys import argv
-
-    parser = Hercule(HerculeScanner(argv[1]))
-    e_types = {}
-    # parse the RQL string
-    try:
-        tree = parser.goal(e_types)
-        print '-'*80
-        print tree
-        print '-'*80
-        print repr(tree)
-        print e_types
-    except SyntaxError, ex:
-        # try to get error message from yapps
-        from yapps.runtime import print_error
-        print_error(ex, parser._scanner)
--- a/pygments_ext.py	Mon Jul 28 11:12:58 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    pygments.lexers.rql
-    ~~~~~~~~~~~~~~~~~~~
-
-    Lexer for RQL the relation query language
-
-    http://www.logilab.org/project/rql
-"""
-
-import re
-
-from pygments.lexer import RegexLexer, _mapping
-from pygments.token import Punctuation, \
-     Text, Comment, Operator, Keyword, Name, String, Number
-
-__all__ = ['RqlLexer']
-
-class RqlLexer(RegexLexer):
-    """
-    Lexer for Relation Query Language.
-    """
-
-    name = 'RQL'
-    aliases = ['rql']
-    filenames = ['*.rql']
-    mimetypes = ['text/x-rql']
-
-    flags = re.IGNORECASE
-    tokens = {
-        'root': [
-            (r'\s+', Text),
-            (r'(DELETE|SET|INSERT|UNION|DISTINCT|WITH|WHERE|BEING|OR'
-             r'|AND|NOT|GROUPBY|HAVING|ORDERBY|ASC|DESC|LIMIT|OFFSET'
-             r'|TODAY|NOW|TRUE|FALSE|NULL|EXISTS)\b', Keyword),
-            (r'[+*/<>=%-]', Operator),
-            (r'(Any|is|instance_of)\b', Name.Builtin),
-            (r'[0-9]+', Number.Integer),
-            (r'[A-Z_][A-Z0-9_]*\??', Name),
-            (r"'(''|[^'])*'", String.Single),
-            (r'"(""|[^"])*"', String.Single),
-            (r'[;:()\[\],\.]', Punctuation)
-        ],
-    }
-
-_mapping.LEXERS['RqlLexer'] = ('rql.pygments_ext', 'RQL', ('rql',), ('*.rql',), ('text/x-rql',))
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/__init__.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,260 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""RQL library (implementation independant)."""
+__docformat__ = "restructuredtext en"
+
+from math import log
+
+import sys
+import threading
+
+import pkg_resources
+from six import StringIO
+
+from rql._exceptions import *
+
+
+__version__ = pkg_resources.get_distribution('rql').version
+#REQUIRED_TYPES = ['String', 'Float', 'Int', 'Boolean', 'Date']
+
+
+class RQLHelper(object):
+    """Helper class for RQL handling
+
+    give access to methods for :
+      - parsing RQL strings
+      - variables type resolving
+      - comparison of two queries
+    """
+    def __init__(self, schema, uid_func_mapping=None, special_relations=None,
+                 resolver_class=None, backend=None):
+        # chech schema
+        #for e_type in REQUIRED_TYPES:
+        #    if not schema.has_entity(e_type):
+        #        raise MissingType(e_type)
+        # create helpers
+        from rql.stcheck import RQLSTChecker, RQLSTAnnotator
+        special_relations = special_relations or {}
+        if uid_func_mapping:
+            for key in uid_func_mapping:
+                special_relations[key] = 'uid'
+        self._checker = RQLSTChecker(schema, special_relations, backend)
+        self._annotator = RQLSTAnnotator(schema, special_relations)
+        self._analyser_lock = threading.Lock()
+        if resolver_class is None:
+            from rql.analyze import ETypeResolver
+            resolver_class = ETypeResolver
+        self._analyser = resolver_class(schema, uid_func_mapping)
+        # IgnoreTypeRestriction analyser
+        from rql.analyze import ETypeResolverIgnoreTypeRestriction
+        self._itr_analyser_lock = threading.Lock()
+        self._itr_analyser = ETypeResolverIgnoreTypeRestriction(schema, uid_func_mapping)
+        self.set_schema(schema)
+
+    def set_schema(self, schema):
+        from rql.utils import is_keyword
+        for etype in schema.entities():
+            etype = str(etype)
+            if is_keyword(etype) or etype.capitalize() == 'Any':
+                raise UsesReservedWord(etype)
+        for rtype in schema.relations():
+            rtype = str(rtype)
+            if is_keyword(rtype):
+                raise UsesReservedWord(rtype)
+        self._checker.schema = schema
+        self._annotator.schema = schema
+        self._analyser.set_schema(schema)
+
+    def get_backend(self):
+        return self._checker.backend
+    def set_backend(self, backend):
+        self._checker.backend = backend
+    backend = property(get_backend, set_backend)
+
+    def parse(self, rqlstring, annotate=True):
+        """Return a syntax tree created from a RQL string."""
+        rqlst = parse(rqlstring, False)
+        self._checker.check(rqlst)
+        if annotate:
+            self.annotate(rqlst)
+        rqlst.schema = self._annotator.schema
+        return rqlst
+
+    def annotate(self, rqlst):
+        self._annotator.annotate(rqlst)
+
+    def compute_solutions(self, rqlst, uid_func_mapping=None, kwargs=None,
+                          debug=False):
+        """Set solutions for variables of the syntax tree.
+
+        Each solution is a dictionary with variable's name as key and
+        variable's type as value.
+        """
+        self._analyser_lock.acquire()
+        try:
+            return self._analyser.visit(rqlst, uid_func_mapping, kwargs,
+                                        debug)
+        finally:
+            self._analyser_lock.release()
+
+    def compute_all_solutions(self, rqlst, uid_func_mapping=None, kwargs=None,
+                          debug=False):
+        """compute syntaxe tree solutions with all types restriction (eg
+        is/instance_of relations) ignored
+        """
+        self._itr_analyser_lock.acquire()
+        try:
+            self._itr_analyser.visit(rqlst, uid_func_mapping, kwargs,
+                                 debug)
+        finally:
+            self._itr_analyser_lock.release()
+
+
+    def simplify(self, rqlst):
+        """Simplify `rqlst` by rewriting non-final variables associated to a const
+        node (if annotator say we can...)
+
+        The tree is modified in-place.
+        """
+        #print 'simplify', rqlst.as_string(encoding='UTF8')
+        if rqlst.TYPE == 'select':
+            from rql import nodes
+            for select in rqlst.children:
+                self._simplify(select)
+
+    def _simplify(self, select):
+        # recurse on subqueries first
+        for subquery in select.with_:
+            for subselect in subquery.query.children:
+                self._simplify(subselect)
+        rewritten = False
+        for var in list(select.defined_vars.values()):
+            stinfo = var.stinfo
+            if stinfo['constnode'] and not stinfo.get('blocsimplification'):
+                uidrel = stinfo['uidrel']
+                var = uidrel.children[0].variable
+                vconsts = []
+                rhs = uidrel.children[1].children[0]
+                for vref in var.references():
+                    rel = vref.relation()
+                    if rel is None:
+                        term = vref
+                        while not term.parent is select:
+                            term = term.parent
+                        if term in select.selection:
+                            rhs = copy_uid_node(select, rhs, vconsts)
+                            if vref is term:
+                                select.selection[select.selection.index(vref)] = rhs
+                                rhs.parent = select
+                            else:
+                                vref.parent.replace(vref, rhs)
+                        elif term in select.orderby:
+                            # remove from orderby
+                            select.remove(term)
+                        elif not select.having:
+                            # remove from groupby if no HAVING clause
+                            select.remove(term)
+                        else:
+                            rhs = copy_uid_node(select, rhs, vconsts)
+                            select.groupby[select.groupby.index(vref)] = rhs
+                            rhs.parent = select
+                    elif rel is uidrel:
+                        uidrel.parent.remove(uidrel)
+                    elif rel.is_types_restriction():
+                        stinfo['typerel'] = None
+                        rel.parent.remove(rel)
+                    else:
+                        rhs = copy_uid_node(select, rhs, vconsts)
+                        vref.parent.replace(vref, rhs)
+                del select.defined_vars[var.name]
+                stinfo['uidrel'] = None
+                rewritten = True
+                if vconsts:
+                    select.stinfo['rewritten'][var.name] = vconsts
+        if rewritten and select.solutions:
+            select.clean_solutions()
+
+    def compare(self, rqlstring1, rqlstring2):
+        """Compare 2 RQL requests.
+
+        Return True if both requests would return the same results.
+        """
+        from rql.compare import compare_tree
+        return compare_tree(self.parse(rqlstring1), self.parse(rqlstring2))
+
+
+def copy_uid_node(select, node, vconsts):
+    node = node.copy(select)
+    node.uid = True
+    vconsts.append(node)
+    return node
+
+
+def parse(rqlstring, print_errors=True):
+    """Return a syntax tree created from a RQL string."""
+    from yapps.runtime import print_error, SyntaxError, NoMoreTokens
+    from rql.parser import Hercule, HerculeScanner
+    # make sure rql string ends with a semi-colon
+    rqlstring = rqlstring.strip()
+    if rqlstring and not rqlstring.endswith(';') :
+        rqlstring += ';'
+    # parse the RQL string
+    parser = Hercule(HerculeScanner(rqlstring))
+    try:
+        return parser.goal()
+    except SyntaxError as ex:
+        if not print_errors:
+            if ex.pos is not None:
+                multi_lines_rql = rqlstring.splitlines()
+                nb_lines = len(multi_lines_rql)
+                if nb_lines > 5:
+                    width = log(nb_lines, 10)+1
+                    template = " %%%ii: %%s" % width
+                    rqlstring = '\n'.join( template % (idx + 1, line) for idx, line in enumerate(multi_lines_rql))
+
+
+                msg = '%s\nat: %r\n%s' % (rqlstring, ex.pos,  ex.msg)
+            else:
+                msg = '%s\n%s' % (rqlstring, ex.msg)
+            exc = RQLSyntaxError(msg)
+            exc.__traceback__ = sys.exc_info()[-1]
+            raise exc
+        # try to get error message from yapps
+        try:
+            out = sys.stdout
+            sys.stdout = stream = StringIO()
+            try:
+                print_error(ex, parser._scanner)
+            finally:
+                sys.stdout = out
+            exc = RQLSyntaxError(stream.getvalue())
+            exc.__traceback__ = sys.exc_info()[-1]
+            raise exc
+        except ImportError: # duh?
+            sys.stdout = out
+            exc = RQLSyntaxError('Syntax Error', ex.msg, 'on line',
+                                 1 + pinput.count('\n', 0, ex.pos))
+            exc.__traceback__ = sys.exc_info()[-1]
+            raise exc
+    except NoMoreTokens:
+        msg = 'Could not complete parsing; stopped around here: \n%s'
+        exc = RQLSyntaxError(msg  % parser._scanner)
+        exc.__traceback__ = sys.exc_info()[-1]
+        raise exc
+
+pyparse = parse
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/_exceptions.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,41 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""Exceptions used in the RQL package."""
+
+__docformat__ = "restructuredtext en"
+
+class RQLException(Exception):
+    """Base exception for exceptions of the rql module."""
+
+class MissingType(RQLException):
+    """Raised when there is some expected type missing from a schema."""
+
+class UsesReservedWord(RQLException):
+    """Raised when the schema uses a reserved word as type or relation."""
+
+class RQLSyntaxError(RQLException):
+    """Raised when there is a syntax error in the rql string."""
+
+class TypeResolverException(RQLException):
+    """Raised when we are unable to guess variables' type."""
+
+class BadRQLQuery(RQLException):
+    """Raised when there is a no sense in the rql query."""
+
+class CoercionError(RQLException):
+    """Failed to infer type of a math expression."""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/analyze.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,588 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""Analyze of the RQL syntax tree to get possible types for RQL variables.
+
+"""
+from __future__ import print_function
+
+__docformat__ = "restructuredtext en"
+
+import os
+
+from six import StringIO, string_types
+from six.moves import zip
+
+from rql import TypeResolverException, nodes
+
+
+try:
+    pure = bool(os.environ.get('RQL_USE_PURE_PYTHON_ANALYSE', 0))
+    if pure:
+        raise ImportError
+    from rql import rql_solve
+except ImportError:
+    rql_solve = None
+    import warnings
+    warnings.filterwarnings(action='ignore', module='logilab.constraint.propagation')
+    from logilab.constraint import Repository, Solver, fd
+
+    # Gecode solver not available
+#rql_solve = None # uncomment to force using logilab-constraint
+
+class ConstraintCSPProblem(object):
+    def __init__(self):
+        self.constraints = []
+        self.domains = {}
+        self.scons = []
+        self.output = StringIO()
+
+    def debug(self):
+        print("Domains:", self.domains)
+        print("Constraints:", self.constraints)
+        print("Scons:", self.scons)
+
+    def get_output(self):
+        return self.output.getvalue()
+
+    def printer(self, *msgs):
+        self.output.write(' '.join(str(msg) for msg in msgs))
+        self.output.write('\n')
+
+    def solve(self):
+        repo = Repository(self.domains.keys(), self.domains, self.get_constraints())
+        solver = Solver(printer=self.printer)
+        # used for timing 
+        #import time
+        #t0=time.time()
+        sols = solver.solve(repo, verbose=(True or self.debug))
+        #print "RUNTIME:", time.time()-t0
+        return sols
+
+    def add_var(self, name, values):
+        self.domains[name] = fd.FiniteDomain(values)
+
+    def end_domain_definition(self):
+        pass
+
+    def get_domains(self):
+        return self.domains
+
+    def get_constraints(self):
+        return self.constraints
+
+    def add_expr( self, vars, expr ):
+        self.constraints.append( fd.make_expression( vars, expr ) )
+        self.scons.append(expr)
+
+    def var_has_type(self, var, etype):
+        assert isinstance(etype, string_types)
+        self.add_expr( (var,), '%s == %r' % (var, etype) )
+
+    def var_has_types(self, var, etypes):
+        etypes = tuple(etypes)
+        for t in etypes:
+            assert isinstance(t, string_types)
+        if len(etypes) == 1:
+            cstr = '%s == "%s"' % (var, etypes[0])
+        else:
+            cstr = '%s in %s ' % (var, etypes)
+        self.add_expr( (var,), cstr)
+
+    def vars_have_same_types(self, varnames, types):
+        self.add_expr( varnames, '%s in %s' % ( '=='.join(varnames), types))
+
+    def or_and(self, equalities):
+        orred = set()
+        variables = set()
+        for orred_expr in equalities:
+            anded = set()
+            for vars, types in orred_expr:
+                types=tuple(types)
+                for t in types:
+                    assert isinstance(t, string_types)
+                if len(types)==1:
+                    anded.add( '%s == "%s"' % ( '=='.join(vars), types[0]) )
+                else:
+                    anded.add( '%s in %s' % ( '=='.join(vars), types) )
+                for var in vars:
+                    variables.add(var)
+            orred.add( '(' + ' and '.join( list(anded) ) + ')' )
+        expr = " or ".join( list(orred) )
+        self.add_expr( tuple(variables), expr )
+
+# GECODE based constraint solver
+_AND = 0 # symbolic values
+_OR = 1
+_EQ = 2
+_EQV = 3
+
+OPSYM={
+    _AND:"and",
+    _OR:"or",
+    _EQ:"eq",
+    _EQV:"eqv"
+}
+
+class GecodeCSPProblem(object):
+    """Builds an internal representation of the constraint
+    that will be passed to the rql_solve module which implements
+    a gecode-based solver
+
+    The internal representation is a tree builds with lists of lists
+    the first item of the list is the node type (_AND,_OR,_EQ,_EQV)
+
+    an example : ["and", [ "eq",0,0 ], ["or", ["eq", 1, 1], ["eq", 1, 2] ] ]
+
+    means Var(0) == Value(0) and ( Var(1)==Val(1) or Var(1) == Val(2)
+
+    TODO: at the moment the solver makes no type checking on the structure
+    of the tree thus can crash badly if something wrong is handled to it
+    this should not happend as the building of the tree is done internally
+    but it should be fixed anyways.
+    When fixing that we should also replace string nodes by integers
+    """
+    def __init__(self):
+        self.constraints = []
+        self.op = [ _AND ]
+        self.domains = {}       # maps var name -> var value
+        self.variables = {}     # maps var name -> var index
+        self.ivariables = []    # maps var index-> var name
+        self.values = {}        # maps val name -> val index
+        self.all_values = set() # this gets turned into a list later
+        self.idx_domains = []   # maps var index -> list of val index
+        self.ivalues = {}       # only used for debugging
+
+    def debug(self):
+        self.ivalues = {}
+        for val_name, val_num in self.values.items():
+            self.ivalues[val_num] = val_name
+        print("Domains:", self.domains)
+        print("Ops:", self.pretty_print_ops(self.op))
+        print("Variables:", self.variables)
+        print("Values:", self.values)
+
+
+    def pretty_print_ops(self, ops):
+        if ops[0] in (_AND, _OR):
+            res = [ OPSYM[ops[0]], '(' ]
+            for op in ops[1:]:
+                res.append(self.pretty_print_ops(op))
+                res.append(',')
+            res.append( ')' )
+            return "".join(res)
+        elif ops[0] == _EQ:
+            return "%s==%s" % (self.ivariables[ops[1]], self.ivalues[ops[2]])
+        elif ops[0] == _EQV:
+            res = [ self.ivariables[k] for k in ops[1:] ]
+            return '~='.join(res)
+
+    def get_output(self):
+        return ""
+
+    def solve(self):
+        constraints = self.op
+
+        # used for timing
+        #import time
+        #t0=time.time()
+
+        sols = rql_solve.solve( self.idx_domains, len(self.all_values), constraints )
+        rql_sols = []
+        for s in sols:
+            r={}
+            for var, val in zip(self.ivariables, s):
+                r[var] = self.all_values[val]
+            rql_sols.append(r)
+        #print "RUNTIME:", time.time()-t0
+        return rql_sols
+
+    def add_var(self, name, values):
+        assert name not in self.variables
+        self.all_values.update( values )
+        self.variables[name] = len(self.variables)
+        self.ivariables.append(name)
+        self.domains[name] = values
+
+    def end_domain_definition(self):
+        # maps integer->value
+        self.all_values = list(self.all_values)
+        # maps value->integer
+        self.values = dict( [ (v,i) for i,v in enumerate(self.all_values)] )
+        #print self.values
+        #print self.domains
+        for var_name in self.ivariables:
+            val_domain = self.domains[var_name]
+            idx_domain = [ self.values[val] for val in val_domain ]
+            self.idx_domains.append( idx_domain )
+
+    def and_eq( self, var, value ):
+        self.op.append( [_EQ, self.variables[var], self.values[value] ] )
+
+    def equal_vars(self, varnames):
+        if len(varnames)>1:
+            self.op.append( [ _EQV] + [ self.variables[v] for v in varnames ] )
+
+    def var_has_type(self, var, etype):
+        self.and_eq( var, etype)
+
+    def var_has_types(self, var, etypes):
+        for t in etypes:
+            assert isinstance(t, string_types)
+        if len(etypes) == 1:
+            self.and_eq( var, tuple(etypes)[0] )
+        else:
+            orred = [ _OR ]
+            for t in etypes:
+                try:
+                    orred.append( [ _EQ, self.variables[var], self.values[t] ] )
+                except KeyError:
+                    # key error may be raised by self.values[t] if self.values
+                    # reflects constraints from subqueries
+                    continue
+            self.op.append( orred )
+
+    def vars_have_same_types(self, varnames, types):
+        self.equal_vars( varnames )
+        for var in varnames:
+            self.var_has_types( var, types )
+
+    def or_and(self, equalities):
+        orred = [ _OR ]
+        for orred_expr in equalities:
+            anded = [ _AND ]
+            for vars, types in orred_expr:
+                self.equal_vars( vars )
+                for t in types:
+                    assert isinstance(t, string_types)
+                for var in vars:
+                    if len(types)==1:
+                        anded.append( [ _EQ, self.variables[var], self.values[types[0]] ] )
+                    else:
+                        or2 = [ _OR ]
+                        for t in types:
+                            or2.append(  [_EQ, self.variables[var], self.values[t] ] )
+                        anded.append( or2 )
+            orred.append(anded)
+        self.op.append(orred)
+
+if rql_solve is None:
+    CSPProblem = ConstraintCSPProblem
+else:
+    CSPProblem = GecodeCSPProblem
+
+#CSPProblem = ConstraintCSPProblem
+
+
+class ETypeResolver(object):
+    """Resolve variables types according to the schema.
+
+    CSP modelisation:
+     * variable    <-> RQL variable
+     * domains     <-> different entity's types defined in the schema
+     * constraints <-> relations between (RQL) variables
+    """
+    var_solkey = 'possibletypes'
+
+    def __init__(self, schema, uid_func_mapping=None):
+        """
+        :Parameters:
+         * `schema`: an object describing entities and relations that implements
+           the ISchema interface.
+         * `uid_func_mapping`: a dictionary where keys are strings representing an
+           attribute used as a Unique IDentifier and values are methods that
+           accept attribute values and return entity's types.
+           [mapping from relation to function taking rhs value as argument
+           and returning an entity type].
+        """
+        self.debug = 0
+        self.set_schema(schema)
+        if uid_func_mapping is None:
+            self.uid_func_mapping = {}
+            self.uid_func = None
+        else:
+            self.uid_func_mapping = uid_func_mapping
+            self.uid_func = next(iter(uid_func_mapping.values()))
+
+    def set_schema(self, schema):
+        self.schema = schema
+        # default domains for a variable
+        self._base_domain = set(str(etype) for etype in schema.entities())
+        self._nonfinal_domain = set(str(etype) for etype in schema.entities()
+                                    if not etype.final)
+
+    def solve(self, node, constraints):
+        # debug info
+        if self.debug > 1:
+            print("- AN1 -"+'-'*80)
+            print(node)
+            print("CONSTRAINTS:")
+            constraints.debug()
+
+        sols = constraints.solve()
+
+        if not sols:
+            rql = node.as_string('utf8', self.kwargs)
+            ex_msg = 'Unable to resolve variables types in "%s"' % (rql,)
+            if True or self.debug:
+                ex_msg += '\n%s' % (constraints.get_output(),)
+            raise TypeResolverException(ex_msg)
+        node.set_possible_types(sols, self.kwargs, self.var_solkey)
+
+    def _visit(self, node, constraints=None):
+        """Recurse down the tree.
+
+            * node: rql node to process
+            * constraints: a XxxCSPProblem object.
+        """
+        func = getattr(self, 'visit_%s' % node.__class__.__name__.lower())
+        if constraints is None:
+            func(node)
+        elif func(node, constraints) is None:
+            for c in node.children:
+                self._visit(c, constraints)
+
+    def _uid_node_types(self, valnode):
+        types = set()
+        for cst in valnode.iget_nodes(nodes.Constant):
+            assert cst.type
+            if cst.type == 'Substitute':
+                eid = self.kwargs[cst.value]
+                self.deambiguifiers.add(cst.value)
+            else:
+                eid = cst.value
+            cst.uidtype = self.uid_func(cst.eval(self.kwargs))
+            types.add(cst.uidtype)
+        return types
+
+    def _init_stmt(self, node):
+        pb = CSPProblem()
+        # set domain for all the variables
+        for var in node.defined_vars.values():
+            pb.add_var( var.name, self._base_domain )
+        # no variable short cut
+        return pb
+
+    def _extract_constraint(self, constraints, var, term, get_target_types):
+        if self.uid_func:
+            alltypes = set()
+            for etype in self._uid_node_types(term):
+                for targettypes in get_target_types(etype):
+                    alltypes.add(targettypes)
+        else:
+            alltypes = get_target_types()
+        domain = constraints.domains[var]
+        constraints.var_has_types( var, [str(t) for t in alltypes if t in domain] )
+
+    def visit(self, node, uid_func_mapping=None, kwargs=None, debug=False):
+        # FIXME: not thread safe
+        self.debug = debug
+        if uid_func_mapping is not None:
+            assert len(uid_func_mapping) <= 1
+            self.uid_func_mapping = uid_func_mapping
+            self.uid_func = next(iter(uid_func_mapping.values()))
+        self.kwargs = kwargs
+        self.deambiguifiers = set()
+        self._visit(node)
+        if uid_func_mapping is not None:
+            self.uid_func_mapping = None
+            self.uid_func = None
+        return self.deambiguifiers
+
+    def visit_union(self, node):
+        for select in node.children:
+            self._visit(select)
+
+    def visit_insert(self, node):
+        if not node.defined_vars:
+            node.set_possible_types([{}])
+            return
+        constraints = self._init_stmt(node)
+        constraints.end_domain_definition()
+        for etype, variable in node.main_variables:
+            if node.TYPE == 'delete' and etype == 'Any':
+                continue
+            assert etype in self.schema, etype
+            var = variable.name
+            constraints.var_has_type( var, etype )
+        for relation in node.main_relations:
+            self._visit(relation, constraints)
+        # get constraints from the restriction subtree
+        if node.where is not None:
+            self._visit(node.where, constraints)
+        self.solve(node, constraints)
+
+    visit_delete = visit_insert
+
+    def visit_set(self, node):
+        if not node.defined_vars:
+            node.set_possible_types([{}])
+            return
+        constraints = self._init_stmt(node)
+        constraints.end_domain_definition()
+        for relation in node.main_relations:
+            self._visit(relation, constraints)
+        # get constraints from the restriction subtree
+        if node.where is not None:
+            self._visit(node.where, constraints)
+        self.solve(node, constraints)
+
+    def visit_select(self, node):
+        if not (node.defined_vars or node.aliases):
+            node.set_possible_types([{}])
+            return
+        for subquery in node.with_: # resolve subqueries first
+            self.visit_union(subquery.query)
+        constraints = self._init_stmt(node)
+        for ca in node.aliases.values():
+            etypes = set(stmt.selection[ca.colnum].get_type(sol, self.kwargs)
+                         for stmt in ca.query.children for sol in stmt.solutions)
+            constraints.add_var( ca.name, etypes )
+        constraints.end_domain_definition()
+        if self.uid_func:
+            # check rewritten uid const
+            for consts in node.stinfo['rewritten'].values():
+                if not consts:
+                    continue
+                uidtype = self.uid_func(consts[0].eval(self.kwargs))
+                for const in consts:
+                    const.uidtype = uidtype
+        # get constraints from the restriction subtree
+        if node.where is not None:
+            self._visit(node.where, constraints)
+        elif not node.with_:
+            varnames = [v.name for v in node.get_selected_variables()]
+            if varnames:
+                # add constraint on real relation types if no restriction
+                types = [eschema.type for eschema in self.schema.entities()
+                         if not eschema.final]
+                constraints.vars_have_same_types( varnames, types )
+        self.solve(node, constraints)
+
+    def visit_relation(self, relation, constraints):
+        """extract constraints for an relation according to it's  type"""
+        if relation.is_types_restriction():
+            self.visit_type_restriction(relation, constraints)
+            return None
+        rtype = relation.r_type
+        lhs, rhs = relation.get_parts()
+        if rtype == 'identity' and relation.neged(strict=True):
+            return None
+        if rtype in self.uid_func_mapping:
+            if isinstance(relation.parent, nodes.Not) or relation.operator() != '=':
+                # non final entity types
+                etypes = self._nonfinal_domain
+            else:
+                etypes = self._uid_node_types(rhs)
+            if etypes:
+                constraints.var_has_types( lhs.name, etypes )
+                return None
+        if isinstance(rhs, nodes.Comparison):
+            rhs = rhs.children[0]
+        rschema = self.schema.rschema(rtype)
+        if isinstance(lhs, nodes.Constant): # lhs is a constant node (simplified tree)
+            if not isinstance(rhs, nodes.VariableRef):
+                return None
+            self._extract_constraint(constraints, rhs.name, lhs, rschema.objects)
+        elif isinstance(rhs, nodes.Constant) and not rschema.final:
+            # rhs.type is None <-> NULL
+            if not isinstance(lhs, nodes.VariableRef) or rhs.type is None:
+                return None
+            self._extract_constraint(constraints, lhs.name, rhs, rschema.subjects)
+        elif not isinstance(lhs, nodes.VariableRef):
+            # XXX: check relation is valid
+            return None
+        elif isinstance(rhs, nodes.VariableRef):
+            lhsvar = lhs.name
+            rhsvar = rhs.name
+            lhsdomain = constraints.domains[lhsvar]
+            # filter according to domain necessary for column aliases
+            rhsdomain = constraints.domains[rhsvar]
+            res = []
+            var_types = []
+            same_var = (rhsvar == lhsvar)
+
+            for frometype, toetypes in rschema.associations():
+                fromtype = str(frometype)
+                if fromtype in lhsdomain:
+                    totypes = set(str(t) for t in toetypes)
+                    ptypes = totypes & rhsdomain
+                    res.append( [ ([lhsvar], [str(fromtype)]),
+                                  ([rhsvar], list(ptypes)) ] )
+                    if same_var and (fromtype in totypes): #ptypes ?
+                        var_types.append(fromtype)
+            constraints.or_and(res)
+            if same_var:
+                constraints.var_has_types( lhsvar, var_types)
+        else:
+            # XXX consider rhs.get_type?
+            lhsdomain = constraints.domains[lhs.name]
+            ptypes = [str(subj) for subj in rschema.subjects()
+                      if subj in lhsdomain]
+            constraints.var_has_types( lhs.name, ptypes )
+        return None
+
+    def visit_type_restriction(self, relation, constraints):
+        lhs, rhs = relation.get_parts()
+        etypes = set(c.value for c in rhs.iget_nodes(nodes.Constant)
+                     if c.type == 'etype')
+        if relation.r_type == 'is_instance_of':
+            for etype in tuple(etypes):
+                for specialization in self.schema.eschema(etype).specialized_by():
+                    etypes.add(specialization.type)
+        if relation.neged(strict=True):
+            etypes = frozenset(t for t in self._nonfinal_domain if not t in etypes)
+
+        constraints.var_has_types( lhs.name, [ str(t) for t in etypes ] )
+
+    def visit_and(self, et, constraints):
+        pass
+    def visit_or(self, ou, constraints):
+        pass
+    def visit_not(self, et, constraints):
+        pass
+    def visit_comparison(self, comparison, constraints):
+        pass
+    def visit_mathexpression(self, mathexpression, constraints):
+        pass
+    def visit_function(self, function, constraints):
+        pass
+    def visit_variableref(self, variableref, constraints):
+        pass
+    def visit_constant(self, constant, constraints):
+        pass
+    def visit_keyword(self, keyword, constraints):
+        pass
+    def visit_exists(self, exists, constraints):
+        pass
+
+
+class ETypeResolverIgnoreTypeRestriction(ETypeResolver):
+    """same as ETypeResolver but ignore type restriction relation
+
+    results are stored in as the 'allpossibletypes' key in variable'stinfo
+    """
+    var_solkey = 'allpossibletypes'
+
+    def visit_type_restriction(self, relation, constraints):
+        pass
+
+    def visit_not(self, et, constraints):
+        child = et.children[0]
+        if isinstance(child, nodes.Relation) and \
+           not self.schema.rschema(child.r_type).final:
+            return True
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/base.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,201 @@
+# copyright 2004-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""Base classes for RQL syntax tree nodes.
+
+Note: this module uses __slots__ to limit memory usage.
+"""
+
+__docformat__ = "restructuredtext en"
+
+from rql.utils import VisitableMixIn
+
+
+class BaseNode(VisitableMixIn):
+    __slots__ = ('parent',)
+
+    def __str__(self):
+        return self.as_string(encoding='utf-8')
+
+    def as_string(self, encoding=None, kwargs=None):
+        """Return the tree as an encoded rql string."""
+        raise NotImplementedError()
+
+    def initargs(self, stmt):
+        """Return list of arguments to give to __init__ to clone this node.
+
+        I don't use __getinitargs__ because I'm not sure it should interfer with
+        copy/pickle
+        """
+        return ()
+
+    @property
+    def root(self):
+        """Return the root node of the tree"""
+        return self.parent.root
+
+    @property
+    def stmt(self):
+        """Return the Select node to which this node belong"""
+        return self.parent.stmt
+
+    @property
+    def scope(self):
+        """Return the scope node to which this node belong (eg Select or Exists
+        node)
+        """
+        return self.parent.scope
+
+    def get_nodes(self, klass):
+        """Return the list of nodes of a given class in the subtree.
+
+        :type klass: a node class (Relation, Constant, etc.)
+        :param klass: the class of nodes to return
+
+        :rtype: list
+        """
+        stack = [self]
+        result = []
+        while stack:
+            node = stack.pop()
+            if isinstance(node, klass):
+                result.append(node)
+            else:
+                stack += node.children
+        return result
+
+    def iget_nodes(self, klass):
+        """Return an iterator over nodes of a given class in the subtree.
+
+        :type klass: a node class (Relation, Constant, etc.)
+        :param klass: the class of nodes to return
+
+        :rtype: iterator
+        """
+        stack = [self]
+        while stack:
+            node = stack.pop()
+            if isinstance(node, klass):
+                yield node
+            else:
+                stack += node.children
+
+    def is_equivalent(self, other):
+        if not other.__class__ is self.__class__:
+            return False
+        for i, child in enumerate(self.children):
+            try:
+                if not child.is_equivalent(other.children[i]):
+                    return False
+            except IndexError:
+                return False
+        return True
+
+    def index_path(self):
+        if self.parent is None:
+            return []
+        myindex = self.parent.children.index(self)
+        parentindexpath = self.parent.index_path()
+        parentindexpath.append(myindex)
+        return parentindexpath
+
+    def go_to_index_path(self, path):
+        if not path:
+            return self
+        return self.children[path[0]].go_to_index_path(path[1:])
+
+    def copy(self, stmt):
+        """Create and return a copy of this node and its descendant.
+
+        stmt is the root node, which should be use to get new variables
+        """
+        new = self.__class__(*self.initargs(stmt))
+        for child in self.children:
+            new.append(child.copy(stmt))
+        return new
+
+
+class Node(BaseNode):
+    """Class for nodes of the tree which may have children (almost all...)"""
+    __slots__ = ('children',)
+
+    def __init__(self) :
+        self.parent = None
+        self.children = []
+
+    def append(self, child):
+        """add a node to children"""
+        self.children.append(child)
+        child.parent = self
+
+    def remove(self, child):
+        """Remove a child node. Return the removed node, its old parent and
+        index in the children list.
+        """
+        index = self.children.index(child)
+        del self.children[index]
+        parent = child.parent
+        child.parent = None
+        return child, parent, index
+
+    def insert(self, index, child):
+        """insert a child node"""
+        self.children.insert(index, child)
+        child.parent = self
+
+    def replace(self, old_child, new_child):
+        """replace a child node with another"""
+        i = self.children.index(old_child)
+        self.children.pop(i)
+        self.children.insert(i, new_child)
+        new_child.parent = self
+        return old_child, self, i
+
+class BinaryNode(Node):
+    __slots__ = ()
+
+    def __init__(self, lhs=None, rhs=None):
+        Node.__init__(self)
+        if not lhs is None:
+            self.append(lhs)
+        if not rhs is None:
+            self.append(rhs)
+
+    def remove(self, child):
+        """Remove the child and replace this node with the other child."""
+        index = self.children.index(child)
+        return self.parent.replace(self, self.children[not index])
+
+    def get_parts(self):
+        """Return the left hand side and the right hand side of this node."""
+        return self.children[0], self.children[1]
+
+
+class LeafNode(BaseNode):
+    """Class optimized for leaf nodes."""
+    __slots__ = ()
+
+    @property
+    def children(self):
+        return ()
+
+    def copy(self, stmt):
+        """Create and return a copy of this node and its descendant.
+
+        stmt is the root node, which should be use to get new variables.
+        """
+        return self.__class__(*self.initargs(stmt))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/compare.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,216 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""Comparing syntax trees.
+
+"""
+__docformat__ = "restructuredtext en"
+
+
+from six.moves import range
+
+from rql.nodes import VariableRef, Variable, Function, Relation, Comparison
+
+def compare_tree(request1, request2):
+    """Compares 2 RQL requests.
+
+    :rtype: bool
+    :return: True if both requests would return the same results.
+    """
+    return make_canon_dict(request1) == make_canon_dict(request2)
+
+def make_canon_dict(rql_tree, verbose=0):
+    """Return a canonical representation of the request as a dictionnary."""
+    allvars = {}
+    canon = {
+        'all_variables' : allvars,
+        'selected' : [],
+        'restriction' : {},
+        }
+
+    canon = RQLCanonizer().visit(rql_tree, canon)
+
+    # forge variable name
+    for var, name_parts in allvars.values():
+        name_parts.sort()
+        var.name = ':'.join(name_parts)
+    sort(canon)
+    if verbose:
+        print 'CANON FOR', rql_tree
+        from pprint import pprint
+        pprint(canon)
+    return canon
+
+def sort(canon_dict):
+    """Remove the all_variables entry and sort other entries in place."""
+    del canon_dict['all_variables']
+    canon_dict['selection'].sort()
+    for values in canon_dict['restriction'].values():
+        values.sort()
+
+class SkipChildren(Exception):
+    """Signal indicating to ignore the current child."""
+
+class RQLCanonizer(object):
+    """Build a dictionnary which represents a RQL syntax tree."""
+
+    def visit(self, node, canon):
+        try:
+            node.accept(self, canon)
+        except SkipChildren:
+            return canon
+        for c in node.children:
+            self.visit(c, canon)
+        return canon
+
+    def visit_select(self, select, canon):
+        allvars = canon['all_variables']
+        for var in select.defined_vars.values():
+            allvars[var] = (Variable(var.name), [])
+        canon['selection'] = l = []
+        selected = select.selected
+        for i in range(len(selected)):
+            node = selected[i]
+            if isinstance(node, VariableRef):
+                node = node.variable
+                allvars[node][1].append(str(i))
+                l.append(allvars[node][0])
+            else:  # Function
+                l.append(node)
+                for var in node.iget_nodes(VariableRef):
+                    var.parent.replace(var, allvars[var.variable][0])
+
+    def visit_group(self, group, canon):
+        canon['group'] = group
+
+    def visit_sort(self, sort, canon):
+        canon['sort'] = sort
+
+    def visit_sortterm(self, sortterm, canon):
+        pass
+
+    def visit_and(self, et, canon):
+        pass
+
+    def visit_or(self, ou, canon):
+        canon_dict = {}
+        keys = []
+        for expr in ou.get_nodes(Relation):
+            key = '%s%s' % (expr.r_type, expr._not)
+            canon_dict.setdefault(key, []).append(expr)
+            keys.append(key)
+        keys.sort()
+        r_type = ':'.join(keys)
+        r_list = canon['restriction'].setdefault(r_type, [])
+        done = {}
+        for key in keys:
+            if key in done:
+                continue
+            done[key] = None
+            for expr in canon_dict[key]:
+                self.manage_relation(expr, canon, r_list)
+        raise SkipChildren()
+
+    def manage_relation(self, relation, canon, r_list):
+        lhs, rhs = relation.get_parts()
+        # handle special case of the IN function
+        func = rhs.children[0]
+        if isinstance(func, Function) and func.name == 'IN':
+            if not relation._not:
+                base_key = '%s%s' % (relation.r_type, relation._not)
+                if not canon['restriction'][base_key]:
+                    del canon['restriction'][base_key]
+                key = ':'.join([base_key] * len(func.children))
+                r_list = canon['restriction'].setdefault(key, [])
+            for e in func.children:
+                eq_expr = Relation(relation.r_type, relation._not)
+                eq_expr.append(lhs)
+                eq_expr.append(Comparison('=', e))
+                self.manage_relation(eq_expr, canon, r_list)
+                # restaure parent attribute to avoid problem later
+                e.parent = func
+                lhs.parent = relation
+            return
+        # build a canonical representation for this relation
+        lhs_expr_reminder = make_lhs_reminder(lhs, canon)
+        rhs_expr_reminder = make_rhs_reminder(rhs, canon)
+        reminder = (lhs_expr_reminder, rhs_expr_reminder)
+        # avoid duplicate
+        if reminder in r_list:
+            return
+        r_list.append(reminder)
+        # make a string which represents this relation (we'll use it later
+        # to build variables' name)
+        expr_reminder = relation.r_type
+        lhs_vars = lhs.get_nodes(VariableRef)
+        if not lhs_vars:
+            expr_reminder = "%s_%s" % (lhs, expr_reminder)
+        rhs_vars = rhs.get_nodes(VariableRef)
+        if not rhs_vars:
+            expr_reminder = "%s_%s" % (expr_reminder, rhs)
+
+        for var in lhs_vars + rhs_vars:
+            var = var.variable
+            canon['all_variables'][var][1].append(expr_reminder)
+
+
+    def visit_relation(self, relation, canon):
+        key = '%s%s' % (relation.r_type, relation._not)
+        r_list = canon['restriction'].setdefault(key, [])
+        self.manage_relation(relation, canon, r_list)
+
+
+    def visit_comparison(self, comparison, canon):
+        """do nothing for this node type"""
+
+    def visit_mathexpression(self, mathexpression, canon):
+        """do nothing for this node type"""
+
+    def visit_function(self, function, canon):
+        """do nothing for this node type"""
+
+    def visit_variableref(self, varref, canon):
+        varref.parent.replace(varref,
+                              canon['all_variables'][varref.variable][0])
+
+    def visit_constant(self, constante, canon):
+        """do nothing for this node type"""
+
+    def visit_union(self, *args):
+        raise NotImplementedError('union comparison not implemented')
+
+
+def make_lhs_reminder(lhs, canon):
+    """Return a reminder for a relation's left hand side
+    (i.e a VariableRef object).
+    """
+    try:
+        lhs = canon['all_variables'][lhs.variable][0]
+    except (KeyError, IndexError):
+        pass
+    return ('=', lhs)
+
+def make_rhs_reminder(rhs, canon):
+    """Return a reminder for a relation's right hand side
+    (i.e a Comparison object).
+    """
+    child = rhs.children[0]
+    try:
+        child = canon['all_variables'][child.variable][0]
+    except AttributeError:
+        pass
+    return (rhs.operator, child)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/editextensions.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,66 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""RQL functions for manipulating syntax trees."""
+
+__docformat__ = "restructuredtext en"
+
+from rql.nodes import Constant, Variable, VariableRef, Relation, make_relation
+
+def switch_selection(rqlst, new_var, old_var):
+    """Switch the select variable from old_var (VariableRef instance) to
+    new_var (Variable instance).
+    """
+    rqlst.remove_selected(old_var)
+    rqlst.add_selected(new_var, 0)
+
+def add_main_restriction(rqlst, new_type, r_type, direction):
+    """The result_tree must represent the same restriction as 'rqlst' and :
+       - 'new_varname' IS <new_type>
+       - 'old_main_var' <r_type> 'new_varname'
+    """
+    new_var = rqlst.make_variable(new_type)
+    # new_var IS new_type
+    rqlst.add_restriction(make_relation(new_var, 'is', (new_type, 'etype'),
+                                        Constant))
+    # new_var REL old_var (ou l'inverse)
+    old_var = rqlst.selected[0]
+    if direction == 'subject':
+        rel_rest = make_relation(old_var.variable, r_type, (new_var, 1),
+                                 VariableRef)
+    else:
+        rel_rest = make_relation(new_var, r_type, (old_var.variable, 1),
+                                 VariableRef)
+    rqlst.add_restriction(rel_rest)
+    return new_var
+
+def remove_has_text_relation(node):
+    """Remove has_text relation."""
+    for rel in node.iget_nodes(Relation):
+        if rel.r_type == 'has_text':
+            node.remove_node(rel)
+            return
+
+def get_vars_relations(node):
+    """Return a dict with 'var_names' as keys, and the list of relations which
+    concern them.
+    """
+    exp_concerns = {}
+    for exp in node.iget_nodes(Relation):
+        for vref in exp.iget_nodes(VariableRef):
+            exp_concerns.setdefault(vref.name, []).append(exp)
+    return exp_concerns
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/gecode_solver.cpp	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,544 @@
+#include <Python.h>
+#include <iostream>
+#include <string.h>
+#include <exception>
+#include "gecode/kernel.hh"
+#include "gecode/int.hh"
+#include "gecode/search.hh"
+
+#if 1
+#define debug(fmt,...)
+#else
+#define debug(fmt,...) printf(fmt, ##__VA_ARGS__)
+#endif
+
+
+#define PM_VERSION(a,b,c) ((a<<16)+(b<<8)+(c))
+// There is no easy way to test for gecode version here
+// so the build system must pass GE_VERSION accordingly
+// by default we build for 3.1.0 if GECODE_VERSION exists
+
+#ifndef GE_VERSION
+#ifndef GECODE_VERSION
+#define GE_VERSION PM_VERSION(2,1,2)
+#else
+#define GE_VERSION PM_VERSION(3,1,0)
+#endif
+#endif
+
+#if GE_VERSION < PM_VERSION(2,0,0)
+#define SELF this
+#define INT_VAR_NONE BVAR_NONE
+#define INT_VAL_MIN BVAL_MIN
+
+#elif GE_VERSION < PM_VERSION(3,0,0)
+#define SELF this
+#define SET_VAR_SIZE_MAX SET_VAR_MAX_CARD
+#define SET_VAL_MIN_INC SET_VAL_MIN
+#else
+#define SELF (*this)
+#define convexHull convex
+#endif
+
+#if GE_VERSION >= PM_VERSION(4, 0, 0)
+#define INT_VAR_NONE INT_VAR_NONE()
+#define INT_VAL_MIN INT_VAL_MIN()
+#endif
+
+using namespace std;
+using namespace Gecode;
+
+#define USE_CLOCK
+#ifdef USE_CLOCK
+#include <ctime>
+
+/// Timer interface stolen from gecode examples
+class Timer {
+private:
+    clock_t t0;
+public:
+    void start(void);
+    double stop(void);
+};
+
+forceinline void
+Timer::start(void) {
+    t0 = clock();
+}
+
+forceinline double
+Timer::stop(void) {
+    return (static_cast<double>(clock()-t0) / CLOCKS_PER_SEC) * 1000.0;
+}
+#else
+#include <sys/time.h>
+#include <unistd.h>
+
+/// Timer interface stolen from gecode examples
+class Timer {
+private:
+    struct timeval t0;
+public:
+    void start(void);
+    double stop(void);
+};
+
+forceinline void
+Timer::start(void) {
+    gettimeofday( &t0, NULL );
+}
+forceinline double
+Timer::stop(void) {
+    struct timeval t1;
+    gettimeofday( &t1, NULL );
+    return (t1.tv_sec - t0.tv_sec)+1e-6*(t1.tv_usec - t0.tv_usec);
+}
+#endif
+
+enum {
+    _AND = 0,
+    _OR  = 1,
+    _EQ  = 2,
+    _EQV = 3
+};
+
+class RqlError : public exception {
+};
+
+class RqlContext {
+    /** Context holding the problem for solving Rql constraints
+	we keep the info as Python objects and parse the problem
+	during the creation of the Gecode problem
+    */
+public:
+    RqlContext(long nvars, PyObject* domains,
+	       long nvalues, PyObject* constraints, PyObject* sols):
+	solutions(-1), // return every solutions
+	time(-1),    // time limit in case the problem is too big
+	fails(-1),     // ?? used by GecodeStop ...
+	nvars(nvars),  // Number of variables
+	nvalues(nvalues), // Number of values
+	constraints(constraints), // A python list, holding the root of the problem
+	sols(sols),    // an empty list that will receive the solutions
+	domains(domains), // A PyList of PyList, one for each var,
+	                  // holding the allowable integer values
+	verbosity(false)  // can help debugging
+    {
+    }
+
+    long solutions;
+    long time;
+    long fails;
+    long nvars;
+    long nvalues;
+    PyObject* constraints;
+    PyObject* sols;
+    PyObject* domains;
+    bool verbosity;
+};
+
+class RqlSolver : public Space {
+/* A gecode Space
+   this is a strange beast that requires special methods and
+   behavior (mostly, copy and (bool,share,space) constructor
+*/
+protected:
+    /* The variables we try to find values for
+       these are the only 'public' variable of the
+       problem.
+
+       we use a lot more intermediate variables but
+       they shouldn't be member of the space
+     */
+    IntVarArray  variables;
+
+public:
+    RqlSolver(const RqlContext& pb):
+	variables(SELF,     // all gecode variable keep a reference to the space
+		  pb.nvars, // number of variables
+		  0,        // minimum domain value
+		  pb.nvalues-1) // max domain value (included)
+    {
+	/* Since we manipulate Boolean expression and
+	   we need to assign truth value to subexpression
+	   eg (a+b)*(c+d) will be translated as :
+	   root = x1 * x2
+	   x1 = a+b
+	   x2 = c+d
+	   root = True
+	*/
+	BoolVar root(SELF, 1,1);
+	
+	set_domains( pb.domains );
+	add_constraints( pb.constraints, root );
+
+	/* the branching strategy, there must be one,
+	   changing it might improve performance, but
+	   in out case, we almost never propagate (ie
+	   gecode solves the problem during its creation)
+	*/
+	branch(SELF, variables, INT_VAR_NONE, INT_VAL_MIN);
+    }
+
+    ~RqlSolver() {};
+
+    RqlSolver(bool share, RqlSolver& s) : Space(share,s)
+    {
+	/* this is necessary for the solver to fork space
+	   while branching
+	*/
+	variables.update(SELF, share, s.variables);
+    }
+
+    void set_domains( PyObject* domains )
+    {
+	PyObject* ovalues;
+	if (!PyList_Check(domains)) {
+	    throw RqlError();
+	}
+	int n = PyList_Size( domains );
+	for(int var=0 ;var<n; ++var) {
+	    /* iterate of domains which should contains
+	       list of values
+	       domains[0] contains possible values for var[0]...
+	    */
+	    int i, nval;
+	    ovalues = PyList_GetItem( domains, var );
+	    if (!PyList_Check(ovalues)) {
+		throw RqlError();
+	    }
+	    nval = PyList_Size( ovalues );
+
+	    /* It's a bit cumbersome to construct an IntSet, but
+	       it's the only way to reduce an integer domain to
+	       a discrete set
+	    */
+	    int* vals = new int[nval];
+	    for(i=0;i<nval;++i) {
+		//refcount ok, borrowed ref
+		vals[i] = PyInt_AsLong( PyList_GetItem( ovalues, i ) );
+		if (vals[i]<0) {
+		    /* we don't have negative values and PyInt_AsLong returns
+		       -1 if the object is not an Int */
+		    delete [] vals;
+		    throw RqlError();
+		}
+	    }
+	    IntSet gvalues(vals,nval);
+	    dom(SELF, variables[var], gvalues);
+	    delete [] vals;
+	}
+    }
+
+    /* Dispatch method from Node to specific node type */
+    void add_constraints( PyObject* desc, BoolVar& var )
+    {
+	long type;
+
+	if (!PyList_Check(desc)) {
+	    throw RqlError();
+	}
+	/* the first element of each list (node) is 
+	   a symbolic Int from _AND, _OR, _EQ, _EQV
+	*/
+	type = PyInt_AsLong( PyList_GetItem( desc, 0 ) );
+	
+	switch(type) {
+	case _AND:
+	    add_and( desc, var );
+	    break;
+	case _OR:
+	    add_or( desc, var );
+	    break;
+	case _EQ:
+	    add_equality( desc, var );
+	    break;
+	case _EQV:
+	    add_equivalence( desc, var );
+	    break;
+	default:
+	    throw RqlError();
+	}
+    }
+
+    /* retrieve an int from a list, throw error if int is <0 */
+    long get_uint( PyObject* lst, int index ) {
+	PyObject* val;
+	val = PyList_GetItem(lst, index);
+	if (val<0) {
+	    throw RqlError();
+	}
+	return PyInt_AsLong(val);
+    }
+
+    /* post gecode condition for Var == Value
+       we can't use domain restriction since this
+       condition can be part of an OR clause
+
+       so we post  (var == value) <=> expr_value
+    */
+    void add_equality( PyObject* desc, BoolVar& expr_value ) {
+	long variable, value;
+	
+	variable = get_uint( desc, 1 );
+	value = get_uint( desc, 2 );
+	if (variable==1) {
+	    debug("RQL:%ld == %ld ***\n", variable, value);
+	} else {
+	    debug("RQL:%ld == %ld\n", variable, value);
+	}
+	rel(SELF, variables[variable], IRT_EQ, value, expr_value);
+    }
+
+    /* post gecode condition for Var[i] == Var[j] ... == Var[k]
+
+       there's no operator for assigning chained equality to boolean
+      
+       so we post for 1<=i<=N (var[0] == var[i]) <=> bool[i]
+                  and    bool[1] & ... & bool[N] <=> expr_value
+       that means if all vars are equal expr_value is true
+       if all vars are different from var[0] expr_value is false
+       if some are equals and some false, the constraint is unsatisfiable
+    */
+    void add_equivalence( PyObject* desc, BoolVar& expr_value ) {
+	int len = PyList_Size(desc);
+	int var0 = get_uint( desc, 1 );
+	BoolVarArray terms(SELF, len-2,0,1);
+	debug("RQL:EQV(%d",var0);
+	for (int i=1;i<len-1;++i) {
+	    int var1 = get_uint(desc, i+1);
+	    debug(",%d",var1);
+	    rel(SELF, variables[var0], IRT_EQ, variables[var1], terms[i-1] );
+	}
+	debug(")\n");
+#if GE_VERSION<PM_VERSION(2,0,0)
+    BoolVarArgs terms_args(terms);
+    bool_and(SELF, terms_args, expr_value);
+#else
+	rel(SELF, BOT_AND, terms, expr_value);
+#endif
+    }
+
+    /* simple and relation between nodes */
+    void add_and( PyObject* desc, BoolVar& var ) {
+	int len = PyList_Size(desc);
+	BoolVarArray terms(SELF, len-1,0,1);
+
+	debug("RQL:AND(\n");
+	for(int i=0;i<len-1;++i) {
+	    PyObject* expr = PyList_GetItem(desc, i+1);
+	    add_constraints( expr, terms[i] );
+	}
+	debug("RQL:)\n");
+#if GE_VERSION<PM_VERSION(2,0,0)
+    BoolVarArgs terms_args(terms);
+    bool_and(SELF, terms_args, var);
+#else
+    rel(SELF, BOT_AND, terms, var);
+#endif
+    }
+
+    /* simple or relation between nodes */
+    void add_or( PyObject* desc, BoolVar& var ) {
+	int len = PyList_Size(desc);
+	BoolVarArray terms(SELF, len-1,0,1);
+
+	debug("RQL:OR(\n");
+	for(int i=0;i<len-1;++i) {
+	    PyObject* expr = PyList_GetItem(desc, i+1);
+	    add_constraints( expr, terms[i] );
+	}
+	debug("RQL:)\n");
+#if GE_VERSION<PM_VERSION(2,0,0)
+    BoolVarArgs terms_args(terms);
+    bool_or(SELF, terms_args, var);
+#else
+	rel(SELF, BOT_OR, terms, var);
+#endif
+
+    }
+
+    template <template<class> class Engine>
+    static void run( RqlContext& pb, Search::Stop* stop )
+    {
+	double t0 = 0;
+	int i = pb.solutions;
+	Timer t;
+	RqlSolver* s = new RqlSolver( pb );
+	t.start();
+	unsigned int n_p = 0;
+	unsigned int n_b = 0;
+	if (s->status() != SS_FAILED) {
+	    n_p = s->propagators();
+#if GE_VERSION<PM_VERSION(3,2,0)
+	    n_b = s->branchings();
+#else
+	    n_b = s->branchers();
+#endif
+	}
+#if GE_VERSION<PM_VERSION(2,0,0)
+    Engine<RqlSolver> e(s);
+#else
+    Search::Options opts;
+	//opts.c_d = pb.c_d;
+	//opts.a_d = pb.a_d;
+	opts.stop = stop;
+	Engine<RqlSolver> e(s, opts);
+#endif
+	delete s;
+	do {
+	    RqlSolver* ex = e.next();
+	    if (ex == NULL)
+		break;
+
+	    ex->add_new_solution(pb);
+
+	    delete ex;
+	    t0 = t0 + t.stop();
+	} while (--i != 0 && (pb.time<0 || t0 < pb.time));
+	Search::Statistics stat = e.statistics();
+	if (pb.verbosity) {
+	    cout << endl;
+	    cout << "Initial" << endl
+		 << "\tpropagators:   " << n_p << endl
+		 << "\tbranchings:    " << n_b << endl
+		 << endl
+		 << "Summary" << endl
+		 << "\truntime:       " << t.stop() << endl
+		 << "\tsolutions:     " << abs(static_cast<int>(pb.solutions) - i) << endl
+		 << "\tpropagations:  " << stat.propagate << endl
+		 << "\tfailures:      " << stat.fail << endl
+#if GE_VERSION < PM_VERSION(3,0,0)
+		 << "\tclones:        " << stat.clone << endl
+		 << "\tcommits:       " << stat.commit << endl
+#else
+		 << "\tdepth:        " << stat.depth << endl
+		 << "\tnode:       " << stat.node << endl
+#endif
+#if GE_VERSION < PM_VERSION(4,2,0)
+		 << "\tpeak memory:   "
+		 << static_cast<int>((stat.memory+1023) / 1024) << " KB"
+		 << endl
+#endif
+		    ;
+	}
+    }
+
+    /* We append each solutions to `sols` as a
+       tuple `t` of the values assigned to each var
+       that is t[i] = solution for var[i]
+    */
+    virtual void add_new_solution(RqlContext& pb) {
+	PyObject *tuple, *ival;
+
+	tuple = PyTuple_New( pb.nvars );
+
+	for(int i=0;i<pb.nvars;++i) {
+	    ival = PyInt_FromLong( variables[i].val() );
+	    PyTuple_SetItem( tuple, i, ival );
+	}
+	PyList_Append( pb.sols, tuple );
+    }
+
+    /* another function need by gecode kernel */
+    virtual Space* copy(bool share) {
+	return new RqlSolver(share, *this);
+    }
+
+};
+
+class FailTimeStop : public Search::Stop {
+private:
+    Search::TimeStop *ts;
+    Search::FailStop *fs;
+public:
+    FailTimeStop(int fails, int time):ts(0L),fs(0L) {
+	if (time>=0)
+	    ts = new Search::TimeStop(time);
+	if (fails>=0) {
+	    fs = new Search::FailStop(fails);
+	}
+    }
+#if GE_VERSION < PM_VERSION(3,1,0)
+    bool stop(const Search::Statistics& s) {
+	int sigs = PyErr_CheckSignals();
+	bool fs_stop = false;
+	bool ts_stop = false;
+	if (fs) {
+	    fs_stop = fs->stop(s);
+	}
+	if (ts) {
+	    ts_stop = ts->stop(s);
+	}
+	return sigs || fs_stop || ts_stop;
+    }
+#else
+    /* from gecode 3.1.0 */
+    bool stop(const Search::Statistics& s, const Search::Options &o) {
+	int sigs = PyErr_CheckSignals();
+	bool fs_stop = false;
+	bool ts_stop = false;
+	if (fs) {
+	    fs_stop = fs->stop(s,o);
+	}
+	if (ts) {
+	    ts_stop = ts->stop(s,o);
+	}
+	return sigs || fs_stop || ts_stop;
+    }
+#endif
+
+    /// Create appropriate stop-object
+    static Search::Stop* create(int fails, int time) {
+	return new FailTimeStop(fails, time);
+    }
+};
+
+static void _solve( RqlContext& ctx )
+{
+    Search::Stop *stop = FailTimeStop::create(ctx.fails, ctx.time);
+
+    RqlSolver::run<DFS>( ctx, stop );
+}
+
+
+static PyObject *
+rql_solve(PyObject *self, PyObject *args)
+{
+    PyObject* sols = 0L;
+    PyObject* constraints;
+    PyObject* domains;
+    long nvars, nvalues;
+    if (!PyArg_ParseTuple(args, "OiO", &domains, &nvalues, &constraints))
+        return NULL;
+    sols = PyList_New(0);
+    try {
+	if (!PyList_Check(domains)) {
+	    throw RqlError();
+	}
+	nvars = PyList_Size(domains);
+	RqlContext ctx(nvars, domains, nvalues, constraints, sols );
+	_solve( ctx );
+    } catch(RqlError& e) {
+	Py_DECREF(sols);
+	PyErr_SetString(PyExc_RuntimeError, "Error parsing constraints");
+	return NULL;
+    };
+    return sols;
+}
+
+static PyMethodDef SolveRqlMethods[] = {
+    {"solve",  rql_solve, METH_VARARGS,
+     "Solve RQL variable types problem."},
+    {NULL, NULL, 0, NULL}        /* Sentinel */
+};
+
+
+PyMODINIT_FUNC
+initrql_solve(void)
+{
+    PyObject* m;
+    m = Py_InitModule("rql_solve", SolveRqlMethods);
+    if (m == NULL)
+        return;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/interfaces.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,81 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""Interfaces used by the RQL package.
+
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.common.interface import Interface
+
+class ISchema(Interface):
+    """RQL expects some base types to exists: String, Float, Int, Boolean, Date
+    and a base relation : is
+    """
+
+    def has_entity(self, etype):
+        """Return true if the given type is defined in the schema.
+        """
+
+    def has_relation(self, rtype):
+        """Return true if the given relation's type is defined in the schema.
+        """
+
+    def entities(self, schema=None):
+        """Return the list of possible types.
+
+        If schema is not None, return a list of schemas instead of types.
+        """
+
+    def relations(self, schema=None):
+        """Return the list of possible relations.
+
+        If schema is not None, return a list of schemas instead of relation's
+        types.
+        """
+
+    def relation_schema(self, rtype):
+        """Return the relation schema for the given relation type.
+        """
+
+
+class IRelationSchema(Interface):
+    """Interface for Relation schema (a relation is a named oriented link
+    between two entities).
+    """
+
+    def associations(self):
+        """Return a list of (fromtype, [totypes]) defining between which types
+        this relation may exists.
+        """
+
+    def subjects(self):
+        """Return a list of types which can be subject of this relation.
+        """
+
+    def objects(self):
+        """Return a list of types which can be object of this relation.
+        """
+
+class IEntitySchema(Interface):
+    """Interface for Entity schema."""
+
+    def is_final(self):
+        """Return true if the entity is a final entity (ie cannot be used
+        as subject of a relation).
+        """
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/nodes.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,1123 @@
+# copyright 2004-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""RQL syntax tree nodes.
+
+This module defines all the nodes we can find in a RQL Syntax tree, except
+root nodes, defined in the `stmts` module.
+"""
+
+__docformat__ = "restructuredtext en"
+
+import sys
+from itertools import chain
+from decimal import Decimal
+from datetime import datetime, date, time, timedelta
+from time import localtime
+
+from logilab.database import DYNAMIC_RTYPE
+
+from rql import CoercionError, RQLException
+from rql.base import BaseNode, Node, BinaryNode, LeafNode
+from rql.utils import (function_description, quote, uquote, common_parent,
+                       VisitableMixIn)
+
+CONSTANT_TYPES = frozenset((None, 'Date', 'Datetime', 'Boolean', 'Float', 'Int',
+                            'String', 'Substitute', 'etype'))
+
+
+ETYPE_PYOBJ_MAP = { bool: 'Boolean',
+                    int: 'Int',
+                    float: 'Float',
+                    Decimal: 'Decimal',
+                    str: 'String',
+                    datetime: 'Datetime',
+                    date: 'Date',
+                    time: 'Time',
+                    timedelta: 'Interval',
+                    }
+if sys.version_info < (3,):
+    ETYPE_PYOBJ_MAP[long] = 'Int'
+    ETYPE_PYOBJ_MAP[unicode] = 'String'
+
+KEYWORD_MAP = {'NOW' : datetime.now,
+               'TODAY': date.today}
+
+def etype_from_pyobj(value):
+    """guess yams type from python value"""
+    # note:
+    # * Password is not selectable so no problem
+    # * use type(value) and not value.__class__ since C instances may have no
+    #   __class__ attribute
+    return ETYPE_PYOBJ_MAP[type(value)]
+
+def variable_ref(var):
+    """get a VariableRef"""
+    if isinstance(var, Variable):
+        return VariableRef(var, noautoref=1)
+    assert isinstance(var, VariableRef)
+    return var
+
+def variable_refs(node):
+    for vref in node.iget_nodes(VariableRef):
+        if isinstance(vref.variable, Variable):
+            yield vref
+
+
+class OperatorExpressionMixin(object):
+
+    def initargs(self, stmt):
+        """return list of arguments to give to __init__ to clone this node"""
+        return (self.operator,)
+
+    def is_equivalent(self, other):
+        if not Node.is_equivalent(self, other):
+            return False
+        return self.operator == other.operator
+
+    def get_description(self, mainindex, tr):
+        """if there is a variable in the math expr used as rhs of a relation,
+        return the name of this relation, else return the type of the math
+        expression
+        """
+        try:
+            return tr(self.get_type())
+        except CoercionError:
+            for vref in self.iget_nodes(VariableRef):
+                vtype = vref.get_description(mainindex, tr)
+                if vtype != 'Any':
+                    return tr(vtype)
+
+
+class HSMixin(object):
+    """mixin class for classes which may be the lhs or rhs of an expression"""
+    __slots__ = ()
+
+    def relation(self):
+        """return the parent relation where self occurs or None"""
+        try:
+            return self.parent.relation()
+        except AttributeError:
+            return None
+
+    def get_description(self, mainindex, tr):
+        mytype = self.get_type()
+        if mytype != 'Any':
+            return tr(mytype)
+        return 'Any'
+
+
+# rql st edition utilities ####################################################
+
+def make_relation(var, rel, rhsargs, rhsclass, operator='='):
+    """build an relation equivalent to '<var> rel = <cst>'"""
+    cmpop = Comparison(operator)
+    cmpop.append(rhsclass(*rhsargs))
+    relation = Relation(rel)
+    if hasattr(var, 'variable'):
+        var = var.variable
+    relation.append(VariableRef(var))
+    relation.append(cmpop)
+    return relation
+
+def make_constant_restriction(var, rtype, value, ctype, operator='='):
+    if ctype is None:
+        ctype = etype_from_pyobj(value)
+    if isinstance(value, (set, frozenset, tuple, list, dict)):
+        if len(value) > 1:
+            rel = make_relation(var, rtype, ('IN',), Function, operator)
+            infunc = rel.children[1].children[0]
+            for atype in sorted(value):
+                infunc.append(Constant(atype, ctype))
+            return rel
+        value = next(iter(value))
+    return make_relation(var, rtype, (value, ctype), Constant, operator)
+
+
+class EditableMixIn(object):
+    """mixin class to add edition functionalities to some nodes, eg root nodes
+    (statement) and Exists nodes
+    """
+    __slots__ = ()
+
+    @property
+    def undo_manager(self):
+        return self.root.undo_manager
+
+    @property
+    def should_register_op(self):
+        root = self.root
+        # root is None during parsing
+        return root is not None and root.should_register_op
+
+    def remove_node(self, node, undefine=False):
+        """remove the given node from the tree
+
+        USE THIS METHOD INSTEAD OF .remove to get correct variable references
+        handling
+        """
+        # unregister variable references in the removed subtree
+        parent = node.parent
+        stmt = parent.stmt
+        for varref in node.iget_nodes(VariableRef):
+            varref.unregister_reference()
+            if undefine and not varref.variable.stinfo['references']:
+                stmt.undefine_variable(varref.variable)
+        # remove return actually removed node and its parent
+        node, parent, index = parent.remove(node)
+        if self.should_register_op:
+            from rql.undo import RemoveNodeOperation
+            self.undo_manager.add_operation(RemoveNodeOperation(node, parent, stmt, index))
+
+    def add_restriction(self, relation):
+        """add a restriction relation"""
+        r = self.where
+        if r is not None:
+            newnode = And(r, relation)
+            self.set_where(newnode)
+            if self.should_register_op:
+                from rql.undo import ReplaceNodeOperation
+                self.undo_manager.add_operation(ReplaceNodeOperation(r, newnode))
+        else:
+            self.set_where(relation)
+            if self.should_register_op:
+                from rql.undo import AddNodeOperation
+                self.undo_manager.add_operation(AddNodeOperation(relation))
+        return relation
+
+    def add_constant_restriction(self, var, rtype, value, ctype,
+                                 operator='='):
+        """builds a restriction node to express a constant restriction:
+
+        variable rtype = value
+        """
+        restr = make_constant_restriction(var, rtype, value, ctype, operator)
+        return self.add_restriction(restr)
+
+    def add_relation(self, lhsvar, rtype, rhsvar):
+        """builds a restriction node to express '<var> eid <eid>'"""
+        return self.add_restriction(make_relation(lhsvar, rtype, (rhsvar,),
+                                                  VariableRef))
+
+    def add_eid_restriction(self, var, eid, c_type='Int'):
+        """builds a restriction node to express '<var> eid <eid>'"""
+        assert c_type in ('Int', 'Substitute'), "Error got c_type=%r in eid restriction" % c_type
+        return self.add_constant_restriction(var, 'eid', eid, c_type)
+
+    def add_type_restriction(self, var, etype):
+        """builds a restriction node to express : variable is etype"""
+        typerel = var.stinfo.get('typerel', None)
+        if typerel:
+            if typerel.r_type == 'is':
+                istarget = typerel.children[1].children[0]
+                if isinstance(istarget, Constant):
+                    etypes = (istarget.value,)
+                else: # Function (IN)
+                    etypes = [et.value for et in istarget.children]
+                if etype not in etypes:
+                    raise RQLException('%r not in %r' % (etype, etypes))
+                if len(etypes) > 1:
+                    # iterate a copy of children because it's modified inplace
+                    for child in istarget.children[:]:
+                        if child.value != etype:
+                            typerel.stmt.remove_node(child)
+                return typerel
+            else:
+                assert typerel.r_type == 'is_instance_of'
+                typerel.stmt.remove_node(typerel)
+        return self.add_constant_restriction(var, 'is', etype, 'etype')
+
+
+# base RQL nodes ##############################################################
+
+class SubQuery(BaseNode):
+    """WITH clause"""
+    __slots__ = ('aliases', 'query')
+    def __init__(self, aliases=None, query=None):
+        if aliases is not None:
+            self.set_aliases(aliases)
+        if query is not None:
+            self.set_query(query)
+
+    def set_aliases(self, aliases):
+        self.aliases = aliases
+        for node in aliases:
+            node.parent = self
+
+    def set_query(self, node):
+        self.query = node
+        node.parent = self
+
+    def copy(self, stmt):
+        return SubQuery([v.copy(stmt) for v in self.aliases], self.query.copy())
+
+    @property
+    def children(self):
+        return self.aliases + [self.query]
+
+    def as_string(self, encoding=None, kwargs=None):
+        return '%s BEING (%s)' % (','.join(v.name for v in self.aliases),
+                                  self.query.as_string(encoding, kwargs))
+    def __repr__(self):
+        return '%s BEING (%s)' % (','.join(repr(v) for v in self.aliases),
+                                  repr(self.query))
+
+class And(BinaryNode):
+    """a logical AND node (binary)"""
+    __slots__ = ()
+
+    def as_string(self, encoding=None, kwargs=None):
+        """return the tree as an encoded rql string"""
+        return '%s, %s' % (self.children[0].as_string(encoding, kwargs),
+                           self.children[1].as_string(encoding, kwargs))
+    def __repr__(self):
+        return '%s AND %s' % (repr(self.children[0]), repr(self.children[1]))
+
+    def ored(self, traverse_scope=False, _fromnode=None):
+        return self.parent.ored(traverse_scope, _fromnode or self)
+
+    def neged(self, traverse_scope=False, _fromnode=None):
+        return self.parent.neged(traverse_scope, _fromnode or self)
+
+
+class Or(BinaryNode):
+    """a logical OR node (binary)"""
+    __slots__ = ()
+
+    def as_string(self, encoding=None, kwargs=None):
+        return '(%s) OR (%s)' % (self.children[0].as_string(encoding, kwargs),
+                                 self.children[1].as_string(encoding, kwargs))
+
+    def __repr__(self):
+        return '%s OR %s' % (repr(self.children[0]), repr(self.children[1]))
+
+    def ored(self, traverse_scope=False, _fromnode=None):
+        return self
+
+    def neged(self, traverse_scope=False, _fromnode=None):
+        return self.parent.neged(traverse_scope, _fromnode or self)
+
+
+class Not(Node):
+    """a logical NOT node (unary)"""
+    __slots__ = ()
+    def __init__(self, expr=None):
+        Node.__init__(self)
+        if expr is not None:
+            self.append(expr)
+
+    def as_string(self, encoding=None, kwargs=None):
+        if isinstance(self.children[0], (Exists, Relation)):
+            return 'NOT %s' % self.children[0].as_string(encoding, kwargs)
+        return 'NOT (%s)' % self.children[0].as_string(encoding, kwargs)
+
+    def __repr__(self, encoding=None, kwargs=None):
+        return 'NOT (%s)' % repr(self.children[0])
+
+    def ored(self, traverse_scope=False, _fromnode=None):
+        # XXX consider traverse_scope ?
+        return self.parent.ored(traverse_scope, _fromnode or self)
+
+    def neged(self, traverse_scope=False, _fromnode=None, strict=False):
+        return self
+
+    def remove(self, child):
+        return self.parent.remove(self)
+
+# def parent_scope_property(attr):
+#     def _get_parent_attr(self, attr=attr):
+#         return getattr(self.parent.scope, attr)
+#     return property(_get_parent_attr)
+# # editable compatibility
+# for method in ('remove_node', 'add_restriction', 'add_constant_restriction',
+#                'add_relation', 'add_eid_restriction', 'add_type_restriction'):
+#     setattr(Not, method, parent_scope_property(method))
+
+
+class Exists(EditableMixIn, BaseNode):
+    """EXISTS sub query"""
+    __slots__ = ('query',)
+
+    def __init__(self, restriction=None):
+        if restriction is not None:
+            self.set_where(restriction)
+        else:
+            self.query = None
+
+    def copy(self, stmt):
+        new = self.query.copy(stmt)
+        return Exists(new)
+
+    @property
+    def children(self):
+        return (self.query,)
+
+    def append(self, node):
+        assert self.query is None
+        self.query = node
+        node.parent = self
+
+    def is_equivalent(self, other):
+        raise NotImplementedError
+
+    def as_string(self, encoding=None, kwargs=None):
+        content = self.query and self.query.as_string(encoding, kwargs)
+        return 'EXISTS(%s)' % content
+
+    def __repr__(self):
+        return 'EXISTS(%s)' % repr(self.query)
+
+    def set_where(self, node):
+        self.query = node
+        node.parent = self
+
+    @property
+    def where(self):
+        return self.query
+
+    def replace(self, oldnode, newnode):
+        assert oldnode is self.query
+        self.query = newnode
+        newnode.parent = self
+        return oldnode, self, None
+
+    def remove(self, child):
+        return self.parent.remove(self)
+
+    @property
+    def scope(self):
+        return self
+
+    def ored(self, traverse_scope=False, _fromnode=None):
+        if not traverse_scope:
+            if _fromnode is not None: # stop here
+                return False
+            return self.parent.ored(traverse_scope, self)
+        return self.parent.ored(traverse_scope, _fromnode)
+
+    def neged(self, traverse_scope=False, _fromnode=None, strict=False):
+        if not traverse_scope:
+            if _fromnode is not None: # stop here
+                return False
+            return self.parent.neged(self)
+        elif strict:
+            return isinstance(self.parent, Not)
+        return self.parent.neged(traverse_scope, _fromnode)
+
+
+class Relation(Node):
+    """a RQL relation"""
+    __slots__ = ('r_type', 'optional',
+                 '_q_sqltable', '_q_needcast') # XXX cubicweb specific
+
+    def __init__(self, r_type, optional=None):
+        Node.__init__(self)
+        self.r_type = r_type.encode()
+        self.optional = None
+        self.set_optional(optional)
+
+    def initargs(self, stmt):
+        """return list of arguments to give to __init__ to clone this node"""
+        return self.r_type, self.optional
+
+    def is_equivalent(self, other):
+        if not Node.is_equivalent(self, other):
+            return False
+        if self.r_type != other.r_type:
+            return False
+        return True
+
+    def as_string(self, encoding=None, kwargs=None):
+        """return the tree as an encoded rql string"""
+        try:
+            lhs = self.children[0].as_string(encoding, kwargs)
+            if self.optional in ('left', 'both'):
+                lhs += '?'
+            rhs = self.children[1].as_string(encoding, kwargs)
+            if self.optional in ('right', 'both'):
+                rhs += '?'
+        except IndexError:
+            return repr(self) # not fully built relation
+        return '%s %s %s' % (lhs, self.r_type, rhs)
+
+    def __repr__(self):
+        if self.optional:
+            rtype = '%s[%s]' % (self.r_type, self.optional)
+        else:
+            rtype = self.r_type
+        try:
+            return 'Relation(%r %s %r)' % (self.children[0], rtype,
+                                           self.children[1])
+        except IndexError:
+            return 'Relation(%s)' % self.r_type
+
+    def set_optional(self, optional):
+        assert optional in (None, 'left', 'right')
+        if optional is not None:
+            if self.optional and self.optional != optional:
+                self.optional = 'both'
+            else:
+                self.optional = optional
+
+    def relation(self):
+        """return the parent relation where self occurs or None"""
+        return self
+
+    def ored(self, traverse_scope=False, _fromnode=None):
+        return self.parent.ored(traverse_scope, _fromnode or self)
+
+    def neged(self, traverse_scope=False, _fromnode=None, strict=False):
+        if strict:
+            return isinstance(self.parent, Not)
+        return self.parent.neged(traverse_scope, _fromnode or self)
+
+    def is_types_restriction(self):
+        if self.r_type not in ('is', 'is_instance_of'):
+            return False
+        rhs = self.children[1]
+        if isinstance(rhs, Comparison):
+            rhs = rhs.children[0]
+        # else: relation used in SET OR DELETE selection
+        return ((isinstance(rhs, Constant) and rhs.type == 'etype')
+                or (isinstance(rhs, Function) and rhs.name == 'IN'))
+
+    def operator(self):
+        """return the operator of the relation <, <=, =, >=, > and LIKE
+
+        (relations used in SET, INSERT and DELETE definitions don't have
+         an operator as rhs)
+        """
+        rhs = self.children[1]
+        if isinstance(rhs, Comparison):
+            return rhs.operator
+        return '='
+
+    def get_parts(self):
+        """return the left hand side and the right hand side of this relation
+        """
+        lhs = self.children[0]
+        rhs = self.children[1]
+        return lhs, rhs
+
+    def get_variable_parts(self):
+        """return the left hand side and the right hand side of this relation,
+        ignoring comparison
+        """
+        lhs = self.children[0]
+        rhs = self.children[1].children[0]
+        return lhs, rhs
+
+    def change_optional(self, value):
+        root = self.root
+        if root is not None and root.should_register_op and value != self.optional:
+            from rql.undo import SetOptionalOperation
+            root.undo_manager.add_operation(SetOptionalOperation(self, self.optional))
+        self.set_optional(value)
+
+
+CMP_OPERATORS = frozenset(('=', '!=', '<', '<=', '>=', '>', 'ILIKE', 'LIKE', 'REGEXP'))
+
+class Comparison(HSMixin, Node):
+    """handle comparisons:
+
+     <, <=, =, >=, > LIKE and ILIKE operators have a unique children.
+    """
+    __slots__ = ('operator', 'optional')
+
+    def __init__(self, operator, value=None, optional=None):
+        Node.__init__(self)
+        if operator == '~=':
+            operator = 'ILIKE'
+        assert operator in CMP_OPERATORS, operator
+        self.operator = operator.encode()
+        self.optional = optional
+        if value is not None:
+            self.append(value)
+
+    def initargs(self, stmt):
+        """return list of arguments to give to __init__ to clone this node"""
+        return (self.operator, None, self.optional)
+
+    def set_optional(self, left, right):
+        if left and right:
+            self.optional = 'both'
+        elif left:
+            self.optional = 'left'
+        elif right:
+            self.optional = 'right'
+
+    def is_equivalent(self, other):
+        if not Node.is_equivalent(self, other):
+            return False
+        return self.operator == other.operator
+
+    def as_string(self, encoding=None, kwargs=None):
+        """return the tree as an encoded rql string"""
+        if len(self.children) == 0:
+            return self.operator
+        if len(self.children) == 2:
+            lhsopt = rhsopt = ''
+            if self.optional in ('left', 'both'):
+                lhsopt = '?'
+            if self.optional in ('right', 'both'):
+                rhsopt = '?'
+            return '%s%s %s %s%s' % (self.children[0].as_string(encoding, kwargs),
+                                     lhsopt, self.operator.encode(),
+                                     self.children[1].as_string(encoding, kwargs), rhsopt)
+        if self.operator == '=':
+            return self.children[0].as_string(encoding, kwargs)
+        return '%s %s' % (self.operator.encode(),
+                          self.children[0].as_string(encoding, kwargs))
+
+    def __repr__(self):
+        return '%s %s' % (self.operator, ', '.join(repr(c) for c in self.children))
+
+
+class MathExpression(OperatorExpressionMixin, HSMixin, BinaryNode):
+    """Mathematical Operators"""
+    __slots__ = ('operator',)
+
+    def __init__(self, operator, lhs=None, rhs=None):
+        BinaryNode.__init__(self, lhs, rhs)
+        self.operator = operator.encode()
+
+    def as_string(self, encoding=None, kwargs=None):
+        """return the tree as an encoded rql string"""
+        return '(%s %s %s)' % (self.children[0].as_string(encoding, kwargs),
+                               self.operator.encode(),
+                               self.children[1].as_string(encoding, kwargs))
+
+    def __repr__(self):
+        return '(%r %s %r)' % (self.children[0], self.operator,
+                               self.children[1])
+
+    def get_type(self, solution=None, kwargs=None):
+        """return the type of object returned by this function if known
+
+        solution is an optional variable/etype mapping
+        """
+        lhstype = self.children[0].get_type(solution, kwargs)
+        rhstype = self.children[1].get_type(solution, kwargs)
+        key = (self.operator, lhstype, rhstype)
+        try:
+            return {('-', 'Date', 'Datetime'):     'Interval',
+                    ('-', 'Datetime', 'Datetime'): 'Interval',
+                    ('-', 'Date', 'Date'):         'Interval',
+                    ('-', 'Date', 'Time'):     'Datetime',
+                    ('+', 'Date', 'Time'):     'Datetime',
+                    ('-', 'Datetime', 'Time'): 'Datetime',
+                    ('+', 'Datetime', 'Time'): 'Datetime',
+                    }[key]
+        except KeyError:
+            if lhstype == rhstype:
+                return rhstype
+            if sorted((lhstype, rhstype)) == ['Float', 'Int']:
+                return 'Float'
+            raise CoercionError(key)
+
+
+class UnaryExpression(OperatorExpressionMixin, Node):
+    """Unary Operators"""
+    __slots__ = ('operator',)
+
+    def __init__(self, operator, child=None):
+        Node.__init__(self)
+        self.operator = operator.encode()
+        if child is not None:
+            self.append(child)
+
+    def as_string(self, encoding=None, kwargs=None):
+        """return the tree as an encoded rql string"""
+        return '%s%s' % (self.operator.encode(),
+                         self.children[0].as_string(encoding, kwargs))
+
+    def __repr__(self):
+        return '%s%r' % (self.operator, self.children[0])
+
+    def get_type(self, solution=None, kwargs=None):
+        """return the type of object returned by this expression if known
+
+        solution is an optional variable/etype mapping
+        """
+        return self.children[0].get_type(solution, kwargs)
+
+
+class Function(HSMixin, Node):
+    """Class used to deal with aggregat functions (sum, min, max, count, avg)
+    and latter upper(), lower() and other RQL transformations functions
+    """
+    __slots__ = ('name',)
+
+    def __init__(self, name):
+        Node.__init__(self)
+        self.name = name.strip().upper().encode()
+
+    def initargs(self, stmt):
+        """return list of arguments to give to __init__ to clone this node"""
+        return (self.name,)
+
+    def is_equivalent(self, other):
+        if not Node.is_equivalent(self, other):
+            return False
+        return self.name == other.name
+
+    def as_string(self, encoding=None, kwargs=None):
+        """return the tree as an encoded rql string"""
+        return '%s(%s)' % (self.name, ', '.join(c.as_string(encoding, kwargs)
+                                                for c in self.children))
+
+    def __repr__(self):
+        return '%s(%s)' % (self.name, ', '.join(repr(c) for c in self.children))
+
+    def get_type(self, solution=None, kwargs=None):
+        """return the type of object returned by this function if known
+
+        solution is an optional variable/etype mapping
+        """
+        func_descr = self.descr()
+        rtype = func_descr.rql_return_type(self)
+        if rtype is None:
+            # XXX support one variable ref child
+            try:
+                rtype = solution and solution.get(self.children[0].name)
+            except AttributeError:
+                pass
+        return rtype or 'Any'
+
+    def get_description(self, mainindex, tr):
+        return self.descr().st_description(self, mainindex, tr)
+
+    def descr(self):
+        """return the type of object returned by this function if known"""
+        return function_description(self.name)
+
+
+class Constant(HSMixin, LeafNode):
+    """String, Int, TRUE, FALSE, TODAY, NULL..."""
+    __slots__ = ('value', 'type', 'uid', 'uidtype')
+
+    def __init__(self, value, c_type, _uid=False, _uidtype=None):
+        assert c_type in CONSTANT_TYPES, "Error got c_type="+repr(c_type)
+        LeafNode.__init__(self) # don't care about Node attributes
+        self.value = value
+        self.type = c_type
+        # updated by the annotator/analyzer if necessary
+        self.uid = _uid
+        self.uidtype = _uidtype
+
+    def initargs(self, stmt):
+        """return list of arguments to give to __init__ to clone this node"""
+        return (self.value, self.type, self.uid, self.uidtype)
+
+    def is_equivalent(self, other):
+        if not LeafNode.is_equivalent(self, other):
+            return False
+        return self.type == other.type and self.value == other.value
+
+    def as_string(self, encoding=None, kwargs=None):
+        """return the tree as an encoded rql string (an unicode string is
+        returned if encoding is None)
+        """
+        if self.type is None:
+            return 'NULL'
+        if self.type in ('etype', 'Date', 'Datetime', 'Int', 'Float'):
+            return str(self.value)
+        if self.type == 'Boolean':
+            return self.value and 'TRUE' or 'FALSE'
+        if self.type == 'Substitute':
+            # XXX could get some type information from self.root().schema()
+            #     and linked relation
+            if kwargs is not None:
+                value = kwargs.get(self.value, '???')
+                if isinstance(value, unicode):
+                    if encoding:
+                        value = quote(value.encode(encoding))
+                    else:
+                        value = uquote(value)
+                elif isinstance(value, str):
+                    value = quote(value)
+                else:
+                    value = repr(value)
+                return value
+            return '%%(%s)s' % self.value
+        if isinstance(self.value, unicode):
+            if encoding is not None:
+                return quote(self.value.encode(encoding))
+            return uquote(self.value)
+        return repr(self.value)
+
+    def __repr__(self):
+        return self.as_string('utf8')
+
+    def eval(self, kwargs):
+        if self.type == 'Substitute':
+            return kwargs[self.value]
+        if self.type in ('Date', 'Datetime'): # TODAY, NOW
+            return KEYWORD_MAP[self.value]()
+        return self.value
+
+    def get_type(self, solution=None, kwargs=None):
+        if self.uid:
+            return self.uidtype
+        if self.type == 'Substitute':
+            if kwargs is not None:
+                return etype_from_pyobj(self.eval(kwargs))
+            return 'String'
+        return self.type
+
+
+class VariableRef(HSMixin, LeafNode):
+    """a reference to a variable in the syntax tree"""
+    __slots__ = ('variable', 'name')
+
+    def __init__(self, variable, noautoref=None):
+        LeafNode.__init__(self) # don't care about Node attributes
+        self.variable = variable
+        self.name = variable.name
+        if noautoref is None:
+            self.register_reference()
+
+    def initargs(self, stmt):
+        """return list of arguments to give to __init__ to clone this node"""
+        var = self.variable
+        if isinstance(var, ColumnAlias):
+            newvar = stmt.get_variable(self.name, var.colnum)
+        else:
+            newvar = stmt.get_variable(self.name)
+        newvar.init_copy(var)
+        return (newvar,)
+
+    def is_equivalent(self, other):
+        if not LeafNode.is_equivalent(self, other):
+            return False
+        return self.name == other.name
+
+    def as_string(self, encoding=None, kwargs=None):
+        """return the tree as an encoded rql string"""
+        return self.name
+
+    def __repr__(self):
+        return 'VarRef(%r)' % self.variable
+
+    def __cmp__(self, other):
+        return not self.is_equivalent(other)
+
+    def register_reference(self):
+        self.variable.register_reference(self)
+
+    def unregister_reference(self):
+        self.variable.unregister_reference(self)
+
+    def get_type(self, solution=None, kwargs=None):
+        return self.variable.get_type(solution, kwargs)
+
+    def get_description(self, mainindex, tr):
+        return self.variable.get_description(mainindex, tr)
+
+    def root_selection_index(self):
+        """return the index of this variable reference *in the root selection*
+        if it's selected, else None
+        """
+        myidx = self.variable.selected_index()
+        if myidx is None:
+            return None
+        stmt = self.stmt
+        union = stmt.parent
+        if union.parent is None:
+            return myidx
+        # first .parent is the SubQuery node, we want the Select node
+        parentselect = union.parent.parent
+        for ca in parentselect.aliases.itervalues():
+            if ca.query is union and ca.colnum == myidx:
+                caidx = ca.selected_index()
+                if caidx is None:
+                    return None
+                return parentselect.selection[caidx].root_selection_index()
+
+
+class SortTerm(Node):
+    """a sort term bind a variable to the boolean <asc>
+    if <asc> ascendant sort
+    else descendant sort
+    """
+    __slots__ = ('asc',)
+
+    def __init__(self, variable, asc=1, copy=None):
+        Node.__init__(self)
+        self.asc = asc
+        if copy is None:
+            self.append(variable)
+
+    def initargs(self, stmt):
+        """return list of arguments to give to __init__ to clone this node"""
+        return (None, self.asc, True)
+
+    def is_equivalent(self, other):
+        if not Node.is_equivalent(self, other):
+            return False
+        return self.asc == other.asc
+
+    def as_string(self, encoding=None, kwargs=None):
+        if self.asc:
+            return '%s' % self.term
+        return '%s DESC' % self.term
+
+    def __repr__(self):
+        if self.asc:
+            return '%r ASC' % self.term
+        return '%r DESC' % self.term
+
+    @property
+    def term(self):
+        return self.children[0]
+
+
+
+###############################################################################
+
+class Referenceable(VisitableMixIn):
+    __slots__ = ('name', 'stinfo', 'stmt')
+
+    def __init__(self, name):
+        self.name = name.strip().encode()
+        # used to collect some global information about the syntax tree
+        self.stinfo = {
+            # link to VariableReference objects in the syntax tree
+            'references': set(),
+            }
+        # reference to the selection
+        self.stmt = None
+
+    @property
+    def schema(self):
+        return self.stmt.root.schema
+
+    def init_copy(self, old):
+        # should copy variable's possibletypes on copy
+        if not self.stinfo.get('possibletypes'):
+            self.stinfo['possibletypes'] = old.stinfo.get('possibletypes')
+
+    def as_string(self, encoding=None, kwargs=None):
+        """return the tree as an encoded rql string"""
+        return self.name
+
+    def register_reference(self, vref):
+        """add a reference to this variable"""
+        self.stinfo['references'].add(vref)
+
+    def unregister_reference(self, vref):
+        """remove a reference to this variable"""
+        try:
+            self.stinfo['references'].remove(vref)
+        except KeyError:
+            # this may occur on hairy undoing
+            pass
+
+    def references(self):
+        """return all references on this variable"""
+        return tuple(self.stinfo['references'])
+
+    def prepare_annotation(self):
+        self.stinfo.update({
+            'scope': None,
+            # relations where this variable is used on the lhs/rhs
+            'relations': set(),
+            'rhsrelations': set(),
+            # selection indexes if any
+            'selected': set(),
+            # type restriction (e.g. "is" / "is_instance_of") where this
+            # variable is used on the lhs
+            'typerel': None,
+            # uid relations (e.g. "eid") where this variable is used on the lhs
+            'uidrel': None,
+            # if this variable is an attribute variable (ie final entity), link
+            # to the (prefered) attribute owner variable
+            'attrvar': None,
+            # constant node linked to an uid variable if any
+            'constnode': None,
+            })
+        # remove optional st infos
+        for key in ('optrelations', 'blocsimplification', 'ftirels'):
+            self.stinfo.pop(key, None)
+
+    def _set_scope(self, key, scopenode):
+        if scopenode is self.stmt or self.stinfo[key] is None:
+            self.stinfo[key] = scopenode
+        elif not (self.stinfo[key] is self.stmt or scopenode is self.stinfo[key]):
+            self.stinfo[key] = common_parent(self.stinfo[key], scopenode).scope
+
+    def set_scope(self, scopenode):
+        self._set_scope('scope', scopenode)
+    def get_scope(self):
+        return self.stinfo['scope']
+    scope = property(get_scope, set_scope)
+
+    def add_optional_relation(self, relation):
+        try:
+            self.stinfo['optrelations'].add(relation)
+        except KeyError:
+            self.stinfo['optrelations'] = set((relation,))
+
+    def get_type(self, solution=None, kwargs=None):
+        """return entity type of this object, 'Any' if not found"""
+        if solution:
+            return solution[self.name]
+        if self.stinfo['typerel']:
+            rhs = self.stinfo['typerel'].children[1].children[0]
+            if isinstance(rhs, Constant):
+                return str(rhs.value)
+        schema = self.schema
+        if schema is not None:
+            for rel in self.stinfo['rhsrelations']:
+                try:
+                    lhstype = rel.children[0].get_type(solution, kwargs)
+                    return schema.eschema(lhstype).destination(rel.r_type)
+                except: # CoertionError, AssertionError :(
+                    pass
+        return 'Any'
+
+    def get_description(self, mainindex, tr, none_allowed=False):
+        """return :
+        * the name of a relation where this variable is used as lhs,
+        * the entity type of this object if specified by a 'is' relation,
+        * 'Any' if nothing nicer has been found...
+
+        give priority to relation name
+        """
+        if mainindex is not None:
+            if mainindex in self.stinfo['selected']:
+                return ', '.join(sorted(
+                    tr(etype) for etype in self.stinfo['possibletypes']))
+        rtype = frtype = None
+        schema = self.schema
+        for rel in self.stinfo['relations']:
+            if schema is not None:
+                rschema = schema.rschema(rel.r_type)
+                if rschema.final:
+                    if self.name == rel.children[0].name:
+                        # ignore final relation where this variable is used as subject
+                        continue
+                    # final relation where this variable is used as object
+                    frtype = rel.r_type
+            rtype = rel.r_type
+            lhs, rhs = rel.get_variable_parts()
+            # use getattr, may not be a variable ref (rewritten, constant...)
+            rhsvar = getattr(rhs, 'variable', None)
+            if mainindex is not None:
+                # relation to the main variable, stop searching
+                lhsvar = getattr(lhs, 'variable', None)
+                context = None
+                if lhsvar is not None and mainindex in lhsvar.stinfo['selected']:
+                    if len(lhsvar.stinfo['possibletypes']) == 1:
+                        context = next(iter(lhsvar.stinfo['possibletypes']))
+                    return tr(rtype, context=context)
+                if rhsvar is not None and mainindex in rhsvar.stinfo['selected']:
+                    if len(rhsvar.stinfo['possibletypes']) == 1:
+                        context = next(iter(rhsvar.stinfo['possibletypes']))
+                    if schema is not None and rschema.symmetric:
+                        return tr(rtype, context=context)
+                    return tr(rtype + '_object', context=context)
+            if rhsvar is self:
+                rtype += '_object'
+        if frtype is not None:
+            return tr(frtype)
+        if mainindex is None and rtype is not None:
+            return tr(rtype)
+        if none_allowed:
+            return None
+        return ', '.join(sorted(
+            tr(etype) for etype in self.stinfo['possibletypes']))
+
+    def selected_index(self):
+        """return the index of this variable in the selection if it's selected,
+        else None
+        """
+        if not self.stinfo['selected']:
+            return None
+        return next(iter(self.stinfo['selected']))
+
+    def main_relation(self):
+        """Return the relation where this variable is used in the rhs.
+
+        It is useful for cases where this variable is final and we are
+        looking for the entity to which it belongs.
+        """
+        for ref in self.references():
+            rel = ref.relation()
+            if rel is None:
+                continue
+            if rel.r_type != 'is' and self.name != rel.children[0].name:
+                return rel
+        return None
+
+    def valuable_references(self):
+        """return the number of "valuable" references :
+        references is in selection or in a non type (is) relations
+        """
+        stinfo = self.stinfo
+        return len(stinfo['selected']) + len(stinfo['relations'])
+
+
+class ColumnAlias(Referenceable):
+    __slots__ = ('colnum', 'query',
+                 '_q_sql', '_q_sqltable') # XXX cubicweb specific
+    def __init__(self, alias, colnum, query=None):
+        super(ColumnAlias, self).__init__(alias)
+        self.colnum = int(colnum)
+        self.query = query
+
+    def __repr__(self):
+        return 'alias %s' % self.name
+
+    def get_type(self, solution=None, kwargs=None):
+        """return entity type of this object, 'Any' if not found"""
+        vtype = super(ColumnAlias, self).get_type(solution, kwargs)
+        if vtype == 'Any':
+            for select in self.query.children:
+                vtype = select.selection[self.colnum].get_type(solution, kwargs)
+                if vtype != 'Any':
+                    return vtype
+        return vtype
+
+    def get_description(self, mainindex, tr):
+        """return entity type of this object, 'Any' if not found"""
+        vtype = super(ColumnAlias, self).get_description(mainindex, tr,
+                                                         none_allowed=True)
+        if vtype is None:
+            vtypes = set()
+            for select in self.query.children:
+                vtype = select.selection[self.colnum].get_description(mainindex, tr)
+                if vtype is not None:
+                    vtypes.add(vtype)
+            if vtypes:
+                return ', '.join(sorted(vtype for vtype in vtypes))
+        return vtype
+
+
+class Variable(Referenceable):
+    """
+    a variable definition, should not be directly added to the syntax tree (use
+    VariableRef instead)
+
+    collects information about a variable use in a syntax tree
+    """
+    __slots__ = ('_q_invariant', '_q_sql', '_q_sqltable') # XXX ginco specific
+
+    def __repr__(self):
+        return '%s' % self.name
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/parser.g	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,362 @@
+"""yapps input grammar for RQL.
+
+:organization: Logilab
+:copyright: 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+
+Select statement grammar
+------------------------
+
+query = <squery> | <union>
+
+union = (<squery>) UNION (<squery>) [UNION (<squery>)]*
+
+squery = Any <selection>
+        [GROUPBY <variables>]
+        [ORDERBY <sortterms>]
+        [LIMIT <nb> OFFSET <nb>]
+        [WHERE <restriction>]
+        [HAVING <aggregat restriction>]
+        [WITH <subquery> [,<subquery]*]
+
+subquery = <variables> BEING (<query>)
+
+variables = <variable> [, <variable>]*
+
+
+Abbreviations in this code
+--------------------------
+
+rules:
+* rel -> relation
+* decl -> declaration
+* expr -> expression
+* restr -> restriction
+* var -> variable
+* func -> function
+* const -> constant
+* cmp -> comparison
+
+variables:
+* R -> syntax tree root
+* S -> select node
+* P -> parent node
+
+"""
+%%
+
+parser Hercule:
+
+    ignore:            r'\s+'
+    # C-like comments
+    ignore:            r'/\*(?:[^*]|\*(?!/))*\*/'
+
+    token DELETE:      r'(?i)DELETE'
+    token SET:         r'(?i)SET'
+    token INSERT:      r'(?i)INSERT'
+    token UNION:       r'(?i)UNION'
+    token DISTINCT:    r'(?i)DISTINCT'
+    token WITH:        r'(?i)WITH'
+    token WHERE:       r'(?i)WHERE'
+    token BEING:       r'(?i)BEING'
+    token OR:          r'(?i)OR'
+    token AND:         r'(?i)AND'
+    token NOT:         r'(?i)NOT'
+    token GROUPBY:     r'(?i)GROUPBY'
+    token HAVING:      r'(?i)HAVING'
+    token ORDERBY:     r'(?i)ORDERBY'
+    token SORT_ASC:    r'(?i)ASC'
+    token SORT_DESC:   r'(?i)DESC'
+    token LIMIT:       r'(?i)LIMIT'
+    token OFFSET:      r'(?i)OFFSET'
+    token DATE:        r'(?i)TODAY'
+    token DATETIME:    r'(?i)NOW'
+    token TRUE:        r'(?i)TRUE'
+    token FALSE:       r'(?i)FALSE'
+    token NULL:        r'(?i)NULL'
+    token EXISTS:      r'(?i)EXISTS'
+    token CMP_OP:      r'(?i)<=|<|>=|>|!=|=|~=|LIKE|ILIKE|REGEXP'
+    token ADD_OP:      r'\+|-|\||#'
+    token MUL_OP:      r'\*|/|%|&'
+    token POW_OP:      r'\^|>>|<<'
+    token UNARY_OP:    r'-|~'
+    token FUNCTION:    r'[A-Za-z_]+\s*(?=\()'
+    token R_TYPE:      r'[a-z_][a-z0-9_]*'
+    token E_TYPE:      r'[A-Z][A-Za-z0-9]*[a-z]+[A-Z0-9]*'
+    token VARIABLE:    r'[A-Z][A-Z0-9_]*'
+    token COLALIAS:    r'[A-Z][A-Z0-9_]*\.\d+'
+    token QMARK:       r'\?'
+
+    token STRING:      r"'([^\'\\]|\\.)*'|\"([^\\\"\\]|\\.)*\""
+    token FLOAT:       r'-?\d+\.\d*'
+    token INT:         r'-?\d+'
+    token SUBSTITUTE:  r'%\([A-Za-z_0-9]+\)s'
+
+
+#// Grammar entry ###############################################################
+
+
+rule goal: DELETE _delete<<Delete()>> ';'             {{ return _delete }}
+
+         | INSERT _insert<<Insert()>> ';'             {{ return _insert }}
+
+         | SET update<<Set()>> ';'                    {{ return update }}
+
+         | union<<Union()>> ';'                       {{ return union }}
+
+#// Deletion  ###################################################################
+
+rule _delete<<R>>: decl_rels<<R>> where<<R>> having<<R>> {{ return R }}
+
+                 | decl_vars<<R>> where<<R>> having<<R>> {{ return R }}
+
+
+#// Insertion  ##################################################################
+
+rule _insert<<R>>: decl_vars<<R>> insert_rels<<R>> {{ return R }}
+
+
+rule insert_rels<<R>>: ":" decl_rels<<R>> where<<R>> having<<R>> {{ return R }}
+
+                     |
+
+
+#// Update  #####################################################################
+
+rule update<<R>>: decl_rels<<R>> where<<R>> having<<R>> {{ return R }}
+
+
+#// Selection  ##################################################################
+
+rule union<<R>>: select<<Select()>>               {{ R.append(select); return R }}
+
+               | r"\(" select<<Select()>> r"\)"   {{ R.append(select) }}
+                 ( UNION
+                   r"\(" select<<Select()>> r"\)" {{ R.append(select) }}
+                 )*                               {{ return R }}
+
+rule select<<S>>: DISTINCT select_<<S>>  {{ S.distinct = True ; return S }}
+                 | select_<<S>>          {{ return S }}
+
+rule select_<<S>>: E_TYPE selection<<S>>
+                   groupby<<S>>
+                   orderby<<S>>
+                   limit_offset<<S>>
+                   where<<S>>
+                   having<<S>>
+                   with_<<S>>             {{ S.set_statement_type(E_TYPE); return S }}
+
+rule selection<<S>>: expr_add<<S>>        {{ S.append_selected(expr_add) }}
+                     (  ',' expr_add<<S>> {{ S.append_selected(expr_add) }}
+                     )*
+
+
+
+#// other clauses (groupby, orderby, with, having) ##############################
+
+rule groupby<<S>>: GROUPBY              {{ nodes = [] }}
+                   expr_add<<S>>        {{ nodes.append(expr_add) }}
+                   ( ',' expr_add<<S>>  {{ nodes.append(expr_add) }}
+                   )*                   {{ S.set_groupby(nodes); return True }}
+                 |
+
+rule having<<S>>: HAVING logical_expr<<S>> {{ S.set_having([logical_expr]) }}
+                |
+
+rule orderby<<S>>: ORDERBY              {{ nodes = [] }}
+                   sort_term<<S>>       {{ nodes.append(sort_term) }}
+                   ( ',' sort_term<<S>> {{ nodes.append(sort_term) }}
+                   )*                   {{ S.set_orderby(nodes); return True }}
+                 |
+
+rule with_<<S>>: WITH                {{ nodes = [] }}
+                 subquery<<S>>       {{ nodes.append(subquery) }}
+                 ( ',' subquery<<S>> {{ nodes.append(subquery) }}
+                 )*                  {{ S.set_with(nodes) }}
+               |
+
+rule subquery<<S>>: variables<<S>>                     {{ node = SubQuery() ; node.set_aliases(variables) }}
+                    BEING r"\(" union<<Union()>> r"\)" {{ node.set_query(union); return node }}
+
+
+rule sort_term<<S>>: expr_add<<S>> sort_meth {{ return SortTerm(expr_add, sort_meth) }}
+
+
+rule sort_meth: SORT_DESC {{ return 0 }}
+
+              | SORT_ASC  {{ return 1 }}
+
+              |           {{ return 1 # default to SORT_ASC }}
+
+
+#// Limit and offset ############################################################
+
+rule limit_offset<<R>> :  limit<<R>> offset<<R>> {{ return limit or offset }}
+
+rule limit<<R>> : LIMIT INT {{ R.set_limit(int(INT)); return True }}
+                |
+
+rule offset<<R>> : OFFSET INT {{ R.set_offset(int(INT)); return True }}
+  		         |
+
+
+#// Restriction statements ######################################################
+
+rule where<<S>>: WHERE restriction<<S>> {{ S.set_where(restriction) }}
+               |
+
+rule restriction<<S>>: rels_or<<S>>       {{ node = rels_or }}
+                       ( ',' rels_or<<S>> {{ node = And(node, rels_or) }}
+                       )*                 {{ return node }}
+
+rule rels_or<<S>>: rels_and<<S>>      {{ node = rels_and }}
+                   ( OR rels_and<<S>> {{ node = Or(node, rels_and) }}
+                   )*                 {{ return node }}
+
+rule rels_and<<S>>: rels_not<<S>>        {{ node = rels_not }}
+                    ( AND rels_not<<S>>  {{ node = And(node, rels_not) }}
+                    )*                   {{ return node }}
+
+rule rels_not<<S>>: NOT rel<<S>> {{ return Not(rel) }}
+                  | rel<<S>>     {{ return rel }}
+
+rule rel<<S>>: rel_base<<S>>                {{ return rel_base }}
+             | r"\(" restriction<<S>> r"\)" {{ return restriction }}
+
+
+rule rel_base<<S>>: var<<S>> opt_left<<S>> rtype        {{ rtype.append(var) ; rtype.set_optional(opt_left) }}
+                    expr<<S>> opt_right<<S>>            {{ rtype.append(expr) ; rtype.set_optional(opt_right) ; return rtype }}
+                  | EXISTS r"\(" restriction<<S>> r"\)" {{ return Exists(restriction) }}
+
+rule rtype: R_TYPE {{ return Relation(R_TYPE) }}
+
+rule opt_left<<S>>: QMARK  {{ return 'left' }}
+                  |
+rule opt_right<<S>>: QMARK  {{ return 'right' }}
+                   |
+
+#// restriction expressions ####################################################
+
+rule logical_expr<<S>>: exprs_or<<S>>       {{ node = exprs_or }}
+                        ( ',' exprs_or<<S>> {{ node = And(node, exprs_or) }}
+                        )*                  {{ return node }}
+
+rule exprs_or<<S>>: exprs_and<<S>>      {{ node = exprs_and }}
+                    ( OR exprs_and<<S>> {{ node = Or(node, exprs_and) }}
+                    )*                  {{ return node }}
+
+rule exprs_and<<S>>: exprs_not<<S>>        {{ node = exprs_not }}
+                     ( AND exprs_not<<S>>  {{ node = And(node, exprs_not) }}
+                     )*                    {{ return node }}
+
+rule exprs_not<<S>>: NOT balanced_expr<<S>> {{ return Not(balanced_expr) }}
+                   | balanced_expr<<S>>     {{ return balanced_expr }}
+
+#// XXX ambiguity, expr_add may also have '(' as first token. Hence
+#// put "(" logical_expr<<S>> ")" rule first. We can then parse:
+#//
+#//   Any T2 WHERE T1 relation T2 HAVING (1 < COUNT(T1));
+#//
+#// but not
+#//
+#//   Any T2 WHERE T1 relation T2 HAVING (1+2) < COUNT(T1);
+rule balanced_expr<<S>>: r"\(" logical_expr<<S>> r"\)" {{ return logical_expr }}
+                       | expr_add<<S>> opt_left<<S>>
+                         expr_op<<S>> opt_right<<S>> {{ expr_op.insert(0, expr_add); expr_op.set_optional(opt_left, opt_right); return expr_op }}
+
+# // cant use expr<<S>> without introducing some ambiguities
+rule expr_op<<S>>: CMP_OP expr_add<<S>> {{ return Comparison(CMP_OP.upper(), expr_add) }}
+                 | in_expr<<S>>         {{ return Comparison('=', in_expr) }}
+
+
+#// common statements ###########################################################
+
+rule variables<<S>>:                   {{ vars = [] }}
+                       var<<S>>        {{ vars.append(var) }}
+                       (  ',' var<<S>> {{ vars.append(var) }}
+                       )*              {{ return vars }}
+
+rule decl_vars<<R>>: E_TYPE var<<R>> (     {{ R.add_main_variable(E_TYPE, var) }}
+                     ',' E_TYPE var<<R>>)* {{ R.add_main_variable(E_TYPE, var) }}
+
+
+rule decl_rels<<R>>: simple_rel<<R>> (     {{ R.add_main_relation(simple_rel) }}
+                     ',' simple_rel<<R>>)* {{ R.add_main_relation(simple_rel) }}
+
+
+rule simple_rel<<R>>: var<<R>> R_TYPE  {{ e = Relation(R_TYPE) ; e.append(var) }}
+                      expr_add<<R>>    {{ e.append(Comparison('=', expr_add)) ; return e }}
+
+
+rule expr<<S>>: CMP_OP expr_add<<S>> {{ return Comparison(CMP_OP.upper(), expr_add) }}
+
+                | expr_add<<S>>      {{ return Comparison('=', expr_add) }}
+
+
+rule expr_add<<S>>: expr_mul<<S>>          {{ node = expr_mul }}
+                    ( ADD_OP expr_mul<<S>> {{ node = MathExpression( ADD_OP, node, expr_mul ) }}
+                    )*                     {{ return node }}
+                  | UNARY_OP expr_mul<<S>> {{ node = UnaryExpression( UNARY_OP, expr_mul ) }}
+                    ( ADD_OP expr_mul<<S>> {{ node = MathExpression( ADD_OP, node, expr_mul ) }}
+                    )*                     {{ return node }}
+
+
+rule expr_mul<<S>>: expr_pow<<S>>          {{ node = expr_pow }}
+                    ( MUL_OP expr_pow<<S>> {{ node = MathExpression( MUL_OP, node, expr_pow) }}
+                    )*                     {{ return node }}
+
+rule expr_pow<<S>>: expr_base<<S>>          {{ node = expr_base }}
+                    ( POW_OP expr_base<<S>> {{ node = MathExpression( MUL_OP, node, expr_base) }}
+                    )*                      {{ return node }}
+
+
+rule expr_base<<S>>: const                     {{ return const }}
+                   | var<<S>>                  {{ return var }}
+                   | etype<<S>>                {{ return etype }}
+                   | func<<S>>                 {{ return func }}
+                   | r"\(" expr_add<<S>> r"\)" {{ return expr_add }}
+
+
+rule func<<S>>: FUNCTION r"\("        {{ F = Function(FUNCTION) }}
+                   ( expr_add<<S>> (     {{ F.append(expr_add) }}
+                      ',' expr_add<<S>>
+                     )*                  {{ F.append(expr_add) }}
+                   )?
+                r"\)"                 {{ return F }}
+
+rule in_expr<<S>>: 'IN' r"\("        {{ F = Function('IN') }}
+                   ( expr_add<<S>> (     {{ F.append(expr_add) }}
+                      ',' expr_add<<S>>
+                     )*                  {{ F.append(expr_add) }}
+                   )?
+                r"\)"                 {{ return F }}
+
+
+rule var<<S>>: VARIABLE {{ return VariableRef(S.get_variable(VARIABLE)) }}
+
+rule etype<<S>>: E_TYPE {{ return S.get_etype(E_TYPE) }}
+
+
+rule const: NULL       {{ return Constant(None, None) }}
+          | DATE       {{ return Constant(DATE.upper(), 'Date') }}
+          | DATETIME   {{ return Constant(DATETIME.upper(), 'Datetime') }}
+          | TRUE       {{ return Constant(True, 'Boolean') }}
+          | FALSE      {{ return Constant(False, 'Boolean') }}
+          | FLOAT      {{ return Constant(float(FLOAT), 'Float') }}
+          | INT        {{ return Constant(int(INT), 'Int') }}
+          | STRING     {{ return Constant(unquote(STRING), 'String') }}
+          | SUBSTITUTE {{ return Constant(SUBSTITUTE[2:-2], 'Substitute') }}
+%%
+
+from warnings import warn
+from rql.stmts import Union, Select, Delete, Insert, Set
+from rql.nodes import *
+
+
+def unquote(string):
+    """Remove quotes from a string."""
+    if string.startswith('"'):
+        return string[1:-1].replace('\\\\', '\\').replace('\\"', '"')
+    elif string.startswith("'"):
+        return string[1:-1].replace('\\\\', '\\').replace("\\'", "'")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/parser.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,708 @@
+"""yapps input grammar for RQL.
+
+:organization: Logilab
+:copyright: 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+
+Select statement grammar
+------------------------
+
+query = <squery> | <union>
+
+union = (<squery>) UNION (<squery>) [UNION (<squery>)]*
+
+squery = Any <selection>
+        [GROUPBY <variables>]
+        [ORDERBY <sortterms>]
+        [LIMIT <nb> OFFSET <nb>]
+        [WHERE <restriction>]
+        [HAVING <aggregat restriction>]
+        [WITH <subquery> [,<subquery]*]
+
+subquery = <variables> BEING (<query>)
+
+variables = <variable> [, <variable>]*
+
+
+Abbreviations in this code
+--------------------------
+
+rules:
+* rel -> relation
+* decl -> declaration
+* expr -> expression
+* restr -> restriction
+* var -> variable
+* func -> function
+* const -> constant
+* cmp -> comparison
+
+variables:
+* R -> syntax tree root
+* S -> select node
+* P -> parent node
+
+"""
+
+# Begin -- grammar generated by Yapps
+from __future__ import print_function
+import sys, re
+from yapps import runtime
+
+class HerculeScanner(runtime.Scanner):
+    patterns = [
+        ("'IN'", re.compile('IN')),
+        ("','", re.compile(',')),
+        ('r"\\)"', re.compile('\\)')),
+        ('r"\\("', re.compile('\\(')),
+        ('":"', re.compile(':')),
+        ("';'", re.compile(';')),
+        ('\\s+', re.compile('\\s+')),
+        ('/\\*(?:[^*]|\\*(?!/))*\\*/', re.compile('/\\*(?:[^*]|\\*(?!/))*\\*/')),
+        ('DELETE', re.compile('(?i)DELETE')),
+        ('SET', re.compile('(?i)SET')),
+        ('INSERT', re.compile('(?i)INSERT')),
+        ('UNION', re.compile('(?i)UNION')),
+        ('DISTINCT', re.compile('(?i)DISTINCT')),
+        ('WITH', re.compile('(?i)WITH')),
+        ('WHERE', re.compile('(?i)WHERE')),
+        ('BEING', re.compile('(?i)BEING')),
+        ('OR', re.compile('(?i)OR')),
+        ('AND', re.compile('(?i)AND')),
+        ('NOT', re.compile('(?i)NOT')),
+        ('GROUPBY', re.compile('(?i)GROUPBY')),
+        ('HAVING', re.compile('(?i)HAVING')),
+        ('ORDERBY', re.compile('(?i)ORDERBY')),
+        ('SORT_ASC', re.compile('(?i)ASC')),
+        ('SORT_DESC', re.compile('(?i)DESC')),
+        ('LIMIT', re.compile('(?i)LIMIT')),
+        ('OFFSET', re.compile('(?i)OFFSET')),
+        ('DATE', re.compile('(?i)TODAY')),
+        ('DATETIME', re.compile('(?i)NOW')),
+        ('TRUE', re.compile('(?i)TRUE')),
+        ('FALSE', re.compile('(?i)FALSE')),
+        ('NULL', re.compile('(?i)NULL')),
+        ('EXISTS', re.compile('(?i)EXISTS')),
+        ('CMP_OP', re.compile('(?i)<=|<|>=|>|!=|=|~=|LIKE|ILIKE|REGEXP')),
+        ('ADD_OP', re.compile('\\+|-|\\||#')),
+        ('MUL_OP', re.compile('\\*|/|%|&')),
+        ('POW_OP', re.compile('\\^|>>|<<')),
+        ('UNARY_OP', re.compile('-|~')),
+        ('FUNCTION', re.compile('[A-Za-z_]+\\s*(?=\\()')),
+        ('R_TYPE', re.compile('[a-z_][a-z0-9_]*')),
+        ('E_TYPE', re.compile('[A-Z][A-Za-z0-9]*[a-z]+[A-Z0-9]*')),
+        ('VARIABLE', re.compile('[A-Z][A-Z0-9_]*')),
+        ('COLALIAS', re.compile('[A-Z][A-Z0-9_]*\\.\\d+')),
+        ('QMARK', re.compile('\\?')),
+        ('STRING', re.compile('\'([^\\\'\\\\]|\\\\.)*\'|\\"([^\\\\\\"\\\\]|\\\\.)*\\"')),
+        ('FLOAT', re.compile('-?\\d+\\.\\d*')),
+        ('INT', re.compile('-?\\d+')),
+        ('SUBSTITUTE', re.compile('%\\([A-Za-z_0-9]+\\)s')),
+    ]
+    def __init__(self, str,*args,**kw):
+        runtime.Scanner.__init__(self,None,{'\\s+':None,'/\\*(?:[^*]|\\*(?!/))*\\*/':None,},str,*args,**kw)
+
+class Hercule(runtime.Parser):
+    Context = runtime.Context
+    def goal(self, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'goal', [])
+        _token = self._peek('DELETE', 'INSERT', 'SET', 'r"\\("', 'DISTINCT', 'E_TYPE', context=_context)
+        if _token == 'DELETE':
+            DELETE = self._scan('DELETE', context=_context)
+            _delete = self._delete(Delete(), _context)
+            self._scan("';'", context=_context)
+            return _delete
+        elif _token == 'INSERT':
+            INSERT = self._scan('INSERT', context=_context)
+            _insert = self._insert(Insert(), _context)
+            self._scan("';'", context=_context)
+            return _insert
+        elif _token == 'SET':
+            SET = self._scan('SET', context=_context)
+            update = self.update(Set(), _context)
+            self._scan("';'", context=_context)
+            return update
+        else: # in ['r"\\("', 'DISTINCT', 'E_TYPE']
+            union = self.union(Union(), _context)
+            self._scan("';'", context=_context)
+            return union
+
+    def _delete(self, R, _parent=None):
+        _context = self.Context(_parent, self._scanner, '_delete', [R])
+        _token = self._peek('E_TYPE', 'VARIABLE', context=_context)
+        if _token == 'VARIABLE':
+            decl_rels = self.decl_rels(R, _context)
+            where = self.where(R, _context)
+            having = self.having(R, _context)
+            return R
+        else: # == 'E_TYPE'
+            decl_vars = self.decl_vars(R, _context)
+            where = self.where(R, _context)
+            having = self.having(R, _context)
+            return R
+
+    def _insert(self, R, _parent=None):
+        _context = self.Context(_parent, self._scanner, '_insert', [R])
+        decl_vars = self.decl_vars(R, _context)
+        insert_rels = self.insert_rels(R, _context)
+        return R
+
+    def insert_rels(self, R, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'insert_rels', [R])
+        _token = self._peek('":"', "';'", context=_context)
+        if _token == '":"':
+            self._scan('":"', context=_context)
+            decl_rels = self.decl_rels(R, _context)
+            where = self.where(R, _context)
+            having = self.having(R, _context)
+            return R
+        else: # == "';'"
+            pass
+
+    def update(self, R, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'update', [R])
+        decl_rels = self.decl_rels(R, _context)
+        where = self.where(R, _context)
+        having = self.having(R, _context)
+        return R
+
+    def union(self, R, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'union', [R])
+        _token = self._peek('r"\\("', 'DISTINCT', 'E_TYPE', context=_context)
+        if _token != 'r"\\("':
+            select = self.select(Select(), _context)
+            R.append(select); return R
+        else: # == 'r"\\("'
+            self._scan('r"\\("', context=_context)
+            select = self.select(Select(), _context)
+            self._scan('r"\\)"', context=_context)
+            R.append(select)
+            while self._peek('UNION', "';'", 'r"\\)"', context=_context) == 'UNION':
+                UNION = self._scan('UNION', context=_context)
+                self._scan('r"\\("', context=_context)
+                select = self.select(Select(), _context)
+                self._scan('r"\\)"', context=_context)
+                R.append(select)
+            return R
+
+    def select(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'select', [S])
+        _token = self._peek('DISTINCT', 'E_TYPE', context=_context)
+        if _token == 'DISTINCT':
+            DISTINCT = self._scan('DISTINCT', context=_context)
+            select_ = self.select_(S, _context)
+            S.distinct = True ; return S
+        else: # == 'E_TYPE'
+            select_ = self.select_(S, _context)
+            return S
+
+    def select_(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'select_', [S])
+        E_TYPE = self._scan('E_TYPE', context=_context)
+        selection = self.selection(S, _context)
+        groupby = self.groupby(S, _context)
+        orderby = self.orderby(S, _context)
+        limit_offset = self.limit_offset(S, _context)
+        where = self.where(S, _context)
+        having = self.having(S, _context)
+        with_ = self.with_(S, _context)
+        S.set_statement_type(E_TYPE); return S
+
+    def selection(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'selection', [S])
+        expr_add = self.expr_add(S, _context)
+        S.append_selected(expr_add)
+        while self._peek("','", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
+            self._scan("','", context=_context)
+            expr_add = self.expr_add(S, _context)
+            S.append_selected(expr_add)
+
+    def groupby(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'groupby', [S])
+        _token = self._peek('GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
+        if _token == 'GROUPBY':
+            GROUPBY = self._scan('GROUPBY', context=_context)
+            nodes = []
+            expr_add = self.expr_add(S, _context)
+            nodes.append(expr_add)
+            while self._peek("','", 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
+                self._scan("','", context=_context)
+                expr_add = self.expr_add(S, _context)
+                nodes.append(expr_add)
+            S.set_groupby(nodes); return True
+        else:
+            pass
+
+    def having(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'having', [S])
+        _token = self._peek('HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
+        if _token == 'HAVING':
+            HAVING = self._scan('HAVING', context=_context)
+            logical_expr = self.logical_expr(S, _context)
+            S.set_having([logical_expr])
+        else: # in ['WITH', "';'", 'r"\\)"']
+            pass
+
+    def orderby(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'orderby', [S])
+        _token = self._peek('ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
+        if _token == 'ORDERBY':
+            ORDERBY = self._scan('ORDERBY', context=_context)
+            nodes = []
+            sort_term = self.sort_term(S, _context)
+            nodes.append(sort_term)
+            while self._peek("','", 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
+                self._scan("','", context=_context)
+                sort_term = self.sort_term(S, _context)
+                nodes.append(sort_term)
+            S.set_orderby(nodes); return True
+        else:
+            pass
+
+    def with_(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'with_', [S])
+        _token = self._peek('WITH', 'r"\\)"', "';'", context=_context)
+        if _token == 'WITH':
+            WITH = self._scan('WITH', context=_context)
+            nodes = []
+            subquery = self.subquery(S, _context)
+            nodes.append(subquery)
+            while self._peek("','", 'r"\\)"', "';'", context=_context) == "','":
+                self._scan("','", context=_context)
+                subquery = self.subquery(S, _context)
+                nodes.append(subquery)
+            S.set_with(nodes)
+        else: # in ['r"\\)"', "';'"]
+            pass
+
+    def subquery(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'subquery', [S])
+        variables = self.variables(S, _context)
+        node = SubQuery() ; node.set_aliases(variables)
+        BEING = self._scan('BEING', context=_context)
+        self._scan('r"\\("', context=_context)
+        union = self.union(Union(), _context)
+        self._scan('r"\\)"', context=_context)
+        node.set_query(union); return node
+
+    def sort_term(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'sort_term', [S])
+        expr_add = self.expr_add(S, _context)
+        sort_meth = self.sort_meth(_context)
+        return SortTerm(expr_add, sort_meth)
+
+    def sort_meth(self, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'sort_meth', [])
+        _token = self._peek('SORT_DESC', 'SORT_ASC', "','", 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
+        if _token == 'SORT_DESC':
+            SORT_DESC = self._scan('SORT_DESC', context=_context)
+            return 0
+        elif _token == 'SORT_ASC':
+            SORT_ASC = self._scan('SORT_ASC', context=_context)
+            return 1
+        else:
+            return 1 # default to SORT_ASC
+
+    def limit_offset(self, R, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'limit_offset', [R])
+        limit = self.limit(R, _context)
+        offset = self.offset(R, _context)
+        return limit or offset
+
+    def limit(self, R, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'limit', [R])
+        _token = self._peek('LIMIT', 'OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
+        if _token == 'LIMIT':
+            LIMIT = self._scan('LIMIT', context=_context)
+            INT = self._scan('INT', context=_context)
+            R.set_limit(int(INT)); return True
+        else: # in ['OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"']
+            pass
+
+    def offset(self, R, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'offset', [R])
+        _token = self._peek('OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
+        if _token == 'OFFSET':
+            OFFSET = self._scan('OFFSET', context=_context)
+            INT = self._scan('INT', context=_context)
+            R.set_offset(int(INT)); return True
+        else: # in ['WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"']
+            pass
+
+    def where(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'where', [S])
+        _token = self._peek('WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
+        if _token == 'WHERE':
+            WHERE = self._scan('WHERE', context=_context)
+            restriction = self.restriction(S, _context)
+            S.set_where(restriction)
+        else: # in ['HAVING', 'WITH', "';'", 'r"\\)"']
+            pass
+
+    def restriction(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'restriction', [S])
+        rels_or = self.rels_or(S, _context)
+        node = rels_or
+        while self._peek("','", 'r"\\)"', 'HAVING', 'WITH', "';'", context=_context) == "','":
+            self._scan("','", context=_context)
+            rels_or = self.rels_or(S, _context)
+            node = And(node, rels_or)
+        return node
+
+    def rels_or(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'rels_or', [S])
+        rels_and = self.rels_and(S, _context)
+        node = rels_and
+        while self._peek('OR', "','", 'r"\\)"', 'HAVING', 'WITH', "';'", context=_context) == 'OR':
+            OR = self._scan('OR', context=_context)
+            rels_and = self.rels_and(S, _context)
+            node = Or(node, rels_and)
+        return node
+
+    def rels_and(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'rels_and', [S])
+        rels_not = self.rels_not(S, _context)
+        node = rels_not
+        while self._peek('AND', 'OR', "','", 'r"\\)"', 'HAVING', 'WITH', "';'", context=_context) == 'AND':
+            AND = self._scan('AND', context=_context)
+            rels_not = self.rels_not(S, _context)
+            node = And(node, rels_not)
+        return node
+
+    def rels_not(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'rels_not', [S])
+        _token = self._peek('NOT', 'r"\\("', 'EXISTS', 'VARIABLE', context=_context)
+        if _token == 'NOT':
+            NOT = self._scan('NOT', context=_context)
+            rel = self.rel(S, _context)
+            return Not(rel)
+        else: # in ['r"\\("', 'EXISTS', 'VARIABLE']
+            rel = self.rel(S, _context)
+            return rel
+
+    def rel(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'rel', [S])
+        _token = self._peek('r"\\("', 'EXISTS', 'VARIABLE', context=_context)
+        if _token != 'r"\\("':
+            rel_base = self.rel_base(S, _context)
+            return rel_base
+        else: # == 'r"\\("'
+            self._scan('r"\\("', context=_context)
+            restriction = self.restriction(S, _context)
+            self._scan('r"\\)"', context=_context)
+            return restriction
+
+    def rel_base(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'rel_base', [S])
+        _token = self._peek('EXISTS', 'VARIABLE', context=_context)
+        if _token == 'VARIABLE':
+            var = self.var(S, _context)
+            opt_left = self.opt_left(S, _context)
+            rtype = self.rtype(_context)
+            rtype.append(var) ; rtype.set_optional(opt_left)
+            expr = self.expr(S, _context)
+            opt_right = self.opt_right(S, _context)
+            rtype.append(expr) ; rtype.set_optional(opt_right) ; return rtype
+        else: # == 'EXISTS'
+            EXISTS = self._scan('EXISTS', context=_context)
+            self._scan('r"\\("', context=_context)
+            restriction = self.restriction(S, _context)
+            self._scan('r"\\)"', context=_context)
+            return Exists(restriction)
+
+    def rtype(self, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'rtype', [])
+        R_TYPE = self._scan('R_TYPE', context=_context)
+        return Relation(R_TYPE)
+
+    def opt_left(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'opt_left', [S])
+        _token = self._peek('QMARK', 'R_TYPE', 'CMP_OP', "'IN'", context=_context)
+        if _token == 'QMARK':
+            QMARK = self._scan('QMARK', context=_context)
+            return 'left'
+        else: # in ['R_TYPE', 'CMP_OP', "'IN'"]
+            pass
+
+    def opt_right(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'opt_right', [S])
+        _token = self._peek('QMARK', 'AND', 'OR', "','", 'r"\\)"', 'WITH', "';'", 'HAVING', context=_context)
+        if _token == 'QMARK':
+            QMARK = self._scan('QMARK', context=_context)
+            return 'right'
+        else: # in ['AND', 'OR', "','", 'r"\\)"', 'WITH', "';'", 'HAVING']
+            pass
+
+    def logical_expr(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'logical_expr', [S])
+        exprs_or = self.exprs_or(S, _context)
+        node = exprs_or
+        while self._peek("','", 'r"\\)"', 'WITH', "';'", context=_context) == "','":
+            self._scan("','", context=_context)
+            exprs_or = self.exprs_or(S, _context)
+            node = And(node, exprs_or)
+        return node
+
+    def exprs_or(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'exprs_or', [S])
+        exprs_and = self.exprs_and(S, _context)
+        node = exprs_and
+        while self._peek('OR', "','", 'r"\\)"', 'WITH', "';'", context=_context) == 'OR':
+            OR = self._scan('OR', context=_context)
+            exprs_and = self.exprs_and(S, _context)
+            node = Or(node, exprs_and)
+        return node
+
+    def exprs_and(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'exprs_and', [S])
+        exprs_not = self.exprs_not(S, _context)
+        node = exprs_not
+        while self._peek('AND', 'OR', "','", 'r"\\)"', 'WITH', "';'", context=_context) == 'AND':
+            AND = self._scan('AND', context=_context)
+            exprs_not = self.exprs_not(S, _context)
+            node = And(node, exprs_not)
+        return node
+
+    def exprs_not(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'exprs_not', [S])
+        _token = self._peek('NOT', 'r"\\("', 'UNARY_OP', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
+        if _token == 'NOT':
+            NOT = self._scan('NOT', context=_context)
+            balanced_expr = self.balanced_expr(S, _context)
+            return Not(balanced_expr)
+        else:
+            balanced_expr = self.balanced_expr(S, _context)
+            return balanced_expr
+
+    def balanced_expr(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'balanced_expr', [S])
+        _token = self._peek('r"\\("', 'UNARY_OP', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
+        if _token == 'r"\\("':
+            self._scan('r"\\("', context=_context)
+            logical_expr = self.logical_expr(S, _context)
+            self._scan('r"\\)"', context=_context)
+            return logical_expr
+        elif 1:
+            expr_add = self.expr_add(S, _context)
+            opt_left = self.opt_left(S, _context)
+            expr_op = self.expr_op(S, _context)
+            opt_right = self.opt_right(S, _context)
+            expr_op.insert(0, expr_add); expr_op.set_optional(opt_left, opt_right); return expr_op
+        else:
+            raise runtime.SyntaxError(_token[0], 'Could not match balanced_expr')
+
+    def expr_op(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'expr_op', [S])
+        _token = self._peek('CMP_OP', "'IN'", context=_context)
+        if _token == 'CMP_OP':
+            CMP_OP = self._scan('CMP_OP', context=_context)
+            expr_add = self.expr_add(S, _context)
+            return Comparison(CMP_OP.upper(), expr_add)
+        else: # == "'IN'"
+            in_expr = self.in_expr(S, _context)
+            return Comparison('=', in_expr)
+
+    def variables(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'variables', [S])
+        vars = []
+        var = self.var(S, _context)
+        vars.append(var)
+        while self._peek("','", 'BEING', context=_context) == "','":
+            self._scan("','", context=_context)
+            var = self.var(S, _context)
+            vars.append(var)
+        return vars
+
+    def decl_vars(self, R, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'decl_vars', [R])
+        E_TYPE = self._scan('E_TYPE', context=_context)
+        var = self.var(R, _context)
+        while self._peek("','", 'R_TYPE', 'QMARK', 'WHERE', '":"', 'CMP_OP', 'HAVING', "'IN'", "';'", 'POW_OP', 'BEING', 'WITH', 'MUL_OP', 'r"\\)"', 'ADD_OP', 'SORT_DESC', 'SORT_ASC', 'GROUPBY', 'ORDERBY', 'LIMIT', 'OFFSET', 'AND', 'OR', context=_context) == "','":
+            R.add_main_variable(E_TYPE, var)
+            self._scan("','", context=_context)
+            E_TYPE = self._scan('E_TYPE', context=_context)
+            var = self.var(R, _context)
+        R.add_main_variable(E_TYPE, var)
+
+    def decl_rels(self, R, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'decl_rels', [R])
+        simple_rel = self.simple_rel(R, _context)
+        while self._peek("','", 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
+            R.add_main_relation(simple_rel)
+            self._scan("','", context=_context)
+            simple_rel = self.simple_rel(R, _context)
+        R.add_main_relation(simple_rel)
+
+    def simple_rel(self, R, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'simple_rel', [R])
+        var = self.var(R, _context)
+        R_TYPE = self._scan('R_TYPE', context=_context)
+        e = Relation(R_TYPE) ; e.append(var)
+        expr_add = self.expr_add(R, _context)
+        e.append(Comparison('=', expr_add)) ; return e
+
+    def expr(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'expr', [S])
+        _token = self._peek('CMP_OP', 'UNARY_OP', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
+        if _token == 'CMP_OP':
+            CMP_OP = self._scan('CMP_OP', context=_context)
+            expr_add = self.expr_add(S, _context)
+            return Comparison(CMP_OP.upper(), expr_add)
+        else:
+            expr_add = self.expr_add(S, _context)
+            return Comparison('=', expr_add)
+
+    def expr_add(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'expr_add', [S])
+        _token = self._peek('UNARY_OP', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
+        if _token != 'UNARY_OP':
+            expr_mul = self.expr_mul(S, _context)
+            node = expr_mul
+            while self._peek('ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'ADD_OP':
+                ADD_OP = self._scan('ADD_OP', context=_context)
+                expr_mul = self.expr_mul(S, _context)
+                node = MathExpression( ADD_OP, node, expr_mul )
+            return node
+        else: # == 'UNARY_OP'
+            UNARY_OP = self._scan('UNARY_OP', context=_context)
+            expr_mul = self.expr_mul(S, _context)
+            node = UnaryExpression( UNARY_OP, expr_mul )
+            while self._peek('ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'ADD_OP':
+                ADD_OP = self._scan('ADD_OP', context=_context)
+                expr_mul = self.expr_mul(S, _context)
+                node = MathExpression( ADD_OP, node, expr_mul )
+            return node
+
+    def expr_mul(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'expr_mul', [S])
+        expr_pow = self.expr_pow(S, _context)
+        node = expr_pow
+        while self._peek('MUL_OP', 'ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'MUL_OP':
+            MUL_OP = self._scan('MUL_OP', context=_context)
+            expr_pow = self.expr_pow(S, _context)
+            node = MathExpression( MUL_OP, node, expr_pow)
+        return node
+
+    def expr_pow(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'expr_pow', [S])
+        expr_base = self.expr_base(S, _context)
+        node = expr_base
+        while self._peek('POW_OP', 'MUL_OP', 'ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'POW_OP':
+            POW_OP = self._scan('POW_OP', context=_context)
+            expr_base = self.expr_base(S, _context)
+            node = MathExpression( MUL_OP, node, expr_base)
+        return node
+
+    def expr_base(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'expr_base', [S])
+        _token = self._peek('r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
+        if _token not in ['r"\\("', 'VARIABLE', 'E_TYPE', 'FUNCTION']:
+            const = self.const(_context)
+            return const
+        elif _token == 'VARIABLE':
+            var = self.var(S, _context)
+            return var
+        elif _token == 'E_TYPE':
+            etype = self.etype(S, _context)
+            return etype
+        elif _token == 'FUNCTION':
+            func = self.func(S, _context)
+            return func
+        else: # == 'r"\\("'
+            self._scan('r"\\("', context=_context)
+            expr_add = self.expr_add(S, _context)
+            self._scan('r"\\)"', context=_context)
+            return expr_add
+
+    def func(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'func', [S])
+        FUNCTION = self._scan('FUNCTION', context=_context)
+        self._scan('r"\\("', context=_context)
+        F = Function(FUNCTION)
+        if self._peek('UNARY_OP', 'r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"':
+            expr_add = self.expr_add(S, _context)
+            while self._peek("','", 'QMARK', 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == "','":
+                F.append(expr_add)
+                self._scan("','", context=_context)
+                expr_add = self.expr_add(S, _context)
+            F.append(expr_add)
+        self._scan('r"\\)"', context=_context)
+        return F
+
+    def in_expr(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'in_expr', [S])
+        self._scan("'IN'", context=_context)
+        self._scan('r"\\("', context=_context)
+        F = Function('IN')
+        if self._peek('UNARY_OP', 'r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"':
+            expr_add = self.expr_add(S, _context)
+            while self._peek("','", 'QMARK', 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == "','":
+                F.append(expr_add)
+                self._scan("','", context=_context)
+                expr_add = self.expr_add(S, _context)
+            F.append(expr_add)
+        self._scan('r"\\)"', context=_context)
+        return F
+
+    def var(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'var', [S])
+        VARIABLE = self._scan('VARIABLE', context=_context)
+        return VariableRef(S.get_variable(VARIABLE))
+
+    def etype(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'etype', [S])
+        E_TYPE = self._scan('E_TYPE', context=_context)
+        return S.get_etype(E_TYPE)
+
+    def const(self, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'const', [])
+        _token = self._peek('NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', context=_context)
+        if _token == 'NULL':
+            NULL = self._scan('NULL', context=_context)
+            return Constant(None, None)
+        elif _token == 'DATE':
+            DATE = self._scan('DATE', context=_context)
+            return Constant(DATE.upper(), 'Date')
+        elif _token == 'DATETIME':
+            DATETIME = self._scan('DATETIME', context=_context)
+            return Constant(DATETIME.upper(), 'Datetime')
+        elif _token == 'TRUE':
+            TRUE = self._scan('TRUE', context=_context)
+            return Constant(True, 'Boolean')
+        elif _token == 'FALSE':
+            FALSE = self._scan('FALSE', context=_context)
+            return Constant(False, 'Boolean')
+        elif _token == 'FLOAT':
+            FLOAT = self._scan('FLOAT', context=_context)
+            return Constant(float(FLOAT), 'Float')
+        elif _token == 'INT':
+            INT = self._scan('INT', context=_context)
+            return Constant(int(INT), 'Int')
+        elif _token == 'STRING':
+            STRING = self._scan('STRING', context=_context)
+            return Constant(unquote(STRING), 'String')
+        else: # == 'SUBSTITUTE'
+            SUBSTITUTE = self._scan('SUBSTITUTE', context=_context)
+            return Constant(SUBSTITUTE[2:-2], 'Substitute')
+
+
+def parse(rule, text):
+    P = Hercule(HerculeScanner(text))
+    return runtime.wrap_error_reporter(P, rule)
+
+# End -- grammar generated by Yapps
+
+
+
+from warnings import warn
+from rql.stmts import Union, Select, Delete, Insert, Set
+from rql.nodes import *
+
+
+def unquote(string):
+    """Remove quotes from a string."""
+    if string.startswith('"'):
+        return string[1:-1].replace('\\\\', '\\').replace('\\"', '"')
+    elif string.startswith("'"):
+        return string[1:-1].replace('\\\\', '\\').replace("\\'", "'")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/parser_main.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,39 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""Main parser command.
+
+"""
+__docformat__ = "restructuredtext en"
+
+if __name__ == '__main__':
+    from sys import argv
+
+    parser = Hercule(HerculeScanner(argv[1]))
+    e_types = {}
+    # parse the RQL string
+    try:
+        tree = parser.goal(e_types)
+        print '-'*80
+        print tree
+        print '-'*80
+        print repr(tree)
+        print e_types
+    except SyntaxError, ex:
+        # try to get error message from yapps
+        from yapps.runtime import print_error
+        print_error(ex, parser._scanner)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/pygments_ext.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+"""
+    pygments.lexers.rql
+    ~~~~~~~~~~~~~~~~~~~
+
+    Lexer for RQL the relation query language
+
+    http://www.logilab.org/project/rql
+"""
+
+import re
+
+from pygments.lexer import RegexLexer, _mapping
+from pygments.token import Punctuation, \
+     Text, Comment, Operator, Keyword, Name, String, Number
+
+__all__ = ['RqlLexer']
+
+class RqlLexer(RegexLexer):
+    """
+    Lexer for Relation Query Language.
+    """
+
+    name = 'RQL'
+    aliases = ['rql']
+    filenames = ['*.rql']
+    mimetypes = ['text/x-rql']
+
+    flags = re.IGNORECASE
+    tokens = {
+        'root': [
+            (r'\s+', Text),
+            (r'(DELETE|SET|INSERT|UNION|DISTINCT|WITH|WHERE|BEING|OR'
+             r'|AND|NOT|GROUPBY|HAVING|ORDERBY|ASC|DESC|LIMIT|OFFSET'
+             r'|TODAY|NOW|TRUE|FALSE|NULL|EXISTS)\b', Keyword),
+            (r'[+*/<>=%-]', Operator),
+            (r'(Any|is|instance_of)\b', Name.Builtin),
+            (r'[0-9]+', Number.Integer),
+            (r'[A-Z_][A-Z0-9_]*\??', Name),
+            (r"'(''|[^'])*'", String.Single),
+            (r'"(""|[^"])*"', String.Single),
+            (r'[;:()\[\],\.]', Punctuation)
+        ],
+    }
+
+_mapping.LEXERS['RqlLexer'] = ('rql.pygments_ext', 'RQL', ('rql',), ('*.rql',), ('text/x-rql',))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/rqlgen.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,214 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""Generation of RQL strings.
+
+"""
+__docformat__ = "restructuredtext en"
+
+from six.moves import range
+
+NOT = 1
+
+class RQLGenerator(object):
+    """
+    Helper class to generate RQL strings.
+    """
+
+    def select(self, etype, nupplets=(), groups=(), sorts=()) :
+        """
+        Return a RQL selection query.
+
+        :Parameters:
+         * `etype`: the desired entity type (can be 'Any')
+
+         * `nupplets`: a list of 4-uples (subject, relation, object, not).
+           <subject> and <object> may be a string representing a variable
+           or a constant. The special variable X represents the searched set
+           of entities.
+           <relation> is the statement axis.
+           <not> is a boolean indicating it should be a negative statement
+           (0 -> positive statement, 1 -> negative statement). You may omit
+           this parameter, it default to 0.
+
+         * `groups`: a list of variables to use in groups
+
+         * `sorts`: a list of sort term. A sort term is a string designing a
+           variable and optionnaly the sort order ('ASC' or 'DESC'). If the
+           sort order is omitted default to 'ASC'
+
+        Example:
+
+        >>> s = RQLGenerator()
+        >>> s.select('Any', (('X', 'eid', 14),) )
+        'Any X\\nWHERE X eid 14'
+        >>> s.select('Person',
+        ...          ( ('X','work_for','S'), ('S','name','"Logilab"'),
+        ...            ('X','firstname','F'), ('X','surname','S') ),
+        ...          sorts=('F ASC', 'S DESC')
+        ...          )
+        'Person X\\nWHERE X work_for S , S name "Logilab" , X firstname F , X surname S\\nSORTBY F ASC, S DESC'
+        """
+        result = [etype + ' X']
+        if nupplets:
+            result.append(self.where(nupplets))
+        if groups:
+            result.append(self.groupby(groups))
+        if sorts:
+            result.append(self.sortby(sorts))
+        return '\n'.join(result)
+
+
+    def where(self, nupplets):
+        """Return a WHERE statement.
+
+        :Parameters:
+         * `nupplets`: a list of 4-uples (subject, relation, object, not)
+           <subject> and <object> maybe a string designing a variable or a
+           constant. The special variable X represents the searched set of
+           entities
+           <relation> is the statement axis
+           <not> is a boolean indicating it should be a negative statement
+           (0 -> positive statement, 1 -> negative statement). You may omit
+           this parameter, it default to 0.
+
+        Example:
+
+        >>> s = RQLGenerator()
+        >>> s.where( (('X', 'eid', 14),) )
+        'WHERE X eid 14'
+        >>> s.where( ( ('X','work_for','S'), ('S','name','"Logilab"'),
+        ...            ('X','firstname','F'), ('X','surname','S') ) )
+        'WHERE X work_for S , S name "Logilab" , X firstname F , X surname S'
+        """
+        result = ['WHERE']
+        total = len(nupplets)
+        for i in range(total):
+            nupplet = nupplets[i]
+            if len(nupplet) == 4 and nupplet[3] == NOT:
+                result.append('NOT')
+            result.append(str(nupplet[0]))
+            result.append(str(nupplet[1]))
+            result.append(str(nupplet[2]))
+            if i < total - 1:
+                result.append(',')
+        return ' '.join(result)
+
+
+    def groupby(self, groups):
+        """Return a GROUPBY statement.
+
+        :param groups: a list of variables to use in groups
+
+        Example:
+
+        >>> s = RQLGenerator()
+        >>> s.groupby(('F', 'S'))
+        'GROUPBY F, S'
+        """
+        return 'GROUPBY %s' % ', '.join(groups)
+
+
+    def sortby(self, sorts):
+        """Return a SORTBY statement.
+
+        :param sorts: a list of sort term. A sort term is a string designing a
+          variable and optionnaly the sort order ('ASC' or 'DESC'). If the
+          sort order is omitted default to 'ASC'
+
+
+        Example:
+
+        >>> s = RQLGenerator()
+        >>> s.sortby(('F ASC', 'S DESC'))
+        'SORTBY F ASC, S DESC'
+        """
+        return 'SORTBY %s' % ', '.join(sorts)
+
+
+    def insert(self, etype, attributes):
+        """Return an INSERT statement.
+
+        :Parameters:
+         * `etype`: the entity type to insert
+         * `attributes`: a list of tuples (attr_name, attr_value)
+
+        Example:
+
+        >>> s = RQLGenerator()
+        >>> s.insert('Person', (('firstname', "Clark"), ('lastname', "Kent")))
+        'INSERT Person X: X firstname "Clark", X lastname "Kent"'
+        """
+        restrictions = ['X %s "%s"' % (attr_name, attr_value)
+                        for attr_name, attr_value in attributes] # .items()]
+        return 'INSERT %s X: %s' % (etype, ', '.join(restrictions))
+
+
+    def delete(self, etype, attributes):
+        """Return a DELETE statement.
+
+        :Parameters:
+         * `etype`: the entity type to delete
+         * `attributes`: a list of tuples (attr_name, attr_value)
+
+        Example:
+
+        >>> s = RQLGenerator()
+        >>> s.delete('Person', (('firstname', "Clark"), ('lastname', "Kent")))
+        'DELETE Person X where X firstname "Clark", X lastname "Kent"'
+        """
+        restrictions = ['X %s "%s"' % (attr_name, attr_value)
+                        for attr_name, attr_value in attributes] # .items()]
+        return 'DELETE %s X where %s' % (etype, ', '.join(restrictions))
+
+
+    def update(self, etype, old_descr, new_descr):
+        """Return a SET statement.
+
+        :Parameters:
+         * `etype`: the entity type to update
+         * `old_descr`: a list of tuples (attr_name, attr_value)
+           that identifies the entity to update
+         * `new_descr`: a list of tuples (attr_name, attr_value)
+           that defines the attributes to update
+
+        Example:
+
+        >>> s = RQLGenerator()
+        >>> s.update('Person', (('firstname', "Clark"), ('lastname', "Kent")),
+        ...         (('job', "superhero"), ('nickname', "superman")))
+        'SET X job "superhero", X nickname "superman" WHERE X is "Person", X firstname "Clark", X lastname "Kent"'
+        """
+        old_restrictions = ['X is "%s"' % etype]
+        old_restrictions += ['X %s "%s"' % (attr_name, attr_value)
+                             for attr_name, attr_value in old_descr]
+        new_restrictions = ['X %s "%s"' % (attr_name, attr_value)
+                            for attr_name, attr_value in new_descr]
+        return 'SET %s WHERE %s' % (', '.join(new_restrictions),
+                                    ', '.join(old_restrictions))
+
+RQLGENERATOR = RQLGenerator()
+
+def _test():
+    """
+    Launch doctest
+    """
+    import doctest, sys
+    return doctest.testmod(sys.modules[__name__])
+
+if __name__ == "__main__":
+    _test()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rql/stcheck.py	Thu May 28 01:13:28 2015 +0200
@@ -0,0 +1,714 @@
+# copyright 2004-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""RQL Syntax tree annotator"""
+
+__docformat__ = "restructuredtext en"
+
+from itertools import chain
+from logilab.common.graph import has_path
+from logilab.database import UnknownFunction
+
+from rql._exceptions import BadRQLQuery
+from rql.utils import function_description
+from rql.nodes import (Relation, VariableRef, Constant, Not, Exists, Function,
+                       And, Variable, Comparison, variable_refs, make_relation)
+from rql.stmts import Union
+
+
+def _var_graphid(subvarname, trmap, select):
+    try:
+        return trmap[subvarname]
+    except KeyError:
+        return subvarname + str(id(select))
+
+def bloc_simplification(variable, term):
+    try:
+        variable.stinfo['blocsimplification'].add(term)
+    except KeyError:
+        variable.stinfo['blocsimplification'] = set((term,))
+
+
+class GoTo(Exception):
+    """Exception used to control the visit of the tree."""
+    def __init__(self, node):
+        self.node = node
+
+VAR_SELECTED = 1
+VAR_HAS_TYPE_REL = 2
+VAR_HAS_UID_REL = 4
+VAR_HAS_REL = 8
+
+class STCheckState(object):
+    def __init__(self):
+        self.errors = []
+        self.under_not = []
+        self.var_info = {}
+
+    def error(self, msg):
+        self.errors.append(msg)
+
+    def add_var_info(self, var, vi):
+        try:
+            self.var_info[var] |= vi
+        except KeyError:
+            self.var_info[var] = vi
+
+class RQLSTChecker(object):
+    """Check a RQL syntax tree for errors not detected on parsing.
+
+    Some simple rewriting of the tree may be done too:
+    * if a OR is used on a symmetric relation
+    * IN function with a single child
+
+    use assertions for internal error but specific `BadRQLQuery` exception for
+    errors due to a bad rql input
+    """
+
+    def __init__(self, schema, special_relations=None, backend=None):
+        self.schema = schema
+        self.special_relations = special_relations or {}
+        self.backend = backend
+
+    def check(self, node):
+        state = STCheckState()
+        self._visit(node, state)
+        if state.errors:
+            raise BadRQLQuery('%s\n** %s' % (node, '\n** '.join(state.errors)))
+        #if node.TYPE == 'select' and \
+        #       not node.defined_vars and not node.get_restriction():
+        #    result = []
+        #    for term in node.selected_terms():
+        #        result.append(term.eval(kwargs))
+
+    def _visit(self, node, state):
+        try:
+            node.accept(self, state)
+        except GoTo as ex:
+            self._visit(ex.node, state)
+        else:
+            for c in node.children:
+                self._visit(c, state)
+            node.leave(self, state)
+
+    def _visit_selectedterm(self, node, state):
+        for i, term in enumerate(node.selection):
+            # selected terms are not included by the default visit,
+            # accept manually each of them
+            self._visit(term, state)
+
+    def _check_selected(self, term, termtype, state):
+        """check that variables referenced in the given term are selected"""
+        for vref in variable_refs(term):
+            # no stinfo yet, use references
+            for ovref in vref.variable.references():
+                rel = ovref.relation()
+                if rel is not None:
+                    break
+            else:
+                msg = 'variable %s used in %s is not referenced by any relation'
+                state.error(msg % (vref.name, termtype))
+
+    # statement nodes #########################################################
+
+    def visit_union(self, node, state):
+        nbselected = len(node.children[0].selection)
+        for select in node.children[1:]:
+            if not len(select.selection) == nbselected:
+                state.error('when using union, all subqueries should have '
+                              'the same number of selected terms')
+    def leave_union(self, node, state):
+        pass
+
+    def visit_select(self, node, state):
+        node.vargraph = {} # graph representing links between variable
+        node.aggregated = set()
+        self._visit_selectedterm(node, state)
+
+    def leave_select(self, node, state):
+        selected = node.selection
+        # check selected variable are used in restriction
+        if node.where is not None or len(selected) > 1:
+            for term in selected:
+                self._check_selected(term, 'selection', state)
+                for vref in term.iget_nodes(VariableRef):
+                    state.add_var_info(vref.variable, VAR_SELECTED)
+        for var in node.defined_vars.values():
+            vinfo = state.var_info.get(var, 0)
+            if not (vinfo & VAR_HAS_REL) and (vinfo & VAR_HAS_TYPE_REL) \
+                   and not (vinfo & VAR_SELECTED):
+                raise BadRQLQuery('unbound variable %s (%s)' % (var.name, selected))
+        if node.groupby:
+            # check that selected variables are used in groups
+            for var in node.selection:
+                if isinstance(var, VariableRef) and not var in node.groupby:
+                    state.error('variable %s should be grouped' % var)
+            for group in node.groupby:
+                self._check_selected(group, 'group', state)
+        if node.distinct and node.orderby:
+            # check that variables referenced in the given term are reachable from
+            # a selected variable with only ?1 cardinality selected
+            selectidx = frozenset(vref.name for term in selected
+                                  for vref in term.get_nodes(VariableRef))
+            schema = self.schema
+            for sortterm in node.orderby:
+                for vref in sortterm.term.get_nodes(VariableRef):
+                    if vref.name in selectidx:
+                        continue
+                    for vname in selectidx:
+                        try:
+                            if self.has_unique_value_path(node, vname, vref.name):
+                                break
+                        except KeyError:
+                            continue # unlinked variable (usually from a subquery)
+                    else:
+                        msg = ('can\'t sort on variable %s which is linked to a'
+                               ' variable in the selection but may have different'
+                               ' values for a resulting row')
+                        state.error(msg % vref.name)
+
+    def has_unique_value_path(self, select, fromvar, tovar):
+        graph = select.vargraph
+        path = has_path(graph, fromvar, tovar)
+        if path is None:
+            return False
+        for var in path:
+            try:
+                rtype = graph[(fromvar, var)]
+                cardidx = 0
+            except KeyError:
+                rtype = graph[(var, fromvar)]
+                cardidx = 1
+            rschema = self.schema.rschema(rtype)
+            for rdef in rschema.rdefs.values():
+                # XXX aggregats handling needs much probably some enhancements...
+                if not (var in select.aggregated
+                        or (rdef.cardinality[cardidx] in '?1' and
+                            (var == tovar or not rschema.final))):
+                    return False
+            fromvar = var
+        return True
+
+
+    def visit_insert(self, insert, state):
+        self._visit_selectedterm(insert, state)
+    def leave_insert(self, node, state):
+        pass
+
+    def visit_delete(self, delete, state):
+        self._visit_selectedterm(delete, state)
+    def leave_delete(self, node, state):
+        pass
+
+    def visit_set(self, update, state):
+        self._visit_selectedterm(update, state)
+    def leave_set(self, node, state):
+        pass
+
+    # tree nodes ##############################################################
+
+    def visit_exists(self, node, state):
+        pass
+    def leave_exists(self, node, state):
+        pass
+
+    def visit_subquery(self, node, state):
+        pass
+
+    def leave_subquery(self, node, state):
+        # copy graph information we're interested in
+        pgraph = node.parent.vargraph
+        for select in node.query.children:
+            # map subquery variable names to outer query variable names
+            trmap = {}
+            for i, vref in enumerate(node.aliases):
+                try:
+                    subvref = select.selection[i]
+                except IndexError:
+                    state.error('subquery "%s" has only %s selected terms, needs %s'
+                                  % (select, len(select.selection), len(node.aliases)))
+                    continue
+                if isinstance(subvref, VariableRef):
+                    trmap[subvref.name] = vref.name
+                elif (isinstance(subvref, Function) and subvref.descr().aggregat
+                      and len(subvref.children) == 1
+                      and isinstance(subvref.children[0], VariableRef)):
+                    # XXX ok for MIN, MAX, but what about COUNT, AVG...
+                    trmap[subvref.children[0].name] = vref.name
+                    node.parent.aggregated.add(vref.name)
+            for key, val in select.vargraph.items():
+                if isinstance(key, tuple):
+                    key = (_var_graphid(key[0], trmap, select),
+                           _var_graphid(key[1], trmap, select))
+                    pgraph[key] = val
+                else:
+                    values = pgraph.setdefault(_var_graphid(key, trmap, select), [])
+                    values += [_var_graphid(v, trmap, select) for v in val]
+
+    def visit_sortterm(self, sortterm, state):
+        term = sortterm.term
+        if isinstance(term, Constant):
+            for select in sortterm.root.children:
+                if len(select.selection) < term.value:
+                    state.error('order column out of bound %s' % term.value)
+        else:
+            stmt = term.stmt
+            for tvref in variable_refs(term):
+                for vref in tvref.variable.references():
+                    if vref.relation() or vref in stmt.selection:
+                        break
+                else:
+                    msg = 'sort variable %s is not referenced any where else'
+                    state.error(msg % tvref.name)
+
+    def leave_sortterm(self, node, state):
+        pass
+
+    def visit_and(self, et, state):
+        pass #assert len(et.children) == 2, len(et.children)
+    def leave_and(self, node, state):
+        pass
+
+    def visit_or(self, ou, state):
+        #assert len(ou.children) == 2, len(ou.children)
+        # simplify Ored expression of a symmetric relation
+        r1, r2 = ou.children[0], ou.children[1]
+        try:
+            r1type = r1.r_type
+            r2type = r2.r_type
+        except AttributeError:
+            return # can't be
+        if r1type == r2type and self.schema.rschema(r1type).symmetric:
+            lhs1, rhs1 = r1.get_variable_parts()
+            lhs2, rhs2 = r2.get_variable_parts()
+            try:
+                if (lhs1.variable is rhs2.variable and
+                    rhs1.variable is lhs2.variable):
+                    ou.parent.replace(ou, r1)
+                    for vref in r2.get_nodes(VariableRef):
+                        vref.unregister_reference()
+                    raise GoTo(r1)
+            except AttributeError:
+                pass
+    def leave_or(self, node, state):
+        pass
+
+    def visit_not(self, not_, state):
+        state.under_not.append(True)
+    def leave_not(self, not_, state):
+        state.under_not.pop()
+        # NOT normalization
+        child = not_.children[0]
+        if self._should_wrap_by_exists(child):
+            not_.replace(child, Exists(child))
+
+    def _should_wrap_by_exists(self, child):
+        if isinstance(child, Exists):
+            return False
+        if not isinstance(child, Relation):
+            return True
+        if child.r_type == 'identity':
+            return False
+        rschema = self.schema.rschema(child.r_type)
+        if rschema.final:
+            return False
+        # XXX no exists for `inlined` relation (allow IS NULL optimization)
+        # unless the lhs variable is only referenced from this neged relation,
+        # in which case it's *not* in the statement's scope, hence EXISTS should
+        # be added anyway
+        if rschema.inlined:
+            references = child.children[0].variable.references()
+            valuable = 0
+            for vref in references:
+                rel = vref.relation()
+                if rel is None or not rel.is_types_restriction():
+                    if valuable:
+                        return False
+                    valuable = 1
+            return True
+        return not child.is_types_restriction()
+
+    def visit_relation(self, relation, state):
+        if relation.optional and state.under_not:
+            state.error("can't use optional relation under NOT (%s)"
+                        % relation.as_string())
+        lhsvar = relation.children[0].variable
+        if relation.is_types_restriction():
+            if relation.optional:
+                state.error('can\'t use optional relation on "%s"'
+                            % relation.as_string())
+            if state.var_info.get(lhsvar, 0) & VAR_HAS_TYPE_REL:
+                state.error('can only one type restriction per variable (use '
+                            'IN for %s if desired)' % lhsvar.name)
+            else:
+                state.add_var_info(lhsvar, VAR_HAS_TYPE_REL)
+            # special case "C is NULL"
+            # if relation.children[1].operator == 'IS':
+            #     lhs, rhs = relation.children
+            #     #assert isinstance(lhs, VariableRef), lhs
+            #     #assert isinstance(rhs.children[0], Constant)
+            #     #assert rhs.operator == 'IS', rhs.operator
+            #     #assert rhs.children[0].type == None
+        else:
+            state.add_var_info(lhsvar, VAR_HAS_REL)
+            rtype = relation.r_type
+            try:
+                rschema = self.schema.rschema(rtype)
+            except KeyError:
+                state.error('unknown relation `%s`' % rtype)
+            else:
+                if rschema.final and relation.optional not in (None, 'right'):
+                     state.error("optional may only be set on the rhs on final relation `%s`"
+                                 % relation.r_type)
+                if self.special_relations.get(rtype) == 'uid' and relation.operator() == '=':
+                    if state.var_info.get(lhsvar, 0) & VAR_HAS_UID_REL:
+                        state.error('can only one uid restriction per variable '
+                                    '(use IN for %s if desired)' % lhsvar.name)
+                    else:
+                        state.add_var_info(lhsvar, VAR_HAS_UID_REL)
+
+            for vref in relation.children[1].get_nodes(VariableRef):
+                state.add_var_info(vref.variable, VAR_HAS_REL)
+        try:
+            vargraph = relation.stmt.vargraph
+            rhsvarname = relation.children[1].children[0].variable.name
+        except AttributeError:
+            pass
+        else:
+            vargraph.setdefault(lhsvar.name, []).append(rhsvarname)
+            vargraph.setdefault(rhsvarname, []).append(lhsvar.name)
+            vargraph[(lhsvar.name, rhsvarname)] = relation.r_type
+
+    def leave_relation(self, relation, state):
+        pass
+        #assert isinstance(lhs, VariableRef), '%s: %s' % (lhs.__class__,
+        #                                                       relation)
+
+    def visit_comparison(self, comparison, state):
+        pass #assert len(comparison.children) in (1,2), len(comparison.children)
+    def leave_comparison(self, node, state):
+        pass
+
+    def visit_mathexpression(self, mathexpr, state):
+        pass #assert len(mathexpr.children) == 2, len(mathexpr.children)
+    def leave_mathexpression(self, node, state):
+        pass
+    def visit_unaryexpression(self, unaryexpr, state):
+        pass #assert len(unaryexpr.children) == 2, len(unaryexpr.children)
+    def leave_unaryexpression(self, node, state):
+        pass
+
+    def visit_function(self, function, state):
+        try:
+            funcdescr = function_description(function.name)
+        except UnknownFunction:
+            state.error('unknown function "%s"' % function.name)
+        else:
+            try:
+                funcdescr.check_nbargs(len(function.children))
+            except BadRQLQuery as ex:
+                state.error(str(ex))
+            if self.backend is not None:
+                try:
+                    funcdescr.st_check_backend(self.backend, function)
+                except BadRQLQuery as ex:
+                    state.error(str(ex))
+            if funcdescr.aggregat:
+                if isinstance(function.children[0], Function) and \
+                       function.children[0].descr().aggregat:
+                    state.error('can\'t nest aggregat functions')
+            if funcdescr.name == 'IN':
+                #assert function.parent.operator == '='
+                if len(function.children) == 1:
+                    function.parent.append(function.children[0])
+                    function.parent.remove(function)
+                #else:
+                #    assert len(function.children) >= 1
+
+    def leave_function(self, node, state):
+        pass
+
+    def visit_variableref(self, variableref, state):
+        #assert len(variableref.children)==0
+        #assert not variableref.parent is variableref
+##         try:
+##             assert variableref.variable in variableref.root().defined_vars.values(), \
+##                    (variableref.root(), variableref.variable, variableref.root().defined_vars)
+##         except AttributeError:
+##             raise Exception((variableref.root(), variableref.variable))
+        pass
+
+    def leave_variableref(self, node, state):
+        pass
+
+    def visit_constant(self, constant, state):
+        if constant.type != 'etype':
+            return
+        if constant.value not in self.schema:
+            state.error('unknown entity type %s' % constant.value)
+        if (isinstance(constant.parent, Function) and
+            constant.parent.name == 'CAST'):
+            return
+        rel = constant.relation()
+        if rel is not None and rel.r_type in ('is', 'is_instance_of'):
+            return
+        state.error('Entity types can only be used inside a CAST() '
+                    'or with "is" relation')
+
+    def leave_constant(self, node, state):
+        pass
+
+
+class RQLSTAnnotator(object):
+    """Annotate RQL syntax tree to ease further code generation from it.
+
+    If an optional variable is shared among multiple scopes, it's rewritten to
+    use identity relation.
+    """
+
+    def __init__(self, schema, special_relations=None):
+        self.schema = schema
+        self.special_relations = special_relations or {}
+
+    def annotate(self, node):
+        #assert not node.annotated
+        node.accept(self)
+        node.annotated = True
+
+    def _visit_stmt(self, node):
+        for var in node.defined_vars.values():
+            var.prepare_annotation()
+        for i, term in enumerate(node.selection):
+            for func in term.iget_nodes(Function):
+                if func.descr().aggregat:
+                    node.has_aggregat = True
+                    break
+            # register the selection column index
+            for vref in term.get_nodes(VariableRef):
+                vref.variable.stinfo['selected'].add(i)
+                vref.variable.set_scope(node)
+        if node.where is not None:
+            node.where.accept(self, node)
+
+    visit_insert = visit_delete = visit_set = _visit_stmt
+
+    def visit_union(self, node):
+        for select in node.children:
+            self.visit_select(select)
+
+    def visit_select(self, node):
+        for var in node.aliases.values():
+            var.prepare_annotation()
+        if node.with_ is not None:
+            for subquery in node.with_:
+                self.visit_union(subquery.query)
+                subquery.query.schema = node.root.schema
+        node.has_aggregat = False
+        self._visit_stmt(node)
+        if node.having:
+            # if there is a having clause, bloc simplification of variables used in GROUPBY
+            for term in node.groupby:
+                for vref in term.get_nodes(VariableRef):
+                    bloc_simplification(vref.variable, term)
+            try:
+                vargraph = node.vargraph
+            except AttributeError:
+                vargraph = None
+            # XXX node.having is a list of size 1
+            assert len(node.having) == 1
+            for term in node.having[0].get_nodes(Comparison):
+                lhsvariables = set(vref.variable for vref in term.children[0].get_nodes(VariableRef))
+                rhsvariables = set(vref.variable for vref in term.children[1].get_nodes(VariableRef))
+                for var in lhsvariables | rhsvariables:
+                    var.stinfo.setdefault('having', []).append(term)
+                if vargraph is not None:
+                    for v1 in lhsvariables:
+                        v1 = v1.name
+                        for v2 in rhsvariables:
+                            v2 = v2.name
+                            if v1 != v2:
+                                vargraph.setdefault(v1, []).append(v2)
+                                vargraph.setdefault(v2, []).append(v1)
+                if term.optional in ('left', 'both'):
+                    for var in lhsvariables:
+                        if var.stinfo['attrvar'] is not None:
+                            optcomps = var.stinfo['attrvar'].stinfo.setdefault('optcomparisons', set())
+                            optcomps.add(term)
+                if