[rql] HAVING support in write queries (INSERT,SET,DELETE). Closes #81394

authorSylvain Th?nault <sylvain.thenault@logilab.fr>
changesetb2e231cbd9da
branchdefault
phasepublic
hiddenno
parent revision#4a0bbee051e0 backport stable
child revision#dae50737d5fb 668:b2e231cbd9da introduces unrelated changes that 1. break tests, 2. make rql>0.30.1 incompatible with cw < 3.13.10
files modified by this revision
nodes.py
parser.g
parser.py
stmts.py
# HG changeset patch
# User Sylvain Thénault <sylvain.thenault@logilab.fr>
# Date 1319471398 -7200
# Mon Oct 24 17:49:58 2011 +0200
# Node ID b2e231cbd9da735cbbfb814f31dbe2d70cd63fbe
# Parent 4a0bbee051e0deab54e231f9ecae55672bd43692
[rql] HAVING support in write queries (INSERT,SET,DELETE). Closes #81394

diff --git a/nodes.py b/nodes.py
@@ -995,16 +995,21 @@
1              # use getattr, may not be a variable ref (rewritten, constant...)
2              rhsvar = getattr(rhs, 'variable', None)
3              if mainindex is not None:
4                  # relation to the main variable, stop searching
5                  lhsvar = getattr(lhs, 'variable', None)
6 +                context = None
7                  if lhsvar is not None and mainindex in lhsvar.stinfo['selected']:
8 -                    return tr(rtype)
9 +                    if len(lhsvar.stinfo['possibletypes']) == 1:
10 +                        context = iter(lhsvar.stinfo['possibletypes']).next()
11 +                    return tr(rtype, context=context)
12                  if rhsvar is not None and mainindex in rhsvar.stinfo['selected']:
13 +                    if len(rhsvar.stinfo['possibletypes']) == 1:
14 +                        context = iter(rhsvar.stinfo['possibletypes']).next()
15                      if schema is not None and rschema.symmetric:
16 -                        return tr(rtype)
17 -                    return tr(rtype + '_object')
18 +                        return tr(rtype, context=context)
19 +                    return tr(rtype + '_object', context=context)
20              if rhsvar is self:
21                  rtype += '_object'
22          if frtype is not None:
23              return tr(frtype)
24          if mainindex is None and rtype is not None:
diff --git a/parser.g b/parser.g
@@ -117,28 +117,28 @@
25 
26           | union<<Union()>> ';'                       {{ return union }}
27 
28  #// Deletion  ###################################################################
29 
30 -rule _delete<<R>>: decl_rels<<R>> where<<R>> {{ return R }}
31 +rule _delete<<R>>: decl_rels<<R>> where<<R>> having<<R>> {{ return R }}
32 
33 -                 | decl_vars<<R>> where<<R>> {{ return R }}
34 +                 | decl_vars<<R>> where<<R>> having<<R>> {{ return R }}
35 
36 
37  #// Insertion  ##################################################################
38 
39  rule _insert<<R>>: decl_vars<<R>> insert_rels<<R>> {{ return R }}
40 
41 
42 -rule insert_rels<<R>>: ":" decl_rels<<R>> where<<R>> {{ return R }}
43 +rule insert_rels<<R>>: ":" decl_rels<<R>> where<<R>> having<<R>> {{ return R }}
44 
45                       |
46 
47 
48  #// Update  #####################################################################
49 
50 -rule update<<R>>: decl_rels<<R>> where<<R>> {{ return R }}
51 +rule update<<R>>: decl_rels<<R>> where<<R>> having<<R>> {{ return R }}
52 
53 
54  #// Selection  ##################################################################
55 
56  rule union<<R>>: select<<Select()>>               {{ R.append(select); return R }}
diff --git a/parser.py b/parser.py
@@ -143,14 +143,16 @@
57          _context = self.Context(_parent, self._scanner, '_delete', [R])
58          _token = self._peek('E_TYPE', 'VARIABLE', context=_context)
59          if _token == 'VARIABLE':
60              decl_rels = self.decl_rels(R, _context)
61              where = self.where(R, _context)
62 +            having = self.having(R, _context)
63              return R
64          else: # == 'E_TYPE'
65              decl_vars = self.decl_vars(R, _context)
66              where = self.where(R, _context)
67 +            having = self.having(R, _context)
68              return R
69 
70      def _insert(self, R, _parent=None):
71          _context = self.Context(_parent, self._scanner, '_insert', [R])
72          decl_vars = self.decl_vars(R, _context)
@@ -162,18 +164,20 @@
73          _token = self._peek('":"', "';'", context=_context)
74          if _token == '":"':
75              self._scan('":"', context=_context)
76              decl_rels = self.decl_rels(R, _context)
77              where = self.where(R, _context)
78 +            having = self.having(R, _context)
79              return R
80          else: # == "';'"
81              pass
82 
83      def update(self, R, _parent=None):
84          _context = self.Context(_parent, self._scanner, 'update', [R])
85          decl_rels = self.decl_rels(R, _context)
86          where = self.where(R, _context)
87 +        having = self.having(R, _context)
88          return R
89 
90      def union(self, R, _parent=None):
91          _context = self.Context(_parent, self._scanner, 'union', [R])
92          _token = self._peek('r"\\("', 'DISTINCT', 'E_TYPE', context=_context)
@@ -241,16 +245,16 @@
93          else:
94              pass
95 
96      def having(self, S, _parent=None):
97          _context = self.Context(_parent, self._scanner, 'having', [S])
98 -        _token = self._peek('HAVING', 'WITH', 'r"\\)"', "';'", context=_context)
99 +        _token = self._peek('HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
100          if _token == 'HAVING':
101              HAVING = self._scan('HAVING', context=_context)
102              logical_expr = self.logical_expr(S, _context)
103              S.set_having([logical_expr])
104 -        else: # in ['WITH', 'r"\\)"', "';'"]
105 +        else: # in ['WITH', "';'", 'r"\\)"']
106              pass
107 
108      def orderby(self, S, _parent=None):
109          _context = self.Context(_parent, self._scanner, 'orderby', [S])
110          _token = self._peek('ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
@@ -337,43 +341,43 @@
111          else: # in ['WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"']
112              pass
113 
114      def where(self, S, _parent=None):
115          _context = self.Context(_parent, self._scanner, 'where', [S])
116 -        _token = self._peek('WHERE', 'HAVING', "';'", 'WITH', 'r"\\)"', context=_context)
117 +        _token = self._peek('WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
118          if _token == 'WHERE':
119              WHERE = self._scan('WHERE', context=_context)
120              restriction = self.restriction(S, _context)
121              S.set_where(restriction)
122 -        else: # in ['HAVING', "';'", 'WITH', 'r"\\)"']
123 +        else: # in ['HAVING', 'WITH', "';'", 'r"\\)"']
124              pass
125 
126      def restriction(self, S, _parent=None):
127          _context = self.Context(_parent, self._scanner, 'restriction', [S])
128          rels_or = self.rels_or(S, _context)
129          node = rels_or
130 -        while self._peek("','", 'r"\\)"', 'HAVING', "';'", 'WITH', context=_context) == "','":
131 +        while self._peek("','", 'r"\\)"', 'HAVING', 'WITH', "';'", context=_context) == "','":
132              self._scan("','", context=_context)
133              rels_or = self.rels_or(S, _context)
134              node = And(node, rels_or)
135          return node
136 
137      def rels_or(self, S, _parent=None):
138          _context = self.Context(_parent, self._scanner, 'rels_or', [S])
139          rels_and = self.rels_and(S, _context)
140          node = rels_and
141 -        while self._peek('OR', "','", 'r"\\)"', 'HAVING', "';'", 'WITH', context=_context) == 'OR':
142 +        while self._peek('OR', "','", 'r"\\)"', 'HAVING', 'WITH', "';'", context=_context) == 'OR':
143              OR = self._scan('OR', context=_context)
144              rels_and = self.rels_and(S, _context)
145              node = Or(node, rels_and)
146          return node
147 
148      def rels_and(self, S, _parent=None):
149          _context = self.Context(_parent, self._scanner, 'rels_and', [S])
150          rels_not = self.rels_not(S, _context)
151          node = rels_not
152 -        while self._peek('AND', 'OR', "','", 'r"\\)"', 'HAVING', "';'", 'WITH', context=_context) == 'AND':
153 +        while self._peek('AND', 'OR', "','", 'r"\\)"', 'HAVING', 'WITH', "';'", context=_context) == 'AND':
154              AND = self._scan('AND', context=_context)
155              rels_not = self.rels_not(S, _context)
156              node = And(node, rels_not)
157          return node
158 
@@ -432,15 +436,15 @@
159          else: # in ['R_TYPE', 'CMP_OP', "'IN'"]
160              pass
161 
162      def opt_right(self, S, _parent=None):
163          _context = self.Context(_parent, self._scanner, 'opt_right', [S])
164 -        _token = self._peek('QMARK', 'AND', 'OR', "','", 'r"\\)"', 'WITH', 'HAVING', "';'", context=_context)
165 +        _token = self._peek('QMARK', 'AND', 'OR', "','", 'r"\\)"', 'WITH', "';'", 'HAVING', context=_context)
166          if _token == 'QMARK':
167              QMARK = self._scan('QMARK', context=_context)
168              return 'right'
169 -        else: # in ['AND', 'OR', "','", 'r"\\)"', 'WITH', 'HAVING', "';'"]
170 +        else: # in ['AND', 'OR', "','", 'r"\\)"', 'WITH', "';'", 'HAVING']
171              pass
172 
173      def logical_expr(self, S, _parent=None):
174          _context = self.Context(_parent, self._scanner, 'logical_expr', [S])
175          exprs_or = self.exprs_or(S, _context)
@@ -523,21 +527,21 @@
176 
177      def decl_vars(self, R, _parent=None):
178          _context = self.Context(_parent, self._scanner, 'decl_vars', [R])
179          E_TYPE = self._scan('E_TYPE', context=_context)
180          var = self.var(R, _context)
181 -        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) == "','":
182 +        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) == "','":
183              R.add_main_variable(E_TYPE, var)
184              self._scan("','", context=_context)
185              E_TYPE = self._scan('E_TYPE', context=_context)
186              var = self.var(R, _context)
187          R.add_main_variable(E_TYPE, var)
188 
189      def decl_rels(self, R, _parent=None):
190          _context = self.Context(_parent, self._scanner, 'decl_rels', [R])
191          simple_rel = self.simple_rel(R, _context)
192 -        while self._peek("','", 'WHERE', 'HAVING', "';'", 'WITH', 'r"\\)"', context=_context) == "','":
193 +        while self._peek("','", 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
194              R.add_main_relation(simple_rel)
195              self._scan("','", context=_context)
196              simple_rel = self.simple_rel(R, _context)
197          R.add_main_relation(simple_rel)
198 
@@ -564,40 +568,40 @@
199          _context = self.Context(_parent, self._scanner, 'expr_add', [S])
200          _token = self._peek('UNARY_OP', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
201          if _token != 'UNARY_OP':
202              expr_mul = self.expr_mul(S, _context)
203              node = expr_mul
204 -            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':
205 +            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':
206                  ADD_OP = self._scan('ADD_OP', context=_context)
207                  expr_mul = self.expr_mul(S, _context)
208                  node = MathExpression( ADD_OP, node, expr_mul )
209              return node
210          else: # == 'UNARY_OP'
211              UNARY_OP = self._scan('UNARY_OP', context=_context)
212              expr_mul = self.expr_mul(S, _context)
213              node = UnaryExpression( UNARY_OP, expr_mul )
214 -            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':
215 +            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':
216                  ADD_OP = self._scan('ADD_OP', context=_context)
217                  expr_mul = self.expr_mul(S, _context)
218                  node = MathExpression( ADD_OP, node, expr_mul )
219              return node
220 
221      def expr_mul(self, S, _parent=None):
222          _context = self.Context(_parent, self._scanner, 'expr_mul', [S])
223          expr_pow = self.expr_pow(S, _context)
224          node = expr_pow
225 -        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':
226 +        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':
227              MUL_OP = self._scan('MUL_OP', context=_context)
228              expr_pow = self.expr_pow(S, _context)
229              node = MathExpression( MUL_OP, node, expr_pow)
230          return node
231 
232      def expr_pow(self, S, _parent=None):
233          _context = self.Context(_parent, self._scanner, 'expr_pow', [S])
234          expr_base = self.expr_base(S, _context)
235          node = expr_base
236 -        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':
237 +        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':
238              POW_OP = self._scan('POW_OP', context=_context)
239              expr_base = self.expr_base(S, _context)
240              node = MathExpression( MUL_OP, node, expr_base)
241          return node
242 
@@ -627,11 +631,11 @@
243          FUNCTION = self._scan('FUNCTION', context=_context)
244          self._scan('r"\\("', context=_context)
245          F = Function(FUNCTION)
246          if self._peek('UNARY_OP', 'r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"':
247              expr_add = self.expr_add(S, _context)
248 -            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) == "','":
249 +            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) == "','":
250                  F.append(expr_add)
251                  self._scan("','", context=_context)
252                  expr_add = self.expr_add(S, _context)
253              F.append(expr_add)
254          self._scan('r"\\)"', context=_context)
@@ -642,11 +646,11 @@
255          self._scan("'IN'", context=_context)
256          self._scan('r"\\("', context=_context)
257          F = Function('IN')
258          if self._peek('UNARY_OP', 'r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"':
259              expr_add = self.expr_add(S, _context)
260 -            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) == "','":
261 +            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) == "','":
262                  F.append(expr_add)
263                  self._scan("','", context=_context)
264                  expr_add = self.expr_add(S, _context)
265              F.append(expr_add)
266          self._scan('r"\\)"', context=_context)
diff --git a/stmts.py b/stmts.py
@@ -58,10 +58,11 @@
267 
268  class ScopeNode(BaseNode):
269      solutions = ()   # list of possibles solutions for used variables
270      _varmaker = None # variable names generator, built when necessary
271      where = None     # where clause node
272 +    having = ()      # XXX now a single node
273 
274      def __init__(self):
275          # dictionnary of defined variables in the original RQL syntax tree
276          self.defined_vars = {}
277 
@@ -70,10 +71,15 @@
278 
279      def set_where(self, node):
280          self.where = node
281          node.parent = self
282 
283 +    def set_having(self, terms):
284 +        self.having = terms
285 +        for node in terms:
286 +            node.parent = self
287 +
288      def copy(self, copy_solutions=True, solutions=None):
289          new = self.__class__()
290          if self.schema is not None:
291              new.schema = self.schema
292          if solutions is not None:
@@ -407,11 +413,10 @@
293      limit = None
294      offset = 0
295      # select clauses
296      groupby = ()
297      orderby = ()
298 -    having = () # XXX now a single node
299      with_ = ()
300      # set by the annotator
301      has_aggregat = False
302 
303      def __init__(self):
@@ -575,15 +580,10 @@
304      def set_groupby(self, terms):
305          self.groupby = terms
306          for node in terms:
307              node.parent = self
308 
309 -    def set_having(self, terms):
310 -        self.having = terms
311 -        for node in terms:
312 -            node.parent = self
313 -
314      def set_with(self, terms, check=True):
315          self.with_ = []
316          for node in terms:
317              self.add_subquery(node, check)
318 
@@ -886,10 +886,12 @@
319      def children(self):
320          children = self.selection[:]
321          children += self.main_relations
322          if self.where:
323              children.append(self.where)
324 +        if self.having:
325 +            children += self.having
326          return children
327 
328      @property
329      def selection(self):
330          return [vref for et, vref in self.main_variables]
@@ -920,10 +922,12 @@
331              if self.main_variables:
332                  result.append(',')
333              result.append(', '.join([repr(rel) for rel in self.main_relations]))
334          if self.where is not None:
335              result.append(repr(self.where))
336 +        if self.having:
337 +            result.append('HAVING ' + ','.join(repr(term) for term in self.having))
338          return ' '.join(result)
339 
340      def as_string(self, encoding=None, kwargs=None):
341          """return the tree as an encoded rql string"""
342          result = ['DELETE']
@@ -935,10 +939,13 @@
343                  result.append(',')
344              result.append(', '.join([rel.as_string(encoding, kwargs)
345                                       for rel in self.main_relations]))
346          if self.where is not None:
347              result.append('WHERE ' + self.where.as_string(encoding, kwargs))
348 +        if self.having:
349 +            result.append('HAVING ' + ','.join(term.as_string(encoding, kwargs)
350 +                                          for term in self.having))
351          return ' '.join(result)
352 
353      def copy(self):
354          new = Delete()
355          for etype, var in self.main_variables:
@@ -946,10 +953,12 @@
356              new.add_main_variable(etype, vref)
357          for child in self.main_relations:
358              new.add_main_relation(child.copy(new))
359          if self.where:
360              new.set_where(self.where.copy(new))
361 +        if self.having:
362 +            new.set_having([sq.copy(new) for sq in self.having])
363          return new
364 
365 
366  class Insert(Statement, ScopeNode):
367      """the Insert node is the root of the syntax tree for insertion statement
@@ -967,10 +976,12 @@
368      def children(self):
369          children = self.selection[:]
370          children += self.main_relations
371          if self.where:
372              children.append(self.where)
373 +        if self.having:
374 +            children += self.having
375          return children
376 
377      @property
378      def selection(self):
379          return [vref for et, vref in self.main_variables]
@@ -1004,10 +1015,12 @@
380          if self.main_relations:
381              result.append(':')
382              result.append(', '.join([repr(rel) for rel in self.main_relations]))
383          if self.where is not None:
384              result.append('WHERE ' + repr(self.where))
385 +        if self.having:
386 +            result.append('HAVING ' + ','.join(repr(term) for term in self.having))
387          return ' '.join(result)
388 
389      def as_string(self, encoding=None, kwargs=None):
390          """return the tree as an encoded rql string"""
391          result = ['INSERT']
@@ -1017,10 +1030,13 @@
392              result.append(':')
393              result.append(', '.join([rel.as_string(encoding, kwargs)
394                                       for rel in self.main_relations]))
395          if self.where is not None:
396              result.append('WHERE ' + self.where.as_string(encoding, kwargs))
397 +        if self.having:
398 +            result.append('HAVING ' + ','.join(term.as_string(encoding, kwargs)
399 +                                               for term in self.having))
400          return ' '.join(result)
401 
402      def copy(self):
403          new = Insert()
404          for etype, var in self.main_variables:
@@ -1028,10 +1044,12 @@
405              new.add_main_variable(etype, vref)
406          for child in self.main_relations:
407              new.add_main_relation(child.copy(new))
408          if self.where:
409              new.set_where(self.where.copy(new))
410 +        if self.having:
411 +            new.set_having([sq.copy(new) for sq in self.having])
412          return new
413 
414 
415  class Set(Statement, ScopeNode):
416      """the Set node is the root of the syntax tree for update statement
@@ -1046,10 +1064,12 @@
417      @property
418      def children(self):
419          children = self.main_relations[:]
420          if self.where:
421              children.append(self.where)
422 +        if self.having:
423 +            children += self.having
424          return children
425 
426      @property
427      def selection(self):
428          return []
@@ -1064,26 +1084,33 @@
429      def __repr__(self):
430          result = ['SET']
431          result.append(', '.join(repr(rel) for rel in self.main_relations))
432          if self.where is not None:
433              result.append('WHERE ' + repr(self.where))
434 +        if self.having:
435 +            result.append('HAVING ' + ','.join(repr(term) for term in self.having))
436          return ' '.join(result)
437 
438      def as_string(self, encoding=None, kwargs=None):
439          """return the tree as an encoded rql string"""
440          result = ['SET']
441          result.append(', '.join(rel.as_string(encoding, kwargs)
442                                  for rel in self.main_relations))
443          if self.where is not None:
444              result.append('WHERE ' + self.where.as_string(encoding, kwargs))
445 +        if self.having:
446 +            result.append('HAVING ' + ','.join(term.as_string(encoding, kwargs)
447 +                                               for term in self.having))
448          return ' '.join(result)
449 
450      def copy(self):
451          new = Set()
452          for child in self.main_relations:
453              new.add_main_relation(child.copy(new))
454          if self.where:
455              new.set_where(self.where.copy(new))
456 +        if self.having:
457 +            new.set_having([sq.copy(new) for sq in self.having])
458          return new
459 
460 
461  build_visitor_stub((Union, Select, Insert, Delete, Set))