[rql] HAVING support in write queries (INSERT,SET,DELETE). Closes #81394
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 24 Oct 2011 17:49:58 +0200
changeset 668 b2e231cbd9da
parent 667 4a0bbee051e0
child 669 dae50737d5fb
[rql] HAVING support in write queries (INSERT,SET,DELETE). Closes #81394
nodes.py
parser.g
parser.py
stmts.py
--- a/nodes.py	Fri Oct 21 17:47:29 2011 +0200
+++ b/nodes.py	Mon Oct 24 17:49:58 2011 +0200
@@ -997,12 +997,17 @@
             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']:
-                    return tr(rtype)
+                    if len(lhsvar.stinfo['possibletypes']) == 1:
+                        context = iter(lhsvar.stinfo['possibletypes']).next()
+                    return tr(rtype, context=context)
                 if rhsvar is not None and mainindex in rhsvar.stinfo['selected']:
+                    if len(rhsvar.stinfo['possibletypes']) == 1:
+                        context = iter(rhsvar.stinfo['possibletypes']).next()
                     if schema is not None and rschema.symmetric:
-                        return tr(rtype)
-                    return tr(rtype + '_object')
+                        return tr(rtype, context=context)
+                    return tr(rtype + '_object', context=context)
             if rhsvar is self:
                 rtype += '_object'
         if frtype is not None:
--- a/parser.g	Fri Oct 21 17:47:29 2011 +0200
+++ b/parser.g	Mon Oct 24 17:49:58 2011 +0200
@@ -119,9 +119,9 @@
 
 #// Deletion  ###################################################################
 
-rule _delete<<R>>: decl_rels<<R>> where<<R>> {{ return R }}
+rule _delete<<R>>: decl_rels<<R>> where<<R>> having<<R>> {{ return R }}
 
-                 | decl_vars<<R>> where<<R>> {{ return R }}
+                 | decl_vars<<R>> where<<R>> having<<R>> {{ return R }}
 
 
 #// Insertion  ##################################################################
@@ -129,14 +129,14 @@
 rule _insert<<R>>: decl_vars<<R>> insert_rels<<R>> {{ return R }}
 
 
-rule insert_rels<<R>>: ":" decl_rels<<R>> where<<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>> {{ return R }}
+rule update<<R>>: decl_rels<<R>> where<<R>> having<<R>> {{ return R }}
 
 
 #// Selection  ##################################################################
--- a/parser.py	Fri Oct 21 17:47:29 2011 +0200
+++ b/parser.py	Mon Oct 24 17:49:58 2011 +0200
@@ -145,10 +145,12 @@
         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):
@@ -164,6 +166,7 @@
             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
@@ -172,6 +175,7 @@
         _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):
@@ -243,12 +247,12 @@
 
     def having(self, S, _parent=None):
         _context = self.Context(_parent, self._scanner, 'having', [S])
-        _token = self._peek('HAVING', 'WITH', 'r"\\)"', "';'", context=_context)
+        _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"\\)"', "';'"]
+        else: # in ['WITH', "';'", 'r"\\)"']
             pass
 
     def orderby(self, S, _parent=None):
@@ -339,19 +343,19 @@
 
     def where(self, S, _parent=None):
         _context = self.Context(_parent, self._scanner, 'where', [S])
-        _token = self._peek('WHERE', 'HAVING', "';'", 'WITH', 'r"\\)"', context=_context)
+        _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"\\)"']
+        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) == "','":
+        while self._peek("','", 'r"\\)"', 'HAVING', 'WITH', "';'", context=_context) == "','":
             self._scan("','", context=_context)
             rels_or = self.rels_or(S, _context)
             node = And(node, rels_or)
@@ -361,7 +365,7 @@
         _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':
+        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)
@@ -371,7 +375,7 @@
         _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':
+        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)
@@ -434,11 +438,11 @@
 
     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)
+        _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', "';'"]
+        else: # in ['AND', 'OR', "','", 'r"\\)"', 'WITH', "';'", 'HAVING']
             pass
 
     def logical_expr(self, S, _parent=None):
@@ -525,7 +529,7 @@
         _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', "'IN'", 'HAVING', "';'", 'POW_OP', 'BEING', 'WITH', 'MUL_OP', 'r"\\)"', 'ADD_OP', 'SORT_DESC', 'SORT_ASC', 'GROUPBY', 'ORDERBY', 'LIMIT', 'OFFSET', 'AND', 'OR', context=_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)
@@ -535,7 +539,7 @@
     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) == "','":
+        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)
@@ -566,7 +570,7 @@
         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':
+            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 )
@@ -575,7 +579,7 @@
             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':
+            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 )
@@ -585,7 +589,7 @@
         _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':
+        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)
@@ -595,7 +599,7 @@
         _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':
+        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)
@@ -629,7 +633,7 @@
         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) == "','":
