[multisource] Update syntax

authorVincent Michel <vincent.michel@logilab.fr>
changeset7c85bf67ddce
branchdefault
phasedraft
hiddenno
parent revision#ac7c62ded8ac [multisource] Implement FROM token
child revision<not specified>
files modified by this revision
__init__.py
nodes.py
parser.g
parser.py
stcheck.py
stmts.py
test/unittest_analyze.py
test/unittest_nodes.py
test/unittest_parser.py
test/unittest_stcheck.py
# HG changeset patch
# User Vincent Michel <vincent.michel@logilab.fr>
# Date 1391787725 0
# Fri Feb 07 15:42:05 2014 +0000
# Node ID 7c85bf67ddce1d0e1b37cd6ddd2aaaf8b603a24a
# Parent ac7c62ded8ac27ff71c497a1d8230344f5418b4f
[multisource] Update syntax

diff --git a/__init__.py b/__init__.py
@@ -134,10 +134,13 @@
1                  self._simplify(select)
2 
3      def _simplify(self, select):
4          # recurse on subqueries first
5          for subquery in select.with_:
6 +            if subquery.has_from:
7 +                # Do not simplify distante query
8 +                continue
9              for subselect in subquery.query.children:
10                  self._simplify(subselect)
11          rewritten = False
12          for var in select.defined_vars.values():
13              stinfo = var.stinfo
diff --git a/nodes.py b/nodes.py
@@ -258,24 +258,24 @@
14  # base RQL nodes ##############################################################
15 
16  class SubQuery(BaseNode):
17      """WITH clause"""
18      __slots__ = ('aliases', 'query')
19 -    def __init__(self, aliases=None, query=None):
20 +
21 +    def __init__(self, aliases=None, query=None, from_=None):
22 +        self.from_ = from_
23          if aliases is not None:
24              self.set_aliases(aliases)
25          if query is not None:
26              self.set_query(query)
27 
28 +    def set_from(self, from_):
29 +        self.from_ = from_
30 +
31      @property
32 -    def from_(self):
33 -        """ Return the from_ of the select if existing
34 -        """
35 -        return self.children[-1].from_
36 -
37 -    def set_from(self, from_):
38 -        self.children[-1].set_from(from_)
39 +    def has_from(self):
40 +        return bool(self.from_)
41 
42      def set_aliases(self, aliases):
43          self.aliases = aliases
44          for node in aliases:
45              node.parent = self
@@ -283,22 +283,26 @@
46      def set_query(self, node):
47          self.query = node
48          node.parent = self
49 
50      def copy(self, stmt):
51 -        return SubQuery([v.copy(stmt) for v in self.aliases], self.query.copy())
52 +        return SubQuery([v.copy(stmt) for v in self.aliases], self.query.copy(), self.from_)
53 
54      @property
55      def children(self):
56          return self.aliases + [self.query]
57 
58      def as_string(self, encoding=None, kwargs=None):
59 -        return '%s BEING (%s)' % (','.join(v.name for v in self.aliases),
60 -                                  self.query.as_string(encoding, kwargs))
61 +        from_ = (' FROM "%s"' % self.from_) if self.from_ else u''
62 +        return '%s BEING (%s)%s' % (','.join(v.name for v in self.aliases),
63 +                                    self.query.as_string(encoding, kwargs),
64 +                                    from_)
65      def __repr__(self):
66 -        return '%s BEING (%s)' % (','.join(repr(v) for v in self.aliases),
67 -                                  repr(self.query))
68 +        from_ = (' FROM "%s"' % self.from_) if self.from_ else u''
69 +        return '%s BEING (%s)%s' % (','.join(repr(v) for v in self.aliases),
70 +                                  repr(self.query), from_)
71 +
72 
73  class And(BinaryNode):
74      """a logical AND node (binary)"""
75      __slots__ = ()
76 
diff --git a/parser.g b/parser.g
@@ -154,11 +154,10 @@
77 
78  rule select_<<S>>: E_TYPE selection<<S>>
79                     groupby<<S>>
80                     orderby<<S>>
81                     limit_offset<<S>>
82 -                   from_<<S>>
83                     where<<S>>
84                     having<<S>>
85                     with_<<S>>             {{ S.set_statement_type(E_TYPE); return S }}
86 
87  rule selection<<S>>: expr_add<<S>>        {{ S.append_selected(expr_add) }}
@@ -189,11 +188,11 @@
88                   ( ',' subquery<<S>> {{ nodes.append(subquery) }}
89                   )*                  {{ S.set_with(nodes) }}
90                 |
91 
92  rule subquery<<S>>: variables<<S>>                     {{ node = SubQuery() ; node.set_aliases(variables) }}
93 -                    BEING r"\(" union<<Union()>> r"\)" {{ node.set_query(union); return node }}
94 +                    BEING r"\(" union<<Union()>> r"\)" from_<<S>> {{node.set_query(union); node.set_from(from_); return node }}
95 
96 
97  rule sort_term<<S>>: expr_add<<S>> sort_meth {{ return SortTerm(expr_add, sort_meth) }}
98 
99 
@@ -204,11 +203,11 @@
100                |           {{ return 1 # default to SORT_ASC }}
101 
102 
103  #// From ########################################################################
104 
105 -rule from_<<R>> :  FROM STRING {{ R.set_from(unquote(STRING)); return True }}
106 +rule from_<<R>> :  FROM STRING {{return unquote(STRING);}}
107                 |
108 
109  #// Limit and offset ############################################################
110 
111  rule limit_offset<<R>> :  limit<<R>> offset<<R>> {{ return limit or offset }}
diff --git a/parser.py b/parser.py
@@ -214,34 +214,33 @@
112          E_TYPE = self._scan('E_TYPE', context=_context)
113          selection = self.selection(S, _context)
114          groupby = self.groupby(S, _context)
115          orderby = self.orderby(S, _context)
116          limit_offset = self.limit_offset(S, _context)
117 -        from_ = self.from_(S, _context)
118          where = self.where(S, _context)
119          having = self.having(S, _context)
120          with_ = self.with_(S, _context)
121          S.set_statement_type(E_TYPE); return S
122 
123      def selection(self, S, _parent=None):
124          _context = self.Context(_parent, self._scanner, 'selection', [S])
125          expr_add = self.expr_add(S, _context)
126          S.append_selected(expr_add)
127 -        while self._peek("','", 'GROUPBY', 'ORDERBY', 'FROM', 'LIMIT', 'OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
128 +        while self._peek("','", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
129              self._scan("','", context=_context)
130              expr_add = self.expr_add(S, _context)
131              S.append_selected(expr_add)
132 
133      def groupby(self, S, _parent=None):
134          _context = self.Context(_parent, self._scanner, 'groupby', [S])
135 -        _token = self._peek('GROUPBY', 'ORDERBY', 'FROM', 'LIMIT', 'OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
136 +        _token = self._peek('GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
137          if _token == 'GROUPBY':
138              GROUPBY = self._scan('GROUPBY', context=_context)
139              nodes = []
140              expr_add = self.expr_add(S, _context)
141              nodes.append(expr_add)
142 -            while self._peek("','", 'ORDERBY', 'FROM', 'LIMIT', 'OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
143 +            while self._peek("','", 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
144                  self._scan("','", context=_context)
145                  expr_add = self.expr_add(S, _context)
146                  nodes.append(expr_add)
147              S.set_groupby(nodes); return True
148          else:
@@ -257,17 +256,17 @@
149          else: # in ['WITH', "';'", 'r"\\)"']
150              pass
151 
152      def orderby(self, S, _parent=None):
153          _context = self.Context(_parent, self._scanner, 'orderby', [S])
154 -        _token = self._peek('ORDERBY', 'FROM', 'LIMIT', 'OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
155 +        _token = self._peek('ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
156          if _token == 'ORDERBY':
157              ORDERBY = self._scan('ORDERBY', context=_context)
158              nodes = []
159              sort_term = self.sort_term(S, _context)
160              nodes.append(sort_term)
161 -            while self._peek("','", 'FROM', 'LIMIT', 'OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
162 +            while self._peek("','", 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context) == "','":
163                  self._scan("','", context=_context)
164                  sort_term = self.sort_term(S, _context)
165                  nodes.append(sort_term)
166              S.set_orderby(nodes); return True
167          else:
@@ -295,21 +294,22 @@
168          node = SubQuery() ; node.set_aliases(variables)
169          BEING = self._scan('BEING', context=_context)
170          self._scan('r"\\("', context=_context)
171          union = self.union(Union(), _context)
172          self._scan('r"\\)"', context=_context)
173 -        node.set_query(union); return node
174 +        from_ = self.from_(S, _context)
175 +        node.set_query(union); node.set_from(from_); return node
176 
177      def sort_term(self, S, _parent=None):
178          _context = self.Context(_parent, self._scanner, 'sort_term', [S])
179          expr_add = self.expr_add(S, _context)
180          sort_meth = self.sort_meth(_context)
181          return SortTerm(expr_add, sort_meth)
182 
183      def sort_meth(self, _parent=None):
184          _context = self.Context(_parent, self._scanner, 'sort_meth', [])
185 -        _token = self._peek('SORT_DESC', 'SORT_ASC', "','", 'FROM', 'LIMIT', 'OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
186 +        _token = self._peek('SORT_DESC', 'SORT_ASC', "','", 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
187          if _token == 'SORT_DESC':
188              SORT_DESC = self._scan('SORT_DESC', context=_context)
189              return 0
190          elif _token == 'SORT_ASC':
191              SORT_ASC = self._scan('SORT_ASC', context=_context)
@@ -317,42 +317,42 @@
192          else:
193              return 1 # default to SORT_ASC
194 
195      def from_(self, R, _parent=None):
196          _context = self.Context(_parent, self._scanner, 'from_', [R])
197 -        _token = self._peek('FROM', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
198 +        _token = self._peek('FROM', "','", 'r"\\)"', "';'", context=_context)
199          if _token == 'FROM':
200              FROM = self._scan('FROM', context=_context)
201              STRING = self._scan('STRING', context=_context)
202 -            R.set_from(unquote(STRING)); return True
203 -        else: # in ['WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"']
204 +            return unquote(STRING);
205 +        else: # in ["','", 'r"\\)"', "';'"]
206              pass
207 
208      def limit_offset(self, R, _parent=None):
209          _context = self.Context(_parent, self._scanner, 'limit_offset', [R])
210          limit = self.limit(R, _context)
211          offset = self.offset(R, _context)
212          return limit or offset
213 
214      def limit(self, R, _parent=None):
215          _context = self.Context(_parent, self._scanner, 'limit', [R])
216 -        _token = self._peek('LIMIT', 'OFFSET', 'FROM', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
217 +        _token = self._peek('LIMIT', 'OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
218          if _token == 'LIMIT':
219              LIMIT = self._scan('LIMIT', context=_context)
220              INT = self._scan('INT', context=_context)
221              R.set_limit(int(INT)); return True
222 -        else:
223 +        else: # in ['OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"']
224              pass
225 
226      def offset(self, R, _parent=None):
227          _context = self.Context(_parent, self._scanner, 'offset', [R])
228 -        _token = self._peek('OFFSET', 'FROM', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
229 +        _token = self._peek('OFFSET', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
230          if _token == 'OFFSET':
231              OFFSET = self._scan('OFFSET', context=_context)
232              INT = self._scan('INT', context=_context)
233              R.set_offset(int(INT)); return True
234 -        else: # in ['FROM', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"']
235 +        else: # in ['WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"']
236              pass
237 
238      def where(self, S, _parent=None):
239          _context = self.Context(_parent, self._scanner, 'where', [S])
240          _token = self._peek('WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', context=_context)
@@ -539,11 +539,11 @@
241 
242      def decl_vars(self, R, _parent=None):
243          _context = self.Context(_parent, self._scanner, 'decl_vars', [R])
244          E_TYPE = self._scan('E_TYPE', context=_context)
245          var = self.var(R, _context)
246 -        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', 'FROM', 'LIMIT', 'OFFSET', 'AND', 'OR', context=_context) == "','":
247 +        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) == "','":
248              R.add_main_variable(E_TYPE, var)
249              self._scan("','", context=_context)
250              E_TYPE = self._scan('E_TYPE', context=_context)
251              var = self.var(R, _context)
252          R.add_main_variable(E_TYPE, var)
@@ -580,40 +580,40 @@
253          _context = self.Context(_parent, self._scanner, 'expr_add', [S])
254          _token = self._peek('UNARY_OP', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
255          if _token != 'UNARY_OP':
256              expr_mul = self.expr_mul(S, _context)
257              node = expr_mul
258 -            while self._peek('ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'FROM', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'ADD_OP':
259 +            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':
260                  ADD_OP = self._scan('ADD_OP', context=_context)
261                  expr_mul = self.expr_mul(S, _context)
262                  node = MathExpression( ADD_OP, node, expr_mul )
263              return node
264          else: # == 'UNARY_OP'
265              UNARY_OP = self._scan('UNARY_OP', context=_context)
266              expr_mul = self.expr_mul(S, _context)
267              node = UnaryExpression( UNARY_OP, expr_mul )
268 -            while self._peek('ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'FROM', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'ADD_OP':
269 +            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':
270                  ADD_OP = self._scan('ADD_OP', context=_context)
271                  expr_mul = self.expr_mul(S, _context)
272                  node = MathExpression( ADD_OP, node, expr_mul )
273              return node
274 
275      def expr_mul(self, S, _parent=None):
276          _context = self.Context(_parent, self._scanner, 'expr_mul', [S])
277          expr_pow = self.expr_pow(S, _context)
278          node = expr_pow
279 -        while self._peek('MUL_OP', 'ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'FROM', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'MUL_OP':
280 +        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':
281              MUL_OP = self._scan('MUL_OP', context=_context)
282              expr_pow = self.expr_pow(S, _context)
283              node = MathExpression( MUL_OP, node, expr_pow)
284          return node
285 
286      def expr_pow(self, S, _parent=None):
287          _context = self.Context(_parent, self._scanner, 'expr_pow', [S])
288          expr_base = self.expr_base(S, _context)
289          node = expr_base
290 -        while self._peek('POW_OP', 'MUL_OP', 'ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'FROM', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'POW_OP':
291 +        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':
292              POW_OP = self._scan('POW_OP', context=_context)
293              expr_base = self.expr_base(S, _context)
294              node = MathExpression( MUL_OP, node, expr_base)
295          return node
296 
@@ -643,11 +643,11 @@
297          FUNCTION = self._scan('FUNCTION', context=_context)
298          self._scan('r"\\("', context=_context)
299          F = Function(FUNCTION)
300          if self._peek('UNARY_OP', 'r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"':
301              expr_add = self.expr_add(S, _context)
302 -            while self._peek("','", 'QMARK', 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'FROM', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == "','":
303 +            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) == "','":
304                  F.append(expr_add)
305                  self._scan("','", context=_context)
306                  expr_add = self.expr_add(S, _context)
307              F.append(expr_add)
308          self._scan('r"\\)"', context=_context)
@@ -658,11 +658,11 @@
309          self._scan("'IN'", context=_context)
310          self._scan('r"\\("', context=_context)
311          F = Function('IN')
312          if self._peek('UNARY_OP', 'r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"':
313              expr_add = self.expr_add(S, _context)
314 -            while self._peek("','", 'QMARK', 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'FROM', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == "','":
315 +            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) == "','":
316                  F.append(expr_add)
317                  self._scan("','", context=_context)
318                  expr_add = self.expr_add(S, _context)
319              F.append(expr_add)
320          self._scan('r"\\)"', context=_context)
diff --git a/stcheck.py b/stcheck.py
@@ -523,10 +523,13 @@
321      def visit_select(self, node):
322          for var in node.aliases.itervalues():
323              var.prepare_annotation()
324          if node.with_ is not None:
325              for subquery in node.with_:
326 +                if subquery.has_from:
327 +                    # Do not distant query (has_from)
328 +                    continue
329                  self.visit_union(subquery.query)
330                  subquery.query.schema = node.root.schema
331          node.has_aggregat = False
332          self._visit_stmt(node)
333          if node.having:
diff --git a/stmts.py b/stmts.py
@@ -245,13 +245,12 @@
334          if self.children[-1].has_from:
335              return True
336          return False
337 
338      def _get_from(self):
339 -        warn('from_ is now a Select node attribute', DeprecationWarning,
340 -             stacklevel=2)
341          return self.children[-1].from_
342 +
343      def set_from(self, from_):
344          if len(self.children) == 1:
345              self.children[-1].set_from(from_)
346              return self
347          self.wrap_selects()
@@ -915,11 +914,11 @@
348 
349  class Delete(Statement, ScopeNode):
350      """the Delete node is the root of the syntax tree for deletion statement
351      """
352      TYPE = 'delete'
353 -    # FROM not implemented (yet...) from Delete node
354 +    # FROM not implemented (yet...) for Delete node
355      has_from = False
356 
357      def __init__(self):
358          Statement.__init__(self)
359          ScopeNode.__init__(self)
@@ -1006,11 +1005,11 @@
360 
361  class Insert(Statement, ScopeNode):
362      """the Insert node is the root of the syntax tree for insertion statement
363      """
364      TYPE = 'insert'
365 -    # FROM not implemented (yet...) from Insert node
366 +    # FROM not implemented (yet...) for Insert node
367      has_from = False
368 
369      def __init__(self):
370          Statement.__init__(self)
371          ScopeNode.__init__(self)
@@ -1099,11 +1098,11 @@
372 
373  class Set(Statement, ScopeNode):
374      """the Set node is the root of the syntax tree for update statement
375      """
376      TYPE = 'set'
377 -    # FROM not implemented (yet...) from Set node
378 +    # FROM not implemented (yet...) for Set node
379      has_from = False
380 
381      def __init__(self):
382          Statement.__init__(self)
383          ScopeNode.__init__(self)
diff --git a/test/unittest_analyze.py b/test/unittest_analyze.py
@@ -240,15 +240,14 @@
384          self.helper.compute_solutions(node, debug=DEBUG)
385          sols = node.children[0].solutions
386          self.assertEqual(sols, [{}])
387 
388      def test_from_1(self):
389 -        node = self.helper.parse('Any X FROM "dbpedia" WHERE X is DbpediaPage')
390 +        node = self.helper.parse('Any X WITH X BEING (Any X WHERE X is DbpediaPage) FROM "dbpedia"')
391          # check constant type of the is relation inserted
392 -        self.assertEqual(node.children[0].where.children[1].children[0].type,
393 -                         'etype')
394          self.helper.compute_solutions(node, debug=DEBUG)
395 +        self.helper.simplify(node)
396          sols = node.children[0].solutions
397          self.assertEqual(sols, ())
398 
399      def test_base_guess_1(self):
400          node = self.helper.parse('Person X WHERE X work_for Y')
@@ -420,11 +419,12 @@
401          self.assertEqual(sols, [{'X': 'Company', 'Z': 'String'},
402                                  {'X': 'Person', 'Z': 'String'},
403                                  {'X': 'Student', 'Z': 'String'}])
404 
405      def test_var_from(self):
406 -        node = self.helper.parse('Any E1 GROUPBY E1 FROM "dbpedia" WHERE E2 is Dbpediapage, E2 name E1')
407 +        node = self.helper.parse('Any E1 GROUPBY E1 WHERE E2 name E1 WITH E2 BEING '
408 +                                 '(Any E2 WHERE E2 is Dbpediapage) FROM "dbpedia"')
409          self.helper.compute_solutions(node, debug=DEBUG)
410          sols = sorted(node.children[0].solutions)
411          self.assertEqual(sols, [])
412 
413      def test_var_name(self):
diff --git a/test/unittest_nodes.py b/test/unittest_nodes.py
@@ -190,15 +190,24 @@
414          self.assertEqual(tree.from_, 'dbpedia.org')
415          tree.recover()
416          self.assertEqual(tree.from_, None)
417 
418      def test_select_set_from_being(self):
419 -        tree = self._simpleparse('Any X WHERE X is Person, X same_as Y WITH Y BEING (Any Y FROM "cubicweb.org" WHERE Y is Person)')
420 +        tree = self._simpleparse('Any X WHERE X is Person, X same_as Y '
421 +                                 'WITH Y BEING (Any Y WHERE Y is Person) FROM "cubicweb.org"')
422 +        self.assertEqual(tree.with_[0].as_string(),
423 +                         'Y BEING (Any Y WHERE Y is Person) FROM "cubicweb.org"')
424          self.assertEqual(tree.with_[0].from_, 'cubicweb.org')
425          tree.with_[0].set_from('')
426          self.assertEqual(tree.with_[0].as_string(), 'Y BEING (Any Y WHERE Y is Person)')
427 
428 +    def test_select_set_from_being_subquery(self):
429 +        tree = self._simpleparse('Any N WITH N BEING (Any N WHERE X is Project, X name N) '
430 +                                 'FROM "http://www.cubicweb.org"')
431 +        for subquery in tree.with_:
432 +            self.assertEqual(subquery.query.parent.from_, 'http://www.cubicweb.org')
433 +
434      def test_select_set_limit(self):
435          tree = self._simpleparse("Any X WHERE X is Person")
436          self.assertEqual(tree.limit, None)
437          self.assertRaises(BadRQLQuery, tree.set_limit, 0)
438          self.assertRaises(BadRQLQuery, tree.set_limit, -1)
@@ -402,15 +411,10 @@
439          tree = self._parse("Any X LIMIT 10 OFFSET 10 WHERE X name 1.0")
440          select = tree.children[0]
441          self.assertEqual(select.limit, 10)
442          self.assertEqual(select.offset, 10)
443 
444 -    def test_set_from(self):
445 -        tree = self._parse('Any X FROM "dbpedia.org" WHERE X is Person')
446 -        select = tree.children[0]
447 -        self.assertEqual(select.from_, 'dbpedia.org')
448 -
449      def test_exists(self):
450          tree = self._simpleparse("Any X,N WHERE X is Person, X name N, EXISTS(X work_for Y)")
451 
452      def test_copy(self):
453          tree = self._parse("Any X,LOWER(Y) GROUPBY N ORDERBY N WHERE X is Person, X name N, X date >= TODAY")
diff --git a/test/unittest_parser.py b/test/unittest_parser.py
@@ -38,12 +38,10 @@
454      '(Any X GROUPBY X WHERE X nom "toto") UNION (Any X GROUPBY X WHERE X firstname "toto") ORDERBY X;',
455 
456      'Any X, X/Y FROM (Any SUM(X) WHERE X is Person) WHERE X is Person;', # missing AS for subquery
457 
458      'Any X, X/Y FROM (Any X WHERE X is) WHERE X is Person;', # missing AS for subquery
459 -    'Any X FROM WHERE X name NULL',
460 -
461      )
462 
463  BAD_QUERIES = (
464      'Person Marcou;',
465      'INSERT Any X : X name "bidule";',
diff --git a/test/unittest_stcheck.py b/test/unittest_stcheck.py
@@ -80,13 +80,15 @@
466      # variable with only ?1 cardinality
467      'DISTINCT Any P ORDERBY PN WHERE P work_for X, P name PN',
468      'DISTINCT Any P ORDERBY XN WHERE P work_for X, X name XN',
469 
470       # FROM node
471 -    'Any X FROM "dbpedia.org" WHERE X is Dbpediapage',
472 -    'Any X FROM "dbpedia.org" WHERE X is Dbpediapage, X related_page Y',
473 -    'Any X,CC WHERE X same_as Y WITHCC BEING (Any CC FROM "geonames" WHERE Y country CC)',
474 +    'Any X WITH X BEING (Any X WHERE X is Dbpediapage) FROM "dbpedia.org"',
475 +    'Any X WITH X BEING (Any X WHERE X is Dbpediapage, X related_page Y) FROM "dbpedia.org" ',
476 +    'Any X,CC WHERE X same_as Y WITH CC BEING (Any CC WHERE Y country CC) FROM "geonames"',
477 +    'Any N, M WITH N BEING (Any X WHERE X is Project, X name N) FROM "http://www.cubicweb.org", '\
478 +    'M BEING (Any X WHERE X is Project, X name N) FROM "http://www.cubicweb.org"',
479 
480      )
481 
482  class CheckClassTest(TestCase):
483      """check wrong queries are correctly detected"""