diff --git a/ChangeLog b/ChangeLog
index bc9c7885a7098e9aa9629dd834fc2c6670e80215_Q2hhbmdlTG9n..e154f58bb9d6ffe875aa8fda57e6ed85e579e642_Q2hhbmdlTG9n 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,11 +2,19 @@
 =================
 
 --
-* support != operator for non equality
-* support for CAST function
-* support for regexp-based pattern matching using a REGEXP operator
-* may now GROUPBY functions / column number
-* fix parsing of negative float
+    * remove_group_var renamed into remove_group_term and fixed implementation
+
+    * rql annotator add 'having' list into variable's stinfo, and
+      properly update variable graph
+
+    * new undo_modification context manager on select nodes
+
+2011-06-09  --  0.29.0
+    * support != operator for non equality
+    * support for CAST function
+    * support for regexp-based pattern matching using a REGEXP operator
+    * may now GROUPBY functions / column number
+    * fix parsing of negative float
 
 2011-01-12  --  0.28.0
     * enhance rewrite_shared_optional so one can specify where the new identity
diff --git a/nodes.py b/nodes.py
index bc9c7885a7098e9aa9629dd834fc2c6670e80215_bm9kZXMucHk=..e154f58bb9d6ffe875aa8fda57e6ed85e579e642_bm9kZXMucHk= 100644
--- a/nodes.py
+++ b/nodes.py
@@ -1,4 +1,4 @@
-# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# 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.
diff --git a/parser.g b/parser.g
index bc9c7885a7098e9aa9629dd834fc2c6670e80215_cGFyc2VyLmc=..e154f58bb9d6ffe875aa8fda57e6ed85e579e642_cGFyc2VyLmc= 100644
--- a/parser.g
+++ b/parser.g
@@ -1,7 +1,7 @@
 """yapps input grammar for RQL.
 
 :organization: Logilab
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 
 
diff --git a/stcheck.py b/stcheck.py
index bc9c7885a7098e9aa9629dd834fc2c6670e80215_c3RjaGVjay5weQ==..e154f58bb9d6ffe875aa8fda57e6ed85e579e642_c3RjaGVjay5weQ== 100644
--- a/stcheck.py
+++ b/stcheck.py
@@ -27,7 +27,7 @@
 from rql._exceptions import BadRQLQuery
 from rql.utils import function_description
 from rql.nodes import (Relation, VariableRef, Constant, Not, Exists, Function,
-                       And, Variable, variable_refs, make_relation)
+                       And, Variable, Comparison, variable_refs, make_relation)
 from rql.stmts import Union
 
 
@@ -521,6 +521,23 @@
             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):
+                for vref in term.iget_nodes(VariableRef):
+                    vref.variable.stinfo.setdefault('having', []).append(term)
+                if vargraph is not None:
+                    lhsvariables = set(vref.name for vref in term.children[0].get_nodes(VariableRef))
+                    rhsvariables = set(vref.name for vref in term.children[1].get_nodes(VariableRef))
+                    for v1 in lhsvariables:
+                        for v2 in rhsvariables:
+                            if v1 != v2:
+                                vargraph.setdefault(v1, []).append(v2)
+                                vargraph.setdefault(v2, []).append(v1)
 
     def rewrite_shared_optional(self, exists, var, identity_rel_scope=None):
         """if variable is shared across multiple scopes, need some tree
diff --git a/stmts.py b/stmts.py
index bc9c7885a7098e9aa9629dd834fc2c6670e80215_c3RtdHMucHk=..e154f58bb9d6ffe875aa8fda57e6ed85e579e642_c3RtdHMucHk= 100644
--- a/stmts.py
+++ b/stmts.py
@@ -1,4 +1,4 @@
-# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# 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.
@@ -27,6 +27,7 @@
 from warnings import warn
 
 from logilab.common.decorators import cached
+from logilab.common.deprecation import deprecated
 
 from rql import BadRQLQuery, CoercionError, nodes
 from rql.base import BaseNode, Node
@@ -47,6 +48,13 @@
             raise AssertionError('vref %r is not referenced (%r)' % (vref, vref.stmt))
     return True
 
+class undo_modification(object):
+    def __init__(self, select):
+        self.select = select
+    def __enter__(self):
+        self.select.save_state()
+    def __exit__(self):
+        self.select.recover()
 
 class ScopeNode(BaseNode):
     solutions = ()   # list of possibles solutions for used variables
@@ -354,6 +362,9 @@
     def should_register_op(self):
         return self.memorizing and not self.undoing
 