+            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)
@@ -644,7 +648,7 @@
         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) == "','":
+            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)
--- a/stmts.py	Fri Oct 21 17:47:29 2011 +0200
+++ b/stmts.py	Mon Oct 24 17:49:58 2011 +0200
@@ -60,6 +60,7 @@
     solutions = ()   # list of possibles solutions for used variables
     _varmaker = None # variable names generator, built when necessary
     where = None     # where clause node
+    having = ()      # XXX now a single node
 
     def __init__(self):
         # dictionnary of defined variables in the original RQL syntax tree
@@ -72,6 +73,11 @@
         self.where = node
         node.parent = self
 
+    def set_having(self, terms):
+        self.having = terms
+        for node in terms:
+            node.parent = self
+
     def copy(self, copy_solutions=True, solutions=None):
         new = self.__class__()
         if self.schema is not None:
@@ -409,7 +415,6 @@
     # select clauses
     groupby = ()
     orderby = ()
-    having = () # XXX now a single node
     with_ = ()
     # set by the annotator
     has_aggregat = False
@@ -577,11 +582,6 @@
         for node in terms:
             node.parent = self
 
-    def set_having(self, terms):
-        self.having = terms
-        for node in terms:
-            node.parent = self
-
     def set_with(self, terms, check=True):
         self.with_ = []
         for node in terms:
@@ -888,6 +888,8 @@
         children += self.main_relations
         if self.where:
             children.append(self.where)
+        if self.having:
+            children += self.having
         return children
 
     @property
@@ -922,6 +924,8 @@
             result.append(', '.join([repr(rel) for rel in self.main_relations]))
         if self.where is not None:
             result.append(repr(self.where))
+        if self.having:
+            result.append('HAVING ' + ','.join(repr(term) for term in self.having))
         return ' '.join(result)
 
     def as_string(self, encoding=None, kwargs=None):
@@ -937,6 +941,9 @@
                                      for rel in self.main_relations]))
         if self.where is not None:
             result.append('WHERE ' + self.where.as_string(encoding, kwargs))
+        if self.having:
+            result.append('HAVING ' + ','.join(term.as_string(encoding, kwargs)
+                                          for term in self.having))
         return ' '.join(result)
 
     def copy(self):
@@ -948,6 +955,8 @@
             new.add_main_relation(child.copy(new))
         if self.where:
             new.set_where(self.where.copy(new))
+        if self.having:
+            new.set_having([sq.copy(new) for sq in self.having])
         return new
 
 
@@ -969,6 +978,8 @@
         children += self.main_relations
         if self.where:
             children.append(self.where)
+        if self.having:
+            children += self.having
         return children
 
     @property
@@ -1006,6 +1017,8 @@
             result.append(', '.join([repr(rel) for rel in self.main_relations]))
         if self.where is not None:
             result.append('WHERE ' + repr(self.where))
+        if self.having:
+            result.append('HAVING ' + ','.join(repr(term) for term in self.having))
         return ' '.join(result)
 
     def as_string(self, encoding=None, kwargs=None):
@@ -1019,6 +1032,9 @@
                                      for rel in self.main_relations]))
         if self.where is not None:
             result.append('WHERE ' + self.where.as_string(encoding, kwargs))
+        if self.having:
+            result.append('HAVING ' + ','.join(term.as_string(encoding, kwargs)
+                                               for term in self.having))
         return ' '.join(result)
 
     def copy(self):
@@ -1030,6 +1046,8 @@
             new.add_main_relation(child.copy(new))
         if self.where:
             new.set_where(self.where.copy(new))
+        if self.having:
+            new.set_having([sq.copy(new) for sq in self.having])
         return new
 
 
@@ -1048,6 +1066,8 @@
         children = self.main_relations[:]
         if self.where:
             children.append(self.where)
+        if self.having:
+            children += self.having
         return children
 
     @property
@@ -1066,6 +1086,8 @@
         result.append(', '.join(repr(rel) for rel in self.main_relations))
         if self.where is not None:
             result.append('WHERE ' + repr(self.where))
+        if self.having:
+            result.append('HAVING ' + ','.join(repr(term) for term in self.having))
         return ' '.join(result)
 
     def as_string(self, encoding=None, kwargs=None):
@@ -1075,6 +1097,9 @@
                                 for rel in self.main_relations))
         if self.where is not None:
             result.append('WHERE ' + self.where.as_string(encoding, kwargs))
+        if self.having:
+            result.append('HAVING ' + ','.join(term.as_string(encoding, kwargs)
+                                               for term in self.having))
         return ' '.join(result)
 
     def copy(self):
@@ -1083,6 +1108,8 @@
             new.add_main_relation(child.copy(new))
         if self.where:
             new.set_where(self.where.copy(new))
+        if self.having:
+            new.set_having([sq.copy(new) for sq in self.having])
         return new