backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 11 Oct 2010 11:33:52 +0200
changeset 596 ea7ad74f8190
parent 588 ede0c8b7d83b (current diff)
parent 595 d0c3ec1701b9 (diff)
child 597 89bd260bd530
backport stable
--- a/base.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/base.py	Mon Oct 11 11:33:52 2010 +0200
@@ -140,9 +140,14 @@
         child.parent = self
 
     def remove(self, child):
-        """remove a child node"""
-        self.children.remove(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"""
@@ -155,7 +160,7 @@
         self.children.pop(i)
         self.children.insert(i, new_child)
         new_child.parent = self
-
+        return old_child, self, i
 
 class BinaryNode(Node):
     __slots__ = ()
@@ -169,8 +174,8 @@
 
     def remove(self, child):
         """Remove the child and replace this node with the other child."""
-        self.children.remove(child)
-        self.parent.replace(self, self.children[0])
+        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."""
--- a/nodes.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/nodes.py	Mon Oct 11 11:33:52 2010 +0200
@@ -105,6 +105,19 @@
     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 = iter(value).next()
+    return make_relation(var, rtype, (value, ctype), Constant, operator)
+
 
 class EditableMixIn(object):
     """mixin class to add edition functionalities to some nodes, eg root nodes
@@ -129,14 +142,17 @@
         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']:
-                node.stmt.undefine_variable(varref.variable)
+                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))
-        node.parent.remove(node)
+            self.undo_manager.add_operation(RemoveNodeOperation(node, parent, stmt, index))
 
     def add_restriction(self, relation):
         """add a restriction relation"""
@@ -160,18 +176,8 @@
 
         variable rtype = value
         """
-        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=operator)
-                infunc = rel.children[1].children[0]
-                for atype in sorted(value):
-                    infunc.append(Constant(atype, ctype))
-                return self.add_restriction(rel)
-            value = iter(value).next()
-        return self.add_restriction(make_relation(var, rtype, (value, ctype),
-                                                  Constant, operator))
+        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>'"""
@@ -279,6 +285,9 @@
     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)
@@ -334,6 +343,10 @@
         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):
@@ -913,13 +926,13 @@
             rtype = rel.r_type
             lhs, rhs = rel.get_variable_parts()
             # use getattr, may not be a variable ref (rewritten, constant...)
-            lhsvar = getattr(lhs, 'variable', None)
             rhsvar = getattr(rhs, 'variable', None)
             if mainindex is not None:
                 # relation to the main variable, stop searching
-                if mainindex in lhsvar.stinfo['selected']:
+                lhsvar = getattr(lhs, 'variable', None)
+                if lhsvar is not None and mainindex in lhsvar.stinfo['selected']:
                     return tr(rtype)
-                if mainindex in rhsvar.stinfo['selected']:
+                if rhsvar is not None and mainindex in rhsvar.stinfo['selected']:
                     if schema is not None and rschema.symmetric:
                         return tr(rtype)
                     return tr(rtype + '_object')
--- a/stcheck.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/stcheck.py	Mon Oct 11 11:33:52 2010 +0200
@@ -315,8 +315,7 @@
         # NOT normalization
         child = not_.children[0]
         if self._should_wrap_by_exists(child):
-            not_.remove(child)
-            not_.append(Exists(child))
+            not_.replace(child, Exists(child))
 
     def _should_wrap_by_exists(self, child):
         if isinstance(child, Exists):
--- a/stmts.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/stmts.py	Mon Oct 11 11:33:52 2010 +0200
@@ -101,10 +101,7 @@
             self._varmaker = rqlvar_maker(defined=self.defined_vars,
                                           # XXX only on Select node
                                           aliases=getattr(self, 'aliases', None))
-        name =  self._varmaker.next()
-        while name in self.defined_vars:
-            name =  self._varmaker.next()
-        return name
+        return self._varmaker.next()
 
     def make_variable(self):
         """create a new variable with an unique name for this tree"""
@@ -146,6 +143,7 @@
             raise
         return True
 
+
 class Statement(object):
     """base class for statement nodes"""
 
@@ -702,6 +700,7 @@
         # XXX resetting oldnode parent cause pb with cw.test_views (w/ facets)
         #oldnode.parent = None
         newnode.parent = self
+        return oldnode, self, None
 
     def remove(self, node):
         if node is self.where:
@@ -716,6 +715,7 @@
         else:
             raise Exception('duh XXX')
         node.parent = None
+        return node, self, None
 
     def undefine_variable(self, var):
         """undefine the given variable and remove all relations where it appears"""
@@ -736,7 +736,10 @@
         # effective undefine operation
         if self.should_register_op:
             from rql.undo import UndefineVarOperation
-            self.undo_manager.add_operation(UndefineVarOperation(var))
+            solutions = [d.copy() for d in self.solutions]
+            self.undo_manager.add_operation(UndefineVarOperation(var, self, solutions))
+        for sol in self.solutions:
+            sol.pop(var.name, None)
         del self.defined_vars[var.name]
 
     def _var_index(self, var):
--- a/test/unittest_analyze.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/test/unittest_analyze.py	Mon Oct 11 11:33:52 2010 +0200
@@ -311,19 +311,19 @@
     def test_uid_func_mapping(self):
         h = self.helper
         def type_from_uid(name):
-            self.assertEquals(name, "Logilab")
+            self.assertEqual(name, "Logilab")
             return 'Company'
         uid_func_mapping = {'name': type_from_uid}
         # constant as rhs of the uid relation
         node = h.parse('Any X WHERE X name "Logilab"')
         h.compute_solutions(node, uid_func_mapping, debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, [{'X': 'Company'}])