+    def undo_modification(self):
+        return undo_modification(self)
+
     def save_state(self):
         """save the current tree"""
         self.undo_manager.push_state()
@@ -708,7 +719,7 @@
         elif node in self.orderby:
             self.remove_sort_term(node)
         elif node in self.groupby:
-            self.remove_group_var(node)
+            self.remove_group_term(node)
         elif node in self.having:
             self.having.remove(node)
         # XXX selection
@@ -730,7 +741,7 @@
             elif isinstance(vref.parent, nodes.SortTerm):
                 self.remove_sort_term(vref.parent)
             elif vref in self.groupby:
-                self.remove_group_var(vref)
+                self.remove_group_term(vref)
             else: # selected variable
                 self.remove_selected(vref)
         # effective undefine operation
@@ -796,7 +807,7 @@
             from rql.undo import AddGroupOperation
             self.undo_manager.add_operation(AddGroupOperation(vref))
 
-    def remove_group_var(self, vref):
+    def remove_group_term(self, term):
         """remove the group variable and the group node if necessary"""
         if self.should_register_op:
             from rql.undo import RemoveGroupOperation
@@ -800,9 +811,11 @@
         """remove the group variable and the group node if necessary"""
         if self.should_register_op:
             from rql.undo import RemoveGroupOperation
-            self.undo_manager.add_operation(RemoveGroupOperation(vref))
-        vref.unregister_reference()
-        self.groupby.remove(vref)
+            self.undo_manager.add_operation(RemoveGroupOperation(term))
+        for vref in term.iget_nodes(nodes.VariableRef):
+            vref.unregister_reference()
+        self.groupby.remove(term)
+    remove_group_var = deprecated('[rql 0.29] use remove_group_term instead')(remove_group_term)
 
     def remove_groups(self):
         for vref in self.groupby[:]:
@@ -806,7 +819,7 @@
 
     def remove_groups(self):
         for vref in self.groupby[:]:
-            self.remove_group_var(vref)
+            self.remove_group_term(vref)
 
     def add_sort_var(self, var, asc=True):
         """add var in 'orderby' constraints
diff --git a/test/unittest_nodes.py b/test/unittest_nodes.py
index bc9c7885a7098e9aa9629dd834fc2c6670e80215_dGVzdC91bml0dGVzdF9ub2Rlcy5weQ==..e154f58bb9d6ffe875aa8fda57e6ed85e579e642_dGVzdC91bml0dGVzdF9ub2Rlcy5weQ== 100644
--- a/test/unittest_nodes.py
+++ b/test/unittest_nodes.py
@@ -1,5 +1,5 @@
 # -*- coding: iso-8859-1 -*-
-# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# 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.
@@ -232,7 +232,7 @@
         tree.check_references()
         self.assertEqual(tree.as_string(), 'Any X')
 
-    def test_select_remove_group_var(self):
+    def test_select_remove_group_term(self):
         tree = self._parse('Any X GROUPBY X')
         tree.save_state()
         select = tree.children[0]
@@ -236,7 +236,7 @@
         tree = self._parse('Any X GROUPBY X')
         tree.save_state()
         select = tree.children[0]
-        select.remove_group_var(select.groupby[0])
+        select.remove_group_term(select.groupby[0])
         tree.check_references()
         self.assertEqual(tree.as_string(), 'Any X')
         tree.recover()
diff --git a/test/unittest_parser.py b/test/unittest_parser.py
index bc9c7885a7098e9aa9629dd834fc2c6670e80215_dGVzdC91bml0dGVzdF9wYXJzZXIucHk=..e154f58bb9d6ffe875aa8fda57e6ed85e579e642_dGVzdC91bml0dGVzdF9wYXJzZXIucHk= 100644
--- a/test/unittest_parser.py
+++ b/test/unittest_parser.py
@@ -1,5 +1,5 @@
 # -*- coding: iso-8859-1 -*-
-# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# 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.
diff --git a/undo.py b/undo.py
index bc9c7885a7098e9aa9629dd834fc2c6670e80215_dW5kby5weQ==..e154f58bb9d6ffe875aa8fda57e6ed85e579e642_dW5kby5weQ== 100644
--- a/undo.py
+++ b/undo.py
@@ -196,7 +196,7 @@
 
     def undo(self, selection):
         """undo the operation on the selection"""
-        self.stmt.remove_group_var(self.node)
+        self.stmt.remove_group_term(self.node)
 
 class RemoveGroupOperation(NodeOperation):
     """Defines how to undo 'remove group'."""