+        self.assertEqual(sols, [{'X': 'Company'}])
         # variable as rhs of the uid relation
         node = h.parse('Any N WHERE X name N')
         h.compute_solutions(node, uid_func_mapping, debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, [{'X': 'Company', 'N': 'String'},
+        self.assertEqual(sols, [{'X': 'Company', 'N': 'String'},
                                  {'X': 'Person', 'N': 'String'},
                                  {'X': 'Student', 'N': 'String'}])
         # substitute as rhs of the uid relation
@@ -331,19 +331,19 @@
         h.compute_solutions(node, uid_func_mapping, {'company': 'Logilab'},
                         debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, [{'X': 'Company'}])
+        self.assertEqual(sols, [{'X': 'Company'}])
 
     def test_non_regr_subjobj1(self):
         h = self.helper
         def type_from_uid(name):
-            self.assertEquals(name, "Societe")
+            self.assertEqual(name, "Societe")
             return 'Eetype'
         uid_func_mapping = {'name': type_from_uid}
         # constant as rhs of the uid relation
         node = h.parse('Any X WHERE X name "Societe", X is ISOBJ, ISSIBJ is X')
         h.compute_solutions(node, uid_func_mapping, debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, [{'X': 'Eetype', 'ISOBJ': 'Eetype', 'ISSIBJ': 'Address'},
+        self.assertEqual(sols, [{'X': 'Eetype', 'ISOBJ': 'Eetype', 'ISSIBJ': 'Address'},
                                  {'X': 'Eetype', 'ISOBJ': 'Eetype', 'ISSIBJ': 'Company'},
                                  {'X': 'Eetype', 'ISOBJ': 'Eetype', 'ISSIBJ': 'Eetype'},
                                  {'X': 'Eetype', 'ISOBJ': 'Eetype', 'ISSIBJ': 'Person'},
@@ -352,46 +352,46 @@
     def test_non_regr_subjobj2(self):
         h = self.helper
         def type_from_uid(name):
-            self.assertEquals(name, "Societe")
+            self.assertEqual(name, "Societe")
             return 'Eetype'
         uid_func_mapping = {'name': type_from_uid}
         node = h.parse('Any X WHERE X name "Societe", X is ISOBJ, ISSUBJ is X, X is_instance_of ISIOOBJ, ISIOSUBJ is_instance_of X')
         h.compute_solutions(node, uid_func_mapping, debug=DEBUG)
         select = node.children[0]
         sols = sorted(select.solutions)
-        self.assertEquals(len(sols), 25)
+        self.assertEqual(len(sols), 25)
         def var_sols(var):
             s = set()
             for sol in sols:
                 s.add(sol.get(var))
             return s
-        self.assertEquals(var_sols('X'), set(('Eetype',)))
-        self.assertEquals(var_sols('X'), select.defined_vars['X'].stinfo['possibletypes'])
-        self.assertEquals(var_sols('ISSUBJ'), set(('Address', 'Company', 'Eetype', 'Person', 'Student')))
-        self.assertEquals(var_sols('ISSUBJ'), select.defined_vars['ISSUBJ'].stinfo['possibletypes'])
-        self.assertEquals(var_sols('ISOBJ'), set(('Eetype',)))
-        self.assertEquals(var_sols('ISOBJ'), select.defined_vars['ISOBJ'].stinfo['possibletypes'])
-        self.assertEquals(var_sols('ISIOSUBJ'), set(('Address', 'Company', 'Eetype', 'Person', 'Student')))
-        self.assertEquals(var_sols('ISIOSUBJ'), select.defined_vars['ISIOSUBJ'].stinfo['possibletypes'])
-        self.assertEquals(var_sols('ISIOOBJ'), set(('Eetype',)))
-        self.assertEquals(var_sols('ISIOOBJ'), select.defined_vars['ISIOOBJ'].stinfo['possibletypes'])
+        self.assertEqual(var_sols('X'), set(('Eetype',)))
+        self.assertEqual(var_sols('X'), select.defined_vars['X'].stinfo['possibletypes'])
+        self.assertEqual(var_sols('ISSUBJ'), set(('Address', 'Company', 'Eetype', 'Person', 'Student')))
+        self.assertEqual(var_sols('ISSUBJ'), select.defined_vars['ISSUBJ'].stinfo['possibletypes'])
+        self.assertEqual(var_sols('ISOBJ'), set(('Eetype',)))
+        self.assertEqual(var_sols('ISOBJ'), select.defined_vars['ISOBJ'].stinfo['possibletypes'])
+        self.assertEqual(var_sols('ISIOSUBJ'), set(('Address', 'Company', 'Eetype', 'Person', 'Student')))
+        self.assertEqual(var_sols('ISIOSUBJ'), select.defined_vars['ISIOSUBJ'].stinfo['possibletypes'])
+        self.assertEqual(var_sols('ISIOOBJ'), set(('Eetype',)))
+        self.assertEqual(var_sols('ISIOOBJ'), select.defined_vars['ISIOOBJ'].stinfo['possibletypes'])
 
     def test_unusableuid_func_mapping(self):
         h = self.helper
         def type_from_uid(name):
-            self.assertEquals(name, "Logilab")
+            self.assertEqual(name, "Logilab")
             return 'Company'
         uid_func_mapping = {'name': type_from_uid}
         node = h.parse('Any X WHERE NOT X name %(company)s')
         h.compute_solutions(node, uid_func_mapping, {'company': 'Logilab'},
                             debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, ALL_SOLS)
+        self.assertEqual(sols, ALL_SOLS)
         node = h.parse('Any X WHERE X name > %(company)s')
         h.compute_solutions(node, uid_func_mapping, {'company': 'Logilab'},
                             debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, ALL_SOLS)
+        self.assertEqual(sols, ALL_SOLS)
 
 
     def test_base_guess_3(self):
@@ -500,7 +500,7 @@
                          [{'X': 'Person', 'F': 'String'}])
         # auto-simplification
         self.assertEqual(len(node.children[0].with_[0].query.children), 1)
-        self.assertEquals(node.as_string(), 'Any L,Y,F WHERE Y located L, Y is Person WITH Y,F BEING (Any X,F WHERE X is Person, X firstname F)')
+        self.assertEqual(node.as_string(), 'Any L,Y,F WHERE Y located L, Y is Person WITH Y,F BEING (Any X,F WHERE X is Person, X firstname F)')
         self.assertEqual(node.children[0].with_[0].query.children[0].solutions,
                          [{'X': 'Person', 'F': 'String'}])
 
--- a/test/unittest_compare.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/test/unittest_compare.py	Mon Oct 11 11:33:52 2010 +0200
@@ -39,11 +39,11 @@
 
     def setUp(self):
         self.h = RQLHelper(DummySchema(), None)
-        self.skip('broken')
+        self.skipTest('broken')
 
     def _compareEquivalent(self,r1,r2):
         """fails if the RQL strings r1 and r2 are equivalent"""
-        self.skip('broken')
+        self.skipTest('broken')
         self.failUnless(self.h.compare(r1, r2),
                         'r1: %s\nr2: %s' % (r1, r2))
 
--- a/test/unittest_editextensions.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/test/unittest_editextensions.py	Mon Oct 11 11:33:52 2010 +0200
@@ -32,12 +32,12 @@
         select.remove_selected(select.selection[0])
         select.add_selected(var)
         # check operations
-        self.assertEquals(rqlst.as_string(), 'Any %s WHERE X is Person' % var.name)
+        self.assertEqual(rqlst.as_string(), 'Any %s WHERE X is Person' % var.name)
         # check references before recovering
         rqlst.check_references()
         rqlst.recover()
         # check equivalence after recovering
-        self.assertEquals(rqlst.as_string(), orig)
+        self.assertEqual(rqlst.as_string(), orig)
         # check references after recovering
         rqlst.check_references()
     
@@ -50,12 +50,12 @@
         select.remove_selected(select.selection[0])
         select.add_selected(var)
         # check operations
-        self.assertEquals(rqlst.as_string(), 'Any %s WHERE X is Person, X name N' % var.name)
+        self.assertEqual(rqlst.as_string(), 'Any %s WHERE X is Person, X name N' % var.name)
         # check references before recovering
         rqlst.check_references()
         rqlst.recover()
         # check equivalence after recovering
-        self.assertEquals(rqlst.as_string(), orig)
+        self.assertEqual(rqlst.as_string(), orig)
         # check references after recovering
         rqlst.check_references()
         
@@ -65,12 +65,12 @@
         rqlst.save_state()
         rqlst.children[0].undefine_variable(rqlst.children[0].defined_vars['Y'])
         # check operations
-        self.assertEquals(rqlst.as_string(), 'Any X WHERE X is Person')
+        self.assertEqual(rqlst.as_string(), 'Any X WHERE X is Person')
         # check references before recovering
         rqlst.check_references()
         rqlst.recover()
         # check equivalence
-        self.assertEquals(rqlst.as_string(), orig)
+        self.assertEqual(rqlst.as_string(), orig)
         # check references after recovering
         rqlst.check_references()
         
@@ -82,12 +82,12 @@
         var = rqlst.children[0].make_variable()
         rqlst.children[0].add_selected(var)
         # check operations
-        self.assertEquals(rqlst.as_string(), 'Any A')
+        self.assertEqual(rqlst.as_string(), 'Any A')
         # check references before recovering
         rqlst.check_references()
         rqlst.recover()
         # check equivalence
-        self.assertEquals(rqlst.as_string(), orig)
+        self.assertEqual(rqlst.as_string(), orig)
         # check references after recovering
         rqlst.check_references()
         
--- a/test/unittest_nodes.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/test/unittest_nodes.py	Mon Oct 11 11:33:52 2010 +0200
@@ -36,23 +36,23 @@
 
 class EtypeFromPyobjTC(TestCase):
     def test_bool(self):
-        self.assertEquals(nodes.etype_from_pyobj(True), 'Boolean')
-        self.assertEquals(nodes.etype_from_pyobj(False), 'Boolean')
+        self.assertEqual(nodes.etype_from_pyobj(True), 'Boolean')
+        self.assertEqual(nodes.etype_from_pyobj(False), 'Boolean')
 
     def test_int(self):
-        self.assertEquals(nodes.etype_from_pyobj(0), 'Int')
-        self.assertEquals(nodes.etype_from_pyobj(1L), 'Int')
+        self.assertEqual(nodes.etype_from_pyobj(0), 'Int')
+        self.assertEqual(nodes.etype_from_pyobj(1L), 'Int')
 
     def test_float(self):
-        self.assertEquals(nodes.etype_from_pyobj(0.), 'Float')
+        self.assertEqual(nodes.etype_from_pyobj(0.), 'Float')
 
     def test_datetime(self):
-        self.assertEquals(nodes.etype_from_pyobj(datetime.now()), 'Datetime')
-        self.assertEquals(nodes.etype_from_pyobj(date.today()), 'Date')
+        self.assertEqual(nodes.etype_from_pyobj(datetime.now()), 'Datetime')
+        self.assertEqual(nodes.etype_from_pyobj(date.today()), 'Date')
 
     def test_string(self):
-        self.assertEquals(nodes.etype_from_pyobj('hop'), 'String')
-        self.assertEquals(nodes.etype_from_pyobj(u'hop'), 'String')
+        self.assertEqual(nodes.etype_from_pyobj('hop'), 'String')
+        self.assertEqual(nodes.etype_from_pyobj(u'hop'), 'String')
 
 
 class NodesTest(TestCase):
@@ -61,11 +61,11 @@
         tree.check_references()
         if normrql is None:
             normrql = rql
-        self.assertEquals(tree.as_string(), normrql)
+        self.assertEqual(tree.as_string(), normrql)
         # just check repr() doesn't raise an exception
         repr(tree)
         copy = tree.copy()
-        self.assertEquals(copy.as_string(), normrql)
+        self.assertEqual(copy.as_string(), normrql)
         copy.check_references()
         return tree
 
@@ -77,9 +77,9 @@
         #del d1['parent']; del d1['children'] # parent and children are slots now
         #d2 = tree2.__dict__.copy()
         #del d2['parent']; del d2['children']
-        self.assertNotEquals(id(tree1), id(tree2))
+        self.assertNotEqual(id(tree1), id(tree2))
         self.assert_(tree1.is_equivalent(tree2))
-        #self.assertEquals(len(tree1.children), len(tree2.children))
+        #self.assertEqual(len(tree1.children), len(tree2.children))
         #for i in range(len(tree1.children)):
         #    self.check_equal_but_not_same(tree1.children[i], tree2.children[i])
 
@@ -93,22 +93,22 @@
         self.assertRaises(BadRQLQuery, tree.set_limit, '1')
         tree.save_state()
         tree.set_limit(10)
-        self.assertEquals(select.limit, 10)
-        self.assertEquals(tree.as_string(), 'Any X LIMIT 10 WHERE X is Person')
+        self.assertEqual(select.limit, 10)
+        self.assertEqual(tree.as_string(), 'Any X LIMIT 10 WHERE X is Person')
         tree.recover()
-        self.assertEquals(select.limit, None)
-        self.assertEquals(tree.as_string(), 'Any X WHERE X is Person')
+        self.assertEqual(select.limit, None)
+        self.assertEqual(tree.as_string(), 'Any X WHERE X is Person')
 
     def test_union_set_limit_2(self):
         # not undoable set_limit since a new root has to be introduced
         tree = self._parse("(Any X WHERE X is Person) UNION (Any X WHERE X is Company)")
         tree.save_state()
         tree.set_limit(10)
-        self.assertEquals(tree.as_string(), 'Any A LIMIT 10 WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
+        self.assertEqual(tree.as_string(), 'Any A LIMIT 10 WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
         select = tree.children[0]
-        self.assertEquals(select.limit, 10)
+        self.assertEqual(select.limit, 10)
         tree.recover()
-        self.assertEquals(tree.as_string(), '(Any X WHERE X is Person) UNION (Any X WHERE X is Company)')
+        self.assertEqual(tree.as_string(), '(Any X WHERE X is Person) UNION (Any X WHERE X is Company)')
 
     def test_union_set_offset_1(self):
         tree = self._parse("Any X WHERE X is Person")
@@ -117,10 +117,10 @@
         self.assertRaises(BadRQLQuery, tree.set_offset, '1')
         tree.save_state()
         tree.set_offset(10)
-        self.assertEquals(select.offset, 10)
+        self.assertEqual(select.offset, 10)
         tree.recover()
-        self.assertEquals(select.offset, 0)
-        self.assertEquals(tree.as_string(), 'Any X WHERE X is Person')
+        self.assertEqual(select.offset, 0)
+        self.assertEqual(tree.as_string(), 'Any X WHERE X is Person')
 
     def test_union_set_offset_2(self):
         # not undoable set_offset since a new root has to be introduced
@@ -128,10 +128,10 @@
         tree.save_state()
         tree.set_offset(10)
         select = tree.children[0]
-        self.assertEquals(tree.as_string(), 'Any A OFFSET 10 WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
-        self.assertEquals(select.offset, 10)
+        self.assertEqual(tree.as_string(), 'Any A OFFSET 10 WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
+        self.assertEqual(select.offset, 10)
         tree.recover()
-        self.assertEquals(tree.as_string(), '(Any X WHERE X is Person) UNION (Any X WHERE X is Company)')
+        self.assertEqual(tree.as_string(), '(Any X WHERE X is Person) UNION (Any X WHERE X is Company)')
 
     def test_union_undo_add_rel(self):
         tree = self._parse("Any A WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))")
@@ -140,34 +140,34 @@
         var = select.make_variable()
         mainvar = select.selection[0].variable
         select.add_relation(mainvar, 'name', var)
-        self.assertEquals(tree.as_string(), 'Any A WHERE A name B WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
+        self.assertEqual(tree.as_string(), 'Any A WHERE A name B WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
         tree.recover()
-        self.assertEquals(tree.as_string(), 'Any A WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
+        self.assertEqual(tree.as_string(), 'Any A WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
 
     def test_select_set_limit(self):
         tree = self._simpleparse("Any X WHERE X is Person")
-        self.assertEquals(tree.limit, None)
+        self.assertEqual(tree.limit, None)
         self.assertRaises(BadRQLQuery, tree.set_limit, 0)
         self.assertRaises(BadRQLQuery, tree.set_limit, -1)
         self.assertRaises(BadRQLQuery, tree.set_limit, '1')
         tree.save_state()
         tree.set_limit(10)
-        self.assertEquals(tree.limit, 10)
+        self.assertEqual(tree.limit, 10)
         tree.recover()
-        self.assertEquals(tree.limit, None)
+        self.assertEqual(tree.limit, None)
 
     def test_select_set_offset(self):
         tree = self._simpleparse("Any X WHERE X is Person")
         self.assertRaises(BadRQLQuery, tree.set_offset, -1)
         self.assertRaises(BadRQLQuery, tree.set_offset, '1')
-        self.assertEquals(tree.offset, 0)
+        self.assertEqual(tree.offset, 0)
         tree.save_state()
         tree.set_offset(0)
-        self.assertEquals(tree.offset, 0)
+        self.assertEqual(tree.offset, 0)
         tree.set_offset(10)
-        self.assertEquals(tree.offset, 10)
+        self.assertEqual(tree.offset, 10)
         tree.recover()
-        self.assertEquals(tree.offset, 0)
+        self.assertEqual(tree.offset, 0)
 
     def test_select_add_sort_var(self):
         tree = self._parse('Any X')
@@ -175,10 +175,10 @@
         select = tree.children[0]
         select.add_sort_var(select.get_variable('X'))
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X ORDERBY X')
+        self.assertEqual(tree.as_string(), 'Any X ORDERBY X')
         tree.recover()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X')
+        self.assertEqual(tree.as_string(), 'Any X')
 
     def test_select_remove_sort_terms(self):
         tree = self._parse('Any X,Y ORDERBY X,Y')
@@ -186,25 +186,40 @@
         select = tree.children[0]
         select.remove_sort_terms()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X,Y')
+        self.assertEqual(tree.as_string(), 'Any X,Y')
         tree.recover()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X,Y ORDERBY X,Y')
+        self.assertEqual(tree.as_string(), 'Any X,Y ORDERBY X,Y')
+
+    def test_select_undefine_variable(self):
+        tree = sparse('Any X,Y ORDERBY X,Y WHERE X work_for Y')
+        tree.save_state()
+        select = tree.children[0]
+        select.undefine_variable(select.defined_vars['Y'])
+        self.assertEqual(select.solutions, [{'X': 'Person'},
+                                             {'X': 'Student'}])
+        tree.check_references()
+        self.assertEqual(tree.as_string(), 'Any X ORDERBY X')
+        tree.recover()
+        tree.check_references()
+        self.assertEqual(tree.as_string(), 'Any X,Y ORDERBY X,Y WHERE X work_for Y')
+        self.assertEqual(select.solutions, [{'X': 'Person', 'Y': 'Company'},
+                                             {'X': 'Student', 'Y': 'Company'}])
 
     def test_select_set_distinct(self):
         tree = self._parse('DISTINCT Any X')
         tree.save_state()
         select = tree.children[0]
-        self.assertEquals(select.distinct, True)
+        self.assertEqual(select.distinct, True)
         tree.save_state()
         select.set_distinct(True)
-        self.assertEquals(select.distinct, True)
+        self.assertEqual(select.distinct, True)
         tree.recover()
-        self.assertEquals(select.distinct, True)
+        self.assertEqual(select.distinct, True)
         select.set_distinct(False)
-        self.assertEquals(select.distinct, False)
+        self.assertEqual(select.distinct, False)
         tree.recover()
-        self.assertEquals(select.distinct, True)
+        self.assertEqual(select.distinct, True)
 
     def test_select_add_group_var(self):
         tree = self._parse('Any X')
@@ -212,10 +227,10 @@
         select = tree.children[0]
         select.add_group_var(select.get_variable('X'))
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X GROUPBY X')
+        self.assertEqual(tree.as_string(), 'Any X GROUPBY X')
         tree.recover()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X')
+        self.assertEqual(tree.as_string(), 'Any X')
 
     def test_select_remove_group_var(self):
         tree = self._parse('Any X GROUPBY X')
@@ -223,10 +238,10 @@
         select = tree.children[0]
         select.remove_group_var(select.groupby[0])
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X')
+        self.assertEqual(tree.as_string(), 'Any X')
         tree.recover()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X GROUPBY X')
+        self.assertEqual(tree.as_string(), 'Any X GROUPBY X')
 
     def test_select_remove_groups(self):
         tree = self._parse('Any X,Y GROUPBY X,Y')
@@ -234,10 +249,10 @@
         select = tree.children[0]
         select.remove_groups()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X,Y')
+        self.assertEqual(tree.as_string(), 'Any X,Y')
         tree.recover()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X,Y GROUPBY X,Y')
+        self.assertEqual(tree.as_string(), 'Any X,Y GROUPBY X,Y')
 
     def test_select_base_1(self):
         tree = self._parse("Any X WHERE X is Person")
@@ -352,34 +367,34 @@
     def test_selected_index(self):
         tree = self._simpleparse("Any X ORDERBY N DESC WHERE X is Person, X name N")
         annotator.annotate(tree)
-        self.assertEquals(tree.defined_vars['X'].selected_index(), 0)
-        self.assertEquals(tree.defined_vars['N'].selected_index(), None)
+        self.assertEqual(tree.defined_vars['X'].selected_index(), 0)
+        self.assertEqual(tree.defined_vars['N'].selected_index(), None)
 
     def test_get_variable_indices_1(self):
         dummy = self._parse("Any A,B,C")
         dummy.children[0].solutions = [{'A': 'String', 'B': 'EUser', 'C': 'EGroup'},
                                        {'A': 'String', 'B': 'Personne', 'C': 'EGroup'},
                                        {'A': 'String', 'B': 'EUser', 'C': 'Societe'}]
-        self.assertEquals(dummy.get_variable_indices(), set([1, 2]))
+        self.assertEqual(dummy.get_variable_indices(), set([1, 2]))
 
     def test_get_variable_indices_2(self):
         dummy = self._parse("Any A,B WHERE B relation C")
         dummy.children[0].solutions = [{'A': 'String', 'B': 'EUser', 'C': 'EGroup'},
                                        {'A': 'String', 'B': 'Personne', 'C': 'EGroup'},
                                        {'A': 'String', 'B': 'EUser', 'C': 'Societe'}]
-        self.assertEquals(dummy.get_variable_indices(), set([1]))
+        self.assertEqual(dummy.get_variable_indices(), set([1]))
 
     def test_get_variable_indices_3(self):
         dummy = self._parse("(Any X WHERE X is EGroup) UNION (Any C WHERE C is EUser)")
         dummy.children[0].solutions = [{'X': 'EGroup'}]
         dummy.children[1].solutions = [{'C': 'EUser'}]
-        self.assertEquals(dummy.get_variable_indices(), set([0]))
+        self.assertEqual(dummy.get_variable_indices(), set([0]))
 
     def test_get_variable_indices_4(self):
         dummy = self._parse("(Any X,XN WHERE X is EGroup, X name XN) UNION (Any C,CL WHERE C is EUser, C login CL)")
         dummy.children[0].solutions = [{'X': 'EGroup', 'XN': 'String'}]
         dummy.children[1].solutions = [{'C': 'EUser', 'CL': 'String'}]
-        self.assertEquals(dummy.get_variable_indices(), set([0]))
+        self.assertEqual(dummy.get_variable_indices(), set([0]))
 
     # insertion tests #########################################################
 
@@ -458,40 +473,40 @@
 
     def test_as_string(self):
         tree = parse("SET X know Y WHERE X friend Y;")
-        self.assertEquals(tree.as_string(), 'SET X know Y WHERE X friend Y')
+        self.assertEqual(tree.as_string(), 'SET X know Y WHERE X friend Y')
 
         tree = parse("Person X")
-        self.assertEquals(tree.as_string(),
+        self.assertEqual(tree.as_string(),
                           'Any X WHERE X is Person')
 
         tree = parse(u"Any X WHERE X has_text 'héhé'")
-        self.assertEquals(tree.as_string('utf8'),
+        self.assertEqual(tree.as_string('utf8'),
                           u'Any X WHERE X has_text "héhé"'.encode('utf8'))
         tree = parse(u"Any X WHERE X has_text %(text)s")
-        self.assertEquals(tree.as_string('utf8', {'text': u'héhé'}),
+        self.assertEqual(tree.as_string('utf8', {'text': u'héhé'}),
                           u'Any X WHERE X has_text "héhé"'.encode('utf8'))
         tree = parse(u"Any X WHERE X has_text %(text)s")
-        self.assertEquals(tree.as_string('utf8', {'text': u'hé"hé'}),
+        self.assertEqual(tree.as_string('utf8', {'text': u'hé"hé'}),
                           u'Any X WHERE X has_text "hé\\"hé"'.encode('utf8'))
         tree = parse(u"Any X WHERE X has_text %(text)s")
-        self.assertEquals(tree.as_string('utf8', {'text': u'hé"\'hé'}),
+        self.assertEqual(tree.as_string('utf8', {'text': u'hé"\'hé'}),
                           u'Any X WHERE X has_text "hé\\"\'hé"'.encode('utf8'))
 
     def test_as_string_no_encoding(self):
         tree = parse(u"Any X WHERE X has_text 'héhé'")
-        self.assertEquals(tree.as_string(),
+        self.assertEqual(tree.as_string(),
                           u'Any X WHERE X has_text "héhé"')
         tree = parse(u"Any X WHERE X has_text %(text)s")
-        self.assertEquals(tree.as_string(kwargs={'text': u'héhé'}),
+        self.assertEqual(tree.as_string(kwargs={'text': u'héhé'}),
                           u'Any X WHERE X has_text "héhé"')
 
     def test_as_string_now_today_null(self):
         tree = parse(u"Any X WHERE X name NULL")
-        self.assertEquals(tree.as_string(), 'Any X WHERE X name NULL')
+        self.assertEqual(tree.as_string(), 'Any X WHERE X name NULL')
         tree = parse(u"Any X WHERE X creation_date NOW")
-        self.assertEquals(tree.as_string(), 'Any X WHERE X creation_date NOW')
+        self.assertEqual(tree.as_string(), 'Any X WHERE X creation_date NOW')
         tree = parse(u"Any X WHERE X creation_date TODAY")
-        self.assertEquals(tree.as_string(), 'Any X WHERE X creation_date TODAY')
+        self.assertEqual(tree.as_string(), 'Any X WHERE X creation_date TODAY')
 
     # sub-queries tests #######################################################
 
@@ -504,19 +519,19 @@
         select.recover()
         X = select.get_variable('X')
         N = select.get_variable('N')
-        self.assertEquals(len(X.references()), 3)
-        self.assertEquals(len(N.references()), 2)
+        self.assertEqual(len(X.references()), 3)
+        self.assertEqual(len(N.references()), 2)
         tree.schema = schema
         #annotator.annotate(tree)
         # XXX how to choose
-        self.assertEquals(X.get_type(), 'Company')
-        self.assertEquals(X.get_type({'X': 'Person'}), 'Person')
-        #self.assertEquals(N.get_type(), 'String')
-        self.assertEquals(X.get_description(0, lambda x:x), 'Company, Person, Student')
-        self.assertEquals(N.get_description(0, lambda x:x), 'firstname, name')
-        self.assertEquals(X.selected_index(), 0)
-        self.assertEquals(N.selected_index(), None)
-        self.assertEquals(X.main_relation(), None)
+        self.assertEqual(X.get_type(), 'Company')
+        self.assertEqual(X.get_type({'X': 'Person'}), 'Person')
+        #self.assertEqual(N.get_type(), 'String')
+        self.assertEqual(X.get_description(0, lambda x:x), 'Company, Person, Student')
+        self.assertEqual(N.get_description(0, lambda x:x), 'firstname, name')
+        self.assertEqual(X.selected_index(), 0)
+        self.assertEqual(N.selected_index(), None)
+        self.assertEqual(X.main_relation(), None)
 
     # non regression tests ####################################################
 
@@ -554,33 +569,33 @@
     def test_known_values_1(self):
         tree = parse('Any X where X name "turlututu"').children[0]
         constants = tree.get_nodes(nodes.Constant)
-        self.assertEquals(len(constants), 1)
-        self.assertEquals(isinstance(constants[0], nodes.Constant), 1)
-        self.assertEquals(constants[0].value, 'turlututu')
+        self.assertEqual(len(constants), 1)
+        self.assertEqual(isinstance(constants[0], nodes.Constant), 1)
+        self.assertEqual(constants[0].value, 'turlututu')
 
     def test_known_values_2(self):
         tree = parse('Any X where X name "turlututu", Y know X, Y name "chapo pointu"').children[0]
         varrefs = tree.get_nodes(nodes.VariableRef)
-        self.assertEquals(len(varrefs), 5)
+        self.assertEqual(len(varrefs), 5)
         for varref in varrefs:
             self.assertIsInstance(varref, nodes.VariableRef)
-        self.assertEquals(sorted(x.name for x in varrefs),
+        self.assertEqual(sorted(x.name for x in varrefs),
                           ['X', 'X', 'X', 'Y', 'Y'])
 
     def test_iknown_values_1(self):
         tree = parse('Any X where X name "turlututu"').children[0]
         constants = list(tree.iget_nodes(nodes.Constant))
-        self.assertEquals(len(constants), 1)
-        self.assertEquals(isinstance(constants[0], nodes.Constant), 1)
-        self.assertEquals(constants[0].value, 'turlututu')
+        self.assertEqual(len(constants), 1)
+        self.assertEqual(isinstance(constants[0], nodes.Constant), 1)
+        self.assertEqual(constants[0].value, 'turlututu')
 
     def test_iknown_values_2(self):
         tree = parse('Any X where X name "turlututu", Y know X, Y name "chapo pointu"').children[0]
         varrefs = list(tree.iget_nodes(nodes.VariableRef))
-        self.assertEquals(len(varrefs), 5)
+        self.assertEqual(len(varrefs), 5)
         for varref in varrefs:
             self.assertIsInstance(varref, nodes.VariableRef)
-        self.assertEquals(sorted(x.name for x in varrefs),
+        self.assertEqual(sorted(x.name for x in varrefs),
                           ['X', 'X', 'X', 'Y', 'Y'])
 
 if __name__ == '__main__':
--- a/test/unittest_parser.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/test/unittest_parser.py	Mon Oct 11 11:33:52 2010 +0200
@@ -149,6 +149,10 @@
     ' GROUPBY T2'
     ' WHERE T1 relation T2'
     ' HAVING 1+2 < COUNT(T1);',
+
+    'Any X,Y,A ORDERBY Y '
+    'WHERE A done_for Y, X split_into Y, A diem D '
+    'HAVING MIN(D) < "2010-07-01", MAX(D) >= "2010-07-01";',
     )
 
 class ParserHercule(TestCase):
@@ -175,7 +179,7 @@
         comparison = base.children[1]
         self.failUnless(isinstance(comparison, nodes.Comparison))
         rhs = comparison.children[0]
-        self.assertEquals(type(rhs.value), unicode)
+        self.assertEqual(type(rhs.value), unicode)
 
     def test_precedence_1(self):
         tree = self.parse("Any X WHERE X firstname 'lulu' AND X name 'toto' OR X name 'tutu';")
@@ -268,22 +272,22 @@
         tree = self.parse("Any X WHERE X firstname %(firstname)s;")
         cste = tree.children[0].where.children[1].children[0]
         self.assert_(isinstance(cste, nodes.Constant))
-        self.assertEquals(cste.type, 'Substitute')
-        self.assertEquals(cste.value, 'firstname')
+        self.assertEqual(cste.type, 'Substitute')
+        self.assertEqual(cste.value, 'firstname')
 
     def test_optional_relation(self):
         tree = self.parse(r'Any X WHERE X related Y;')
         related = tree.children[0].where
-        self.assertEquals(related.optional, None)
+        self.assertEqual(related.optional, None)
         tree = self.parse(r'Any X WHERE X? related Y;')
         related = tree.children[0].where
-        self.assertEquals(related.optional, 'left')
+        self.assertEqual(related.optional, 'left')
         tree = self.parse(r'Any X WHERE X related Y?;')
         related = tree.children[0].where
-        self.assertEquals(related.optional, 'right')
+        self.assertEqual(related.optional, 'right')
         tree = self.parse(r'Any X WHERE X? related Y?;')
         related = tree.children[0].where
-        self.assertEquals(related.optional, 'both')
+        self.assertEqual(related.optional, 'both')
 
     def test_exists(self):
         tree = self.parse("Any X WHERE X firstname 'lulu',"
@@ -297,9 +301,9 @@
 
     def test_etype(self):
         tree = self.parse('EmailAddress X;')
-        self.assertEquals(tree.as_string(), 'Any X WHERE X is EmailAddress')
+        self.assertEqual(tree.as_string(), 'Any X WHERE X is EmailAddress')
         tree = self.parse('EUser X;')
-        self.assertEquals(tree.as_string(), 'Any X WHERE X is EUser')
+        self.assertEqual(tree.as_string(), 'Any X WHERE X is EUser')
 
     def test_spec(self):
         """test all RQL string found in the specification and test they are well parsed"""
--- a/test/unittest_rqlgen.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/test/unittest_rqlgen.py	Mon Oct 11 11:33:52 2010 +0200
@@ -41,21 +41,21 @@
         """tests select with entity type only
         """
         rql = self.rql_generator.select('Person')
-        self.assertEquals(rql, 'Person X')
+        self.assertEqual(rql, 'Person X')
         
 
     def test_select_group(self):
         """tests select with group
         """
         rql = self.rql_generator.select('Person', groups=('X',))
-        self.assertEquals(rql, 'Person X\nGROUPBY X')
+        self.assertEqual(rql, 'Person X\nGROUPBY X')
 
 
     def test_select_sort(self):
         """tests select with sort
         """
         rql = self.rql_generator.select('Person', sorts=('X ASC',))
-        self.assertEquals(rql, 'Person X\nSORTBY X ASC')
+        self.assertEqual(rql, 'Person X\nSORTBY X ASC')
 
 
     def test_select(self):
@@ -68,7 +68,7 @@
                                           ('X','surname','S') ),
                                         ('X',),
                                         ('F ASC', 'S DESC'))
-        self.assertEquals(rql, 'Person X\nWHERE X work_for S , S name "Logilab"'
+        self.assertEqual(rql, 'Person X\nWHERE X work_for S , S name "Logilab"'
                           ' , X firstname F , X surname S\nGROUPBY X'
                           '\nSORTBY F ASC, S DESC')
                                         
@@ -80,7 +80,7 @@
                                          ('S','name','"Logilab"'),
                                          ('X','firstname','F'),
                                          ('X','surname','S') ) )
-        self.assertEquals(rql, 'WHERE X work_for S , S name "Logilab" '
+        self.assertEqual(rql, 'WHERE X work_for S , S name "Logilab" '
                           ', X firstname F , X surname S')
 
 
@@ -88,14 +88,14 @@
         """tests the groupby() method behaviour
         """
         rql = self.rql_generator.groupby(('F', 'S'))
-        self.assertEquals(rql, 'GROUPBY F, S')
+        self.assertEqual(rql, 'GROUPBY F, S')
         
 
     def test_sortby(self):
         """tests the sortby() method behaviour
         """
         rql = self.rql_generator.sortby(('F ASC', 'S DESC'))
-        self.assertEquals(rql, 'SORTBY F ASC, S DESC')
+        self.assertEqual(rql, 'SORTBY F ASC, S DESC')
         
 
     def test_insert(self):
@@ -103,7 +103,7 @@
         """
         rql = self.rql_generator.insert('Person', (('firstname', "Clark"),
                                                    ('lastname', "Kent")))
-        self.assertEquals(rql, 'INSERT Person X: X firstname "Clark",'
+        self.assertEqual(rql, 'INSERT Person X: X firstname "Clark",'
                           ' X lastname "Kent"')
         
         
@@ -115,7 +115,7 @@
                                          ('lastname', "Kent")),
                                         (('job', "superhero"),
                                          ('nick', "superman")))
-        self.assertEquals(rql, 'SET X job "superhero", X nick "superman" '
+        self.assertEqual(rql, 'SET X job "superhero", X nick "superman" '
                           'WHERE X is "Person", X firstname "Clark", X '
                           'lastname "Kent"')
 
@@ -126,7 +126,7 @@
         rql = self.rql_generator.delete('Person',
                                         (('firstname', "Clark"),
                                          ('lastname', "Kent")))
-        self.assertEquals(rql, 'DELETE Person X where X firstname "Clark", '
+        self.assertEqual(rql, 'DELETE Person X where X firstname "Clark", '
                           'X lastname "Kent"')
         
 if __name__ == '__main__':
--- a/test/unittest_stcheck.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/test/unittest_stcheck.py	Mon Oct 11 11:33:52 2010 +0200
@@ -109,7 +109,7 @@
     def _test_rewrite(self, rql, expected):
         rqlst = self.parse(rql)
         self.simplify(rqlst)
-        self.assertEquals(rqlst.as_string(), expected)
+        self.assertEqual(rqlst.as_string(), expected)
 
     def test_rewrite(self):
         for rql, expected in (
@@ -194,20 +194,20 @@
                             'VC work_for S, S name "draft" '
                             'WITH VF, VC, VCD BEING (Any VF, MAX(VC), VCD GROUPBY VF, VCD '
                             '                        WHERE VC connait VF, VC creation_date VCD)'))
-        self.assertEquals(rqlst.children[0].vargraph,
+        self.assertEqual(rqlst.children[0].vargraph,
                           {'VCD': ['VC'], 'VF': ['VC'], 'S': ['VC'], 'VC': ['S', 'VF', 'VCD'],
                            ('VC', 'S'): 'work_for',
                            ('VC', 'VF'): 'connait',
                            ('VC', 'VCD'): 'creation_date'})
-        self.assertEquals(rqlst.children[0].aggregated, set(('VC',)))
+        self.assertEqual(rqlst.children[0].aggregated, set(('VC',)))
 
 
 ##     def test_rewriten_as_string(self):
 ##         rqlst = self.parse('Any X WHERE X eid 12')
-##         self.assertEquals(rqlst.as_string(), 'Any X WHERE X eid 12')
+##         self.assertEqual(rqlst.as_string(), 'Any X WHERE X eid 12')
 ##         rqlst = rqlst.copy()
 ##         self.annotate(rqlst)
-##         self.assertEquals(rqlst.as_string(), 'Any X WHERE X eid 12')
+##         self.assertEqual(rqlst.as_string(), 'Any X WHERE X eid 12')
 
 class CopyTest(TestCase):
 
@@ -230,7 +230,7 @@
         root = self.parse('Any X,U WHERE C owned_by U, NOT X owned_by U, X eid 1, C eid 2')
         self.simplify(root)
         stmt = root.children[0]
-        self.assertEquals(stmt.defined_vars['U'].valuable_references(), 3)
+        self.assertEqual(stmt.defined_vars['U'].valuable_references(), 3)
         copy = stmts.Select()
         copy.append_selected(stmt.selection[0].copy(copy))
         copy.append_selected(stmt.selection[1].copy(copy))
@@ -239,8 +239,8 @@
         newroot.append(copy)
         self.annotate(newroot)
         self.simplify(newroot)
-        self.assertEquals(newroot.as_string(), 'Any 1,U WHERE 2 owned_by U, NOT EXISTS(1 owned_by U)')
-        self.assertEquals(copy.defined_vars['U'].valuable_references(), 3)
+        self.assertEqual(newroot.as_string(), 'Any 1,U WHERE 2 owned_by U, NOT EXISTS(1 owned_by U)')
+        self.assertEqual(copy.defined_vars['U'].valuable_references(), 3)
 
 
 class AnnotateTest(TestCase):
@@ -261,42 +261,42 @@
         rqlst = self.parse('Any X WHERE C is Company, EXISTS(X work_for C)').children[0]
         C = rqlst.defined_vars['C']
         self.failIf(C.scope is rqlst, C.scope)
-        self.assertEquals(len(C.stinfo['relations']), 1)
+        self.assertEqual(len(C.stinfo['relations']), 1)
 
     def test_is_rel_no_scope_2(self):
         rqlst = self.parse('Any X, ET WHERE C is ET, EXISTS(X work_for C)').children[0]
         C = rqlst.defined_vars['C']
         self.failUnless(C.scope is rqlst, C.scope)
-        self.assertEquals(len(C.stinfo['relations']), 2)
+        self.assertEqual(len(C.stinfo['relations']), 2)
 
 
     def test_not_rel_normalization_1(self):
         rqlst = self.parse('Any X WHERE C is Company, NOT X work_for C').children[0]
-        self.assertEquals(rqlst.as_string(), 'Any X WHERE C is Company, NOT EXISTS(X work_for C)')
+        self.assertEqual(rqlst.as_string(), 'Any X WHERE C is Company, NOT EXISTS(X work_for C)')
         C = rqlst.defined_vars['C']
         self.failIf(C.scope is rqlst, C.scope)
 
     def test_not_rel_normalization_2(self):
         rqlst = self.parse('Any X, ET WHERE C is ET, NOT X work_for C').children[0]
-        self.assertEquals(rqlst.as_string(), 'Any X,ET WHERE C is ET, NOT EXISTS(X work_for C)')
+        self.assertEqual(rqlst.as_string(), 'Any X,ET WHERE C is ET, NOT EXISTS(X work_for C)')
         C = rqlst.defined_vars['C']
         self.failUnless(C.scope is rqlst, C.scope)
 
     def test_not_rel_normalization_3(self):
         rqlst = self.parse('Any X WHERE C is Company, X work_for C, NOT C name "World Company"').children[0]
-        self.assertEquals(rqlst.as_string(), "Any X WHERE C is Company, X work_for C, NOT C name 'World Company'")
+        self.assertEqual(rqlst.as_string(), "Any X WHERE C is Company, X work_for C, NOT C name 'World Company'")
         C = rqlst.defined_vars['C']
         self.failUnless(C.scope is rqlst, C.scope)
 
     def test_not_rel_normalization_4(self):
         rqlst = self.parse('Any X WHERE C is Company, NOT (X work_for C, C name "World Company")').children[0]
-        self.assertEquals(rqlst.as_string(), "Any X WHERE C is Company, NOT EXISTS(X work_for C, C name 'World Company')")
+        self.assertEqual(rqlst.as_string(), "Any X WHERE C is Company, NOT EXISTS(X work_for C, C name 'World Company')")
         C = rqlst.defined_vars['C']
         self.failIf(C.scope is rqlst, C.scope)
 
     def test_not_rel_normalization_5(self):
         rqlst = self.parse('Any X WHERE X work_for C, EXISTS(C identity D, NOT Y work_for D, D name "World Company")').children[0]
-        self.assertEquals(rqlst.as_string(), "Any X WHERE X work_for C, EXISTS(C identity D, NOT EXISTS(Y work_for D), D name 'World Company')")
+        self.assertEqual(rqlst.as_string(), "Any X WHERE X work_for C, EXISTS(C identity D, NOT EXISTS(Y work_for D), D name 'World Company')")
         D = rqlst.defined_vars['D']
         self.failIf(D.scope is rqlst, D.scope)
         self.failUnless(D.scope.parent.scope is rqlst, D.scope.parent.scope)
@@ -305,13 +305,13 @@
         rqlst = self.parse('Any X WITH X BEING (Any X WHERE C is Company, EXISTS(X work_for C))').children[0]
         C = rqlst.with_[0].query.children[0].defined_vars['C']
         self.failIf(C.scope is rqlst, C.scope)
-        self.assertEquals(len(C.stinfo['relations']), 1)
+        self.assertEqual(len(C.stinfo['relations']), 1)
 
     def test_subquery_annotation_2(self):
         rqlst = self.parse('Any X,ET WITH X,ET BEING (Any X, ET WHERE C is ET, EXISTS(X work_for C))').children[0]
         C = rqlst.with_[0].query.children[0].defined_vars['C']
         self.failUnless(C.scope is rqlst.with_[0].query.children[0], C.scope)
-        self.assertEquals(len(C.stinfo['relations']), 2)
+        self.assertEqual(len(C.stinfo['relations']), 2)
 
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_utils.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/test/unittest_utils.py	Mon Oct 11 11:33:52 2010 +0200
@@ -55,19 +55,19 @@
 
     def test_rqlvar_maker(self):
         varlist = list(utils.rqlvar_maker(27))
-        self.assertEquals(varlist, list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') + ['AA'])
+        self.assertEqual(varlist, list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') + ['AA'])
         varlist = list(utils.rqlvar_maker(27*26+1))
-        self.assertEquals(varlist[-2], 'ZZ')
-        self.assertEquals(varlist[-1], 'AAA')
+        self.assertEqual(varlist[-2], 'ZZ')
+        self.assertEqual(varlist[-1], 'AAA')
 
     def test_rqlvar_maker_dontstop(self):
         varlist = utils.rqlvar_maker()
-        self.assertEquals(varlist.next(), 'A')
-        self.assertEquals(varlist.next(), 'B')
+        self.assertEqual(varlist.next(), 'A')
+        self.assertEqual(varlist.next(), 'B')
         for i in range(24):
             varlist.next()
-        self.assertEquals(varlist.next(), 'AA')
-        self.assertEquals(varlist.next(), 'AB')
+        self.assertEqual(varlist.next(), 'AA')
+        self.assertEqual(varlist.next(), 'AB')
 
         
 if __name__ == '__main__':
--- a/undo.py	Mon Sep 13 14:47:09 2010 +0200
+++ b/undo.py	Mon Oct 11 11:33:52 2010 +0200
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with rql. If not, see <http://www.gnu.org/licenses/>.
-"""Manages undos on RQL syntax trees.
+"""Manages undos on RQL syntax trees."""
 
-"""
 __docformat__ = "restructuredtext en"
 
 from rql.nodes import VariableRef, Variable, BinaryNode
@@ -61,9 +60,11 @@
 
 class NodeOperation(object):
     """Abstract class for node manipulation operations."""
-    def __init__(self, node):
+    def __init__(self, node, stmt=None):
         self.node = node
-        self.stmt = node.stmt
+        if stmt is None:
+            stmt = node.stmt
+        self.stmt = stmt
 
     def __str__(self):
         """undo the operation on the selection"""
@@ -73,18 +74,21 @@
 
 class MakeVarOperation(NodeOperation):
     """Defines how to undo make_variable()."""
-
     def undo(self, selection):
         """undo the operation on the selection"""
         self.stmt.undefine_variable(self.node)
 
 class UndefineVarOperation(NodeOperation):
     """Defines how to undo undefine_variable()."""
+    def __init__(self, node, stmt, solutions):
+        NodeOperation.__init__(self, node, stmt)
+        self.solutions = solutions
 
     def undo(self, selection):
         """undo the operation on the selection"""
         var = self.node
         self.stmt.defined_vars[var.name] = var
+        self.stmt.solutions = self.solutions
 
 class SelectVarOperation(NodeOperation):
     """Defines how to undo add_selected()."""
@@ -135,45 +139,36 @@
 class RemoveNodeOperation(NodeOperation):
     """Defines how to undo remove_node()."""
 
-    def __init__(self, node):
-        NodeOperation.__init__(self, node)
-        self.node_parent = node.parent
-        if isinstance(self.node_parent, Select):
-            assert self.node is self.node_parent.where
-        else:
-            self.index = node.parent.children.index(node)
+    def __init__(self, node, parent, stmt, index):
+        NodeOperation.__init__(self, node, stmt)
+        self.node_parent = parent
+        #if isinstance(parent, Select):
+        #    assert self.node is parent.where
+        self.index = index
         # XXX FIXME : find a better way to do that
-        # needed when removing a BinaryNode's child
-        self.binary_remove = isinstance(self.node_parent, BinaryNode)
-        if self.binary_remove:
-            self.gd_parent = self.node_parent.parent
-            if isinstance(self.gd_parent, Select):
-                assert self.node_parent is self.gd_parent.where
-            else:
-                self.parent_index = self.gd_parent.children.index(self.node_parent)
+        self.binary_remove = isinstance(node, BinaryNode)
 
     def undo(self, selection):
         """undo the operation on the selection"""
+        parent = self.node_parent
+        if self.index is None:
+            assert isinstance(parent, Select)
+            sibling = parent.where = self.node
+            parent.where = self.node
         if self.binary_remove:
             # if 'parent' was a BinaryNode, then first reinsert the removed node
             # at the same pos in the original 'parent' Binary Node, and then
             # reinsert this BinaryNode in its parent's children list
             # WARNING : the removed node sibling's parent is no longer the
             # 'node_parent'. We must Reparent it manually !
-            node_sibling = self.node_parent.children[0]
-            node_sibling.parent = self.node_parent
-            self.node_parent.insert(self.index, self.node)
-            if isinstance(self.gd_parent, Select):
-                self.gd_parent.where = self.node_parent
-            else:
-                self.gd_parent.children[self.parent_index] = self.node_parent
-                self.node_parent.parent = self.gd_parent
-        elif isinstance(self.node_parent, Select):
-            self.node_parent.where = self.node
-            self.node.parent = self.node_parent
-        else:
-            self.node_parent.insert(self.index, self.node)
+            if self.index is not None:
+                sibling = self.node_parent.children[self.index]
+                parent.children[self.index] = self.node
+            sibling.parent = self.node
+        elif self.index is not None:
+            parent.insert(self.index, self.node)
         # register reference from the removed node
+        self.node.parent = parent
         for varref in self.node.iget_nodes(VariableRef):
             varref.register_reference()