# HG changeset patch # User Sylvain <syt@logilab.fr> # Date 1208793570 -7200 # Mon Apr 21 17:59:30 2008 +0200 # Node ID b66c3bc2e92a42b5aa05dbf4fbe4fcfc92b55d6c # Parent 5c8280470677ef4e3d03d0ce8fb91757b722b9b1 grammar finalization, api refactoring/cleanup, unittests ok diff --git a/__init__.py b/__init__.py --- a/__init__.py +++ b/__init__.py @@ -4,6 +4,7 @@ :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ +__docformat__ = "restructuredtext en" import sys import threading @@ -50,7 +51,7 @@ raise UsesReservedWord(etype) for rtype in schema.relations(): rtype = str(rtype) - if is_keyword(rtype):# or rtype.lower() == 'is': + if is_keyword(rtype): raise UsesReservedWord(rtype) self._checker.schema = schema self._annotator.schema = schema @@ -93,24 +94,12 @@ from rql import nodes for select in rqlst.children: self._simplify(select) - # deal with rewritten variable which are used in orderby - for vname in select.stinfo['rewritten']: - try: - var = rqlst.defined_vars.pop(vname) - except KeyError: - continue - else: - for vref in var.references(): - term = vref.parent - while not isinstance(term, nodes.SortTerm): - term = term.parent - rqlst.remove_sort_term(term) return rqlst def _simplify(self, select): # recurse on subqueries first - for subquery in select.from_: - for select in subquery.children: + for subquery in select.with_: + for select in subquery.query.children: self._simplify(select) for var in select.defined_vars.values(): stinfo = var.stinfo @@ -122,10 +111,25 @@ rhs = uidrel.children[1].children[0] #from rql.nodes import Constant #assert isinstance(rhs, nodes.Constant), rhs - for varref in var.references(): - rel = varref.relation() - #assert varref.parent - if rel and (rel is uidrel or rel.is_types_restriction()): + for vref in var.references(): + rel = vref.relation() + #assert vref.parent + if rel is None: + term = vref + while not term.parent is select: + term = term.parent + if term in select.selection: + rhs = rhs.copy(select) + rhs.uid = True + vconsts.append(rhs) + if vref is term: + select.selection[select.selection.index(vref)] = rhs + rhs.parent = select + else: + vref.parent.replace(vref, rhs) + else: + select.remove(term) + elif rel is uidrel or rel.is_types_restriction(): # drop this relation rel.parent.remove(rel) else: @@ -137,9 +141,9 @@ # # substitute rhs # if rel and uidrel._not: # rel._not = rel._not or uidrel._not - varref.parent.replace(varref, rhs) + vref.parent.replace(vref, rhs) del select.defined_vars[var.name] - if select.stinfo['rewritten']: + if select.stinfo['rewritten'] and select.solutions: select.clean_solutions() def compare(self, rqlstring1, rqlstring2): diff --git a/analyze.py b/analyze.py --- a/analyze.py +++ b/analyze.py @@ -128,10 +128,9 @@ (var,), '%s == %r' % (var, etype))) for relation in node.main_relations: self._visit(relation, constraints) - restriction = node.get_restriction() - if restriction is not None: - # get constraints from the restriction subtree - self._visit(restriction, constraints) + # get constraints from the restriction subtree + if node.where is not None: + self._visit(node.where, constraints) self.solve(node, domains, constraints) visit_delete = visit_insert @@ -144,21 +143,20 @@ constraints = [] for relation in node.main_relations: self._visit(relation, constraints) - restriction = node.get_restriction() - if restriction is not None: - # get constraints from the restriction subtree - self._visit(restriction, constraints) + # get constraints from the restriction subtree + if node.where is not None: + self._visit(node.where, constraints) self.solve(node, domains, constraints) def visit_select(self, node): if not (node.defined_vars or node.aliases): node.set_possible_types([{}]) return - for subquery in node.from_: # resolve subqueries first - self.visit_union(subquery) + for subquery in node.with_: # resolve subqueries first + self.visit_union(subquery.query) domains = self._init_stmt(node) for ca in node.aliases.itervalues(): - etypes = set(stmt.selected[ca.colnum].get_type(sol, self.kwargs) + etypes = set(stmt.selection[ca.colnum].get_type(sol, self.kwargs) for stmt in ca.query.children for sol in stmt.solutions) domains[ca.name] = fd.FiniteDomain(etypes) constraints = [] @@ -170,11 +168,10 @@ uidtype = self.uid_func(consts[0].eval(self.kwargs)) for const in consts: const.uidtype = uidtype - restriction = node.get_restriction() - if not restriction is None: - # get constraints from the restriction subtree - self._visit(restriction, constraints) - elif not node.from_: + # get constraints from the restriction subtree + if node.where is not None: + self._visit(node.where, constraints) + elif not node.with_: varnames = [v.name for v in node.get_selected_variables()] if varnames: # add constraint on real relation types if no restriction @@ -474,7 +471,7 @@ def visit_select(self, node): sols = self.visit_children(node) - for n in node.selected: + for n in node.selection: sols2 = n.accept(self) for s in sols2: del s['type'] diff --git a/base.py b/base.py --- a/base.py +++ b/base.py @@ -14,6 +14,10 @@ def __str__(self): return self.as_string(encoding='utf-8') + def as_string(self, encoding=None, kwargs=None): + """return the tree as an encoded rql string""" + raise NotImplementedError() + def initargs(self, stmt): """return list of arguments to give to __init__ to clone this node @@ -88,6 +92,16 @@ if not path: return self return self.children[path[0]].go_to_index_path(path[1:]) + + def copy(self, stmt): + """create and return a copy of this node and its descendant + + stmt is the root node, which should be use to get new variables + """ + new = self.__class__(*self.initargs(stmt)) + for child in self.children: + new.append(child.copy(stmt)) + return new class Node(BaseNode): @@ -119,16 +133,7 @@ self.children.pop(i) self.children.insert(i, new_child) new_child.parent = self - - def copy(self, stmt): - """create and return a copy of this node and its descendant - stmt is the root node, which should be use to get new variables - """ - new = self.__class__(*self.initargs(stmt)) - for child in self.children: - new.append(child.copy(stmt)) - return new class BinaryNode(Node): __slots__ = () @@ -168,4 +173,3 @@ """ return self.__class__(*self.initargs(stmt)) - diff --git a/nodes.py b/nodes.py --- a/nodes.py +++ b/nodes.py @@ -24,8 +24,8 @@ 'TODAY': today} from rql import CoercionError -from rql.base import Node, BinaryNode, LeafNode -from rql.utils import function_description, quote, uquote +from rql.base import BaseNode, Node, BinaryNode, LeafNode +from rql.utils import function_description, quote, uquote, build_visitor_stub CONSTANT_TYPES = frozenset((None, 'Date', 'Datetime', 'Boolean', 'Float', 'Int', 'String', 'Substitute', 'etype')) @@ -50,6 +50,11 @@ assert isinstance(var, VariableRef) return var +def variable_refs(node): + for vref in node.iget_nodes(VariableRef): + if isinstance(vref.variable, Variable): + yield vref + class HSMixin(object): """mixin class for classes which may be the lhs or rhs of an expression""" @@ -94,7 +99,7 @@ def should_register_op(self): root = self.root # root is None during parsing - return root is not None and root.memorizing and not root.undoing + return root is not None and root.should_register_op def remove_node(self, node): """remove the given node from the tree @@ -114,19 +119,18 @@ def add_restriction(self, relation): """add a restriction relation""" - r = self.get_restriction() + r = self.where if r is not None: - newnode = AND(r, relation) - self.replace(r, newnode) + newnode = And(r, relation) + self.set_where(newnode) if self.should_register_op: from rql.undo import ReplaceNodeOperation self.undo_manager.add_operation(ReplaceNodeOperation(r, newnode)) else: - self.insert(0, relation) + self.set_where(relation) if self.should_register_op: from rql.undo import AddNodeOperation self.undo_manager.add_operation(AddNodeOperation(relation)) - #assert check_relations(self) return relation def add_constant_restriction(self, var, rtype, value, ctype, @@ -161,19 +165,116 @@ etype = iter(etype).next() # may be a set return self.add_constant_restriction(var, 'is', etype, 'etype') +# keywordsection nodes ######################################################## + +# class KWNode(Node): +# __slots__ = () + +# def as_string(self, encoding=None, kwargs=None): +# return '%s %s' % (self.__class__.__name__.upper(), +# ', '.join(child.as_string(encoding, kwargs) +# for child in self.children)) +# def __repr__(self): +# return '%s %s' % (self.__class__.__name__.upper(), +# ', '.join(repr(child) for child in self.children)) + +# class Where(BaseNode): +# """WHERE clause""" +# __slots__ = ('node',) +# def __init__(self, node): +# self.node = node +# node.parent = self +# @property +# def children(self): +# return (self.node,) + + +# def copy(self, stmt): +# """create and return a copy of this node and its descendant + +# stmt is the root node, which should be use to get new variables +# """ +# return Where(self.node.copy(stmt)) + +# def accept(self, visitor, *args, **kwargs): +# return visitor.visit_where(self, *args, **kwargs) +# def leave(self, visitor, *args, **kwargs): +# return visitor.leave_where(self, *args, **kwargs) +# def as_string(self, encoding=None, kwargs=None): +# return 'WHERE %s' % ', '.join(child.as_string(encoding, kwargs) +# for child in self.children) +# return 'WHERE %s' % ', '.join(repr(child) for child in self.children) + + +# class GroupBy(KWNode): +# """GROUPBY clause""" +# __slots__ = () +# def accept(self, visitor, *args, **kwargs): +# return visitor.visit_groupby(self, *args, **kwargs) +# def leave(self, visitor, *args, **kwargs): +# return visitor.leave_groupby(self, *args, **kwargs) + + +# class Having(KWNode): +# """HAVING clause""" +# __slots__ = () +# def accept(self, visitor, *args, **kwargs): +# return visitor.visit_having(self, *args, **kwargs) +# def leave(self, visitor, *args, **kwargs): +# return visitor.leave_having(self, *args, **kwargs) + + +# class OrderBy(KWNode): +# """ORDERBY clause""" +# __slots__ = () +# def accept(self, visitor, *args, **kwargs): +# return visitor.visit_orderby(self, *args, **kwargs) +# def leave(self, visitor, *args, **kwargs): +# return visitor.leave_orderby(self, *args, **kwargs) + + +# class With(KWNode): +# """WITH clause""" +# __slots__ = () +# def accept(self, visitor, *args, **kwargs): +# return visitor.visit_sort(self, *args, **kwargs) +# def leave(self, visitor, *args, **kwargs): +# return visitor.leave_sort(self, *args, **kwargs) + # base RQL nodes ############################################################## -class AND(BinaryNode): +class SubQuery(BaseNode): + """WITH clause""" + __slots__ = ('aliases', 'query') + def set_aliases(self, aliases): + self.aliases = aliases + for node in aliases: + node.parent = self + + def set_query(self, node): + self.query = node + node.parent = self + + def copy(self, stmt): + self.set_aliases([v.copy(stmt) for v in self.aliases]) + self.set_query(self.query.copy()) + + @property + def children(self): + return self.aliases + [self.query] + + def as_string(self, encoding=None, kwargs=None): + return '%s BEING (%s)' % (','.join(v.name for v in self.aliases), + self.query.as_string()) + def __repr__(self): + return '%s BEING (%s)' % (','.join(repr(v) for v in self.aliases), + repr(self.query)) + +class And(BinaryNode): """a logical AND node (binary)""" __slots__ = () - def accept(self, visitor, *args, **kwargs): - return visitor.visit_and(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_and(self, *args, **kwargs) - def as_string(self, encoding=None, kwargs=None): """return the tree as an encoded rql string""" return '%s, %s' % (self.children[0].as_string(encoding, kwargs), @@ -187,16 +288,10 @@ return self.parent.neged_rel(_fromnode or self) -class OR(BinaryNode): +class Or(BinaryNode): """a logical OR node (binary)""" __slots__ = () - def accept(self, visitor, *args, **kwargs): - return visitor.visit_or(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_or(self, *args, **kwargs) - def as_string(self, encoding=None, kwargs=None): return '(%s) OR (%s)' % (self.children[0].as_string(encoding, kwargs), self.children[1].as_string(encoding, kwargs)) @@ -213,11 +308,6 @@ class Not(Node): """a logical NOT node (unary)""" __slots__ = () - def accept(self, visitor, *args, **kwargs): - return visitor.visit_not(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_not(self, *args, **kwargs) def as_string(self, encoding=None, kwargs=None): if isinstance(self.children[0], (Exists, Relation)): @@ -233,34 +323,35 @@ return self -class Exists(EditableMixIn, Node): +class Exists(EditableMixIn, BaseNode): """EXISTS sub query""" - __slots__ = () + __slots__ = ('query',) def __init__(self, restriction=None): - Node.__init__(self) if restriction is not None: - self.append(restriction) + self.set_where(restriction) + @property + def children(self): + return (self.query,) + def is_equivalent(self, other): raise NotImplementedError - - def accept(self, visitor, *args, **kwargs): - return visitor.visit_exists(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_exists(self, *args, **kwargs) - + def as_string(self, encoding=None, kwargs=None): - content = self.children and self.children[0].as_string(encoding, kwargs) + content = self.query and self.query.as_string(encoding, kwargs) return 'EXISTS(%s)' % content def __repr__(self): - content = self.children and repr(self.children[0]) - return 'EXISTS(%s)' % content + return 'EXISTS(%s)' % repr(self.query) - def get_restriction(self): - return self.children[0] + def set_where(self, node): + self.query = node + node.parent = self + + @property + def where(self): + return self.query @property def scope(self): @@ -298,12 +389,6 @@ return False return True - def accept(self, visitor, *args, **kwargs): - return visitor.visit_relation( self, *args, **kwargs ) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_relation( self, *args, **kwargs ) - def as_string(self, encoding=None, kwargs=None): """return the tree as an encoded rql string""" try: @@ -342,7 +427,9 @@ def ored_rel(self, _fromnode=None): return self.parent.ored_rel(_fromnode or self) - def neged_rel(self, _fromnode=None): + def neged_rel(self, _fromnode=None, strict=False): + if strict: + return isinstance(self.parent, Not) return self.parent.neged_rel(_fromnode or self) def is_types_restriction(self): @@ -417,12 +504,6 @@ return False return self.operator == other.operator - def accept(self, visitor, *args, **kwargs): - return visitor.visit_comparison( self, *args, **kwargs ) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_comparison( self, *args, **kwargs ) - def as_string(self, encoding=None, kwargs=None): """return the tree as an encoded rql string""" if len(self.children) == 0: @@ -457,12 +538,6 @@ return False return self.operator == other.operator - def accept(self, visitor, *args, **kwargs): - return visitor.visit_mathexpression(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_mathexpression(self, *args, **kwargs) - def as_string(self, encoding=None, kwargs=None): """return the tree as an encoded rql string""" return '(%s %s %s)' % (self.children[0].as_string(encoding, kwargs), @@ -529,12 +604,6 @@ return False return self.name == other.name - def accept(self, visitor, *args, **kwargs): - return visitor.visit_function(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_function(self, *args, **kwargs) - def as_string(self, encoding=None, kwargs=None): """return the tree as an encoded rql string""" return '%s(%s)' % (self.name, ', '.join(c.as_string(encoding, kwargs) @@ -587,12 +656,6 @@ return False return self.type == other.type and self.value == other.value - def accept(self, visitor, *args, **kwargs): - return visitor.visit_constant(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_constant(self, *args, **kwargs) - def as_string(self, encoding=None, kwargs=None): """return the tree as an encoded rql string (an unicode string is returned if encoding is None) @@ -667,12 +730,6 @@ return False return self.name == other.name - def accept(self, visitor, *args, **kwargs): - return visitor.visit_variableref(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_variableref(self, *args, **kwargs) - def as_string(self, encoding=None, kwargs=None): """return the tree as an encoded rql string""" return self.name @@ -694,50 +751,6 @@ def get_description(self): return self.variable.get_description() - -class KWNode(Node): - __slots__ = () - def as_string(self, encoding=None, kwargs=None): - return '%s %s' % (self.keyword, - ', '.join(child.as_string(encoding, kwargs) - for child in self.children)) - - def __repr__(self): - return '%s %s' % (self.keyword, - ', '.join(repr(child) for child in self.children)) - -class Group(KWNode): - """a group (GROUPBY) node""" - __slots__ = () - keyword = 'GROUPBY' - def accept(self, visitor, *args, **kwargs): - return visitor.visit_group(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_group(self, *args, **kwargs) - - -class Having(KWNode): - """a having (HAVING) node""" - __slots__ = () - keyword = 'HAVING' - def accept(self, visitor, *args, **kwargs): - return visitor.visit_having(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_having(self, *args, **kwargs) - - -class Sort(KWNode): - """a sort (ORDERBY) node""" - __slots__ = () - keyword = 'ORDERBY' - - def accept(self, visitor, *args, **kwargs): - return visitor.visit_sort(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_sort(self, *args, **kwargs) class SortTerm(Node): @@ -772,12 +785,6 @@ return '%r ASC' % self.term return '%r DESC' % self.term - def accept(self, visitor, *args, **kwargs): - return visitor.visit_sortterm(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_sortterm(self, *args, **kwargs) - @property def term(self): return self.children[0] @@ -805,12 +812,6 @@ return solution[self.name] return 'Any' - def accept(self, visitor, *args, **kwargs): - return visitor.visit_columnalias(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_columnalias(self, *args, **kwargs) - # def as_string(self, encoding=None, kwargs=None): # return self.alias @@ -869,18 +870,6 @@ def __repr__(self): return '%s(%#X)' % (self.name, id(self)) - def accept(self, visitor, *args, **kwargs): - """though variable are not actually tree nodes, they may be visited in - some cases - """ - return visitor.visit_variable(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - """though variable are not actually tree nodes, they may be visited in - some cases - """ - return visitor.leave_variable(self, *args, **kwargs) - def set_scope(self, scopenode): if scopenode is self.stmt or self.stinfo['scope'] is None: self.stinfo['scope'] = scopenode @@ -920,10 +909,9 @@ """return the index of this variable in the selection if it's selected, else None """ - for i, term in enumerate(self.stmt.selected_terms()): - for node in term.iget_nodes(VariableRef): - if node.variable is self: - return i + if not self.stinfo['selected']: + return None + return iter(self.stinfo['selected']).next() @property def schema(self): @@ -989,3 +977,6 @@ return rel return None +build_visitor_stub((SubQuery, And, Or, Not, Exists, Relation, + Comparison, MathExpression, Function, Constant, + VariableRef, SortTerm, ColumnAlias, Variable)) diff --git a/parser.g b/parser.g --- a/parser.g +++ b/parser.g @@ -3,14 +3,50 @@ :organization: Logilab :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" + + +Select statement grammar +------------------------ + +query = <squery> | <union> + +union = (<squery>) UNION (<squery>) [UNION (<squery>)]* + +squery = Any <selection> + [GROUPBY <variables>] + [ORDERBY <sortterms>] + [WHERE <restriction>] + [HAVING <aggregat restriction>] + [WITH <subquery> [,<subquery]*] + +subquery = <variables> BEING (<query>) + +variables = <variable> [, <variable>]* -from rql.stmts import Union, Select, Delete, Insert, Update -from rql.nodes import * +Abbreviations in this code +-------------------------- -_OR = OR -_AND = AND +rules: +* rel -> relation +* decl -> declaration +* expr -> expression +* restr -> restriction +* var -> variable +* func -> function +* const -> constant +* cmp -> comparison + +variables: +* R -> syntax tree root +* S -> select node +* P -> parent node + +""" + +from warnings import warn +from rql.stmts import Union, Select, Delete, Insert, Set +from rql.nodes import * def unquote(string): @@ -32,9 +68,9 @@ token INSERT: r'(?i)INSERT' token UNION: r'(?i)UNION' token DISTINCT: r'(?i)DISTINCT' - token FROM: r'(?i)FROM' + token WITH: r'(?i)WITH' token WHERE: r'(?i)WHERE' - token AS: r'(?i)AS' + token BEING: r'(?i)BEING' token OR: r'(?i)OR' token AND: r'(?i)AND' token NOT: r'(?i)NOT' @@ -68,116 +104,102 @@ # Grammar entry ############################################################### -# -# abbreviations : -# -# rel -> relation -# decl -> declaration -# expr -> expression -# restr -> restriction -# var -> variable -# func -> function -# const -> constant -# cmp -> comparison + + +rule goal: DELETE _delete<<Delete()>> ';' {{ return _delete }} -rule goal: DELETE _delete<<Delete()>> ';' {{ return _delete }} - - | INSERT _insert<<Insert()>> ';' {{ return _insert }} + | INSERT _insert<<Insert()>> ';' {{ return _insert }} - | SET update<<Update()>> ';' {{ return update }} + | SET update<<Set()>> ';' {{ return update }} - | union - sort<<union>> limit_offset<<union>> ';' {{ return union }} + | union<<Union()>> limit_offset<<union>> ';' {{ return union }} # Deletion ################################################################### -rule _delete<<V>>: rels_decl<<V>> restr<<V>> {{ return V }} +rule _delete<<R>>: decl_rels<<R>> where<<R>> {{ return R }} - | vars_decl<<V>> restr<<V>> {{ return V }} + | decl_vars<<R>> where<<R>> {{ return R }} # Insertion ################################################################## -rule _insert<<V>>: vars_decl<<V>> insert_rels<<V>> {{ return V }} +rule _insert<<R>>: decl_vars<<R>> insert_rels<<R>> {{ return R }} -rule insert_rels<<V>>: ":" rels_decl<<V>> restr<<V>> {{ return V }} +rule insert_rels<<R>>: ":" decl_rels<<R>> where<<R>> {{ return R }} | # Update ##################################################################### -rule update<<V>>: rels_decl<<V>> restr<<V>> {{ return V }} +rule update<<R>>: decl_rels<<R>> where<<R>> {{ return R }} # Selection ################################################################## -rule union: select<<Select()>> {{ root = Union(); root.append(select) }} - ( UNION select<<Select()>> {{ root.append(select) }} - )* {{ return root }} +rule union<<R>>: select<<Select()>> {{ R.append(select); return R }} -rule select<<V>>: _select<<V>> {{ return _select }} - | r"\(" _select<<V>> r"\)" {{ return _select }} - -rule _select<<V>>: DISTINCT select_base<<V>> {{ V.distinct = True ; return V }} - | select_base<<V>> {{ return V }} + | r"\(" select<<Select()>> r"\)" {{ R.append(select) }} + ( UNION + r"\(" select<<Select()>> r"\)" {{ R.append(select) }} + )* {{ return R }} - -rule select_base<<V>>: E_TYPE selected_terms<<V>> select_from<<V>> restr<<V>> - group<<V>> having<<V>> {{ V.set_statement_type(E_TYPE) ; return V }} - -rule select_from<<V>>: FROM subqueries<<V>> - | +rule select<<S>>: DISTINCT select_<<S>> {{ S.distinct = True ; return S }} + | select_<<S>> {{ return S }} -rule subqueries<<V>>: subquery<<V>> ( - ',' subquery<<V>> - )* - - -rule subquery<<V>>: r"\(" union r"\)" AS aliases {{ V.add_subquery(union, aliases) }} +rule select_<<S>>: E_TYPE selection<<S>> + groupby<<S>> + orderby<<S>> + where<<S>> + having<<S>> + with_<<S>> + dgroupby<<S>> + dorderby<<S>> {{ S.set_statement_type(E_TYPE); return S }} -rule aliases: VARIABLE {{ return [VARIABLE] }} - | r"\(" VARIABLE ( {{ variables = [VARIABLE] }} - ',' VARIABLE {{ variables.append(VARIABLE) }} - )* - r"\)" {{ return variables }} - -rule selected_terms<<V>>: added_expr<<V>> ( {{ V.append_selected(added_expr) }} - ',' added_expr<<V>> - )* {{ V.append_selected(added_expr) }} +rule selection<<S>>: expr_add<<S>> {{ S.append_selected(expr_add) }} + ( ',' expr_add<<S>> {{ S.append_selected(expr_add) }} + )* -# Groups and sorts ############################################################ +# other clauses (groupby, orderby, with, having) ############################## + +# to remove in rql 1.0 +rule dorderby<<S>>: orderby<<S>> {{ if orderby: warn('ORDERBY is now before WHERE clause') }} +rule dgroupby<<S>>: groupby<<S>> {{ if groupby: warn('GROUPBY is now before WHERE clause') }} -rule group<<V>>: GROUPBY {{ G = Group() }} - var<<V>> ( {{ G.append(var) }} - ',' var<<V>> - )* {{ G.append(var) ; V.append(G) }} - +rule groupby<<S>>: GROUPBY variables<<S>> {{ S.set_groupby(variables); return True }} + | + +rule having<<S>>: HAVING {{ nodes = [] }} + expr_cmp<<S>> {{ nodes.append(expr_cmp) }} + ( ',' expr_cmp<<S>> {{ nodes.append(expr_cmp) }} + )* {{ S.set_having(nodes) }} + | + +rule orderby<<S>>: ORDERBY {{ nodes = [] }} + sort_term<<S>> {{ nodes.append(sort_term) }} + ( ',' sort_term<<S>> {{ nodes.append(sort_term) }} + )* {{ S.set_orderby(nodes); return True }} | -rule having<<V>>: HAVING {{ G = Having() }} - cmp_expr<<V>> ( {{ G.append(cmp_expr) }} - ',' cmp_expr<<V>> - )* {{ G.append(cmp_expr) ; V.append(G) }} - - | +rule with_<<S>>: WITH {{ nodes = [] }} + subquery<<S>> {{ nodes.append(subquery) }} + ( ',' subquery<<S>> {{ nodes.append(subquery) }} + )* {{ S.set_with(nodes) }} + | -rule cmp_expr<<V>>: added_expr<<V>> {{ c1 = added_expr }} - CMP_OP {{ cmp = Comparison(CMP_OP.upper(), c1); }} - added_expr<<V>> {{ cmp.append(added_expr); return cmp }} +rule subquery<<S>>: variables<<S>> {{ node = SubQuery() ; node.set_aliases(variables) }} + BEING r"\(" union<<Union()>> r"\)" {{ node.set_query(union); return node }} + -rule sort<<V>>: ORDERBY {{ S = Sort(); V.set_sortterms(S) }} - sort_term<<V>> ( {{ S.append(sort_term) }} - ',' sort_term<<V>> - )* {{ S.append(sort_term) }} +rule expr_cmp<<S>>: expr_add<<S>> {{ c1 = expr_add }} + CMP_OP {{ cmp = Comparison(CMP_OP.upper(), c1) }} + expr_add<<S>> {{ cmp.append(expr_add); return cmp }} + - | - - -rule sort_term<<V>>: added_expr<<V>> sort_meth {{ return SortTerm(added_expr, sort_meth) }} +rule sort_term<<S>>: expr_add<<S>> sort_meth {{ return SortTerm(expr_add, sort_meth) }} rule sort_meth: SORT_DESC {{ return 0 }} @@ -189,110 +211,101 @@ # Limit and offset ############################################################ -rule limit_offset<<V>> : limit<<V>> offset<<V>> +rule limit_offset<<R>> : limit<<R>> offset<<R>> -rule limit<<V>> : LIMIT INT {{ V.set_limit(int(INT)) }} +rule limit<<R>> : LIMIT INT {{ R.set_limit(int(INT)) }} | - -rule offset<<V>> : OFFSET INT {{ V.set_offset(int(INT)) }} +rule offset<<R>> : OFFSET INT {{ R.set_offset(int(INT)) }} | # Restriction statements ###################################################### -rule restr<<V>>: WHERE rels<<V>> {{ V.append(rels) }} - +rule where<<S>>: WHERE restriction<<S>> {{ S.set_where(restriction) }} | -rule rels<<V>>: ored_rels<<V>> {{ lhs = ored_rels }} - ( ',' ored_rels<<V>> {{ lhs = AND(lhs, ored_rels) }} - )* {{ return lhs }} +rule restriction<<S>>: rels_or<<S>> {{ node = rels_or }} + ( ',' rels_or<<S>> {{ node = And(node, rels_or) }} + )* {{ return node }} - +rule rels_or<<S>>: rels_and<<S>> {{ node = rels_and }} + ( OR rels_and<<S>> {{ node = Or(node, rels_and) }} + )* {{ return node }} -rule ored_rels<<V>>: anded_rels<<V>> {{ lhs = anded_rels }} - ( OR anded_rels<<V>> {{ lhs = _OR(lhs,anded_rels) }} - )* {{ return lhs }} - -rule anded_rels<<V>>: not_rels<<V>> {{ lhs = not_rels }} - ( AND not_rels<<V>> {{ lhs = _AND(lhs, not_rels) }} - )* {{ return lhs }} +rule rels_and<<S>>: rels_not<<S>> {{ node = rels_not }} + ( AND rels_not<<S>> {{ node = And(node, rels_not) }} + )* {{ return node }} -rule not_rels<<V>>: NOT rel<<V>> {{ not_ = Not(); not_.append(rel); return not_ }} - - | rel<<V>> {{ return rel }} +rule rels_not<<S>>: NOT rel<<S>> {{ node = Not(); node.append(rel); return node }} + | rel<<S>> {{ return rel }} + +rule rel<<S>>: rel_base<<S>> {{ return rel_base }} + | r"\(" restriction<<S>> r"\)" {{ return restriction }} -rule rel<<V>>: base_rel<<V>> {{ return base_rel }} - - | r"\(" rels<<V>> r"\)" {{ return rels }} - +rule rel_base<<S>>: var<<S>> opt_left<<S>> rtype {{ rtype.append(var) ; rtype.set_optional(opt_left) }} + expr<<S>> opt_right<<S>> {{ rtype.append(expr) ; rtype.set_optional(opt_right) ; return rtype }} + | EXISTS r"\(" restriction<<S>> r"\)" {{ return Exists(restriction) }} + +rule rtype: R_TYPE {{ return Relation(R_TYPE) }} -rule base_rel<<V>>: var<<V>> opt_left<<V>> rtype<<V>> {{ rtype.append(var) ; rtype.set_optional(opt_left) }} - expr<<V>> opt_right<<V>> {{ rtype.append(expr) ; rtype.set_optional(opt_right) ; return rtype }} - | EXISTS r"\(" rels<<V>> r"\)" {{ return Exists(rels) }} - - -rule rtype<<V>>: R_TYPE {{ return Relation(R_TYPE) }} - -rule opt_left<<V>>: QMARK {{ return 'left' }} - | -rule opt_right<<V>>: QMARK {{ return 'right' }} +rule opt_left<<S>>: QMARK {{ return 'left' }} + | +rule opt_right<<S>>: QMARK {{ return 'right' }} | # common statements ########################################################### -rule vars_decl<<V>>: E_TYPE var<<V>> ( {{ V.add_main_variable(E_TYPE, var) }} - ',' E_TYPE var<<V>>)* {{ V.add_main_variable(E_TYPE, var) }} - - -rule rels_decl<<V>>: simple_rel<<V>> ( {{ V.add_main_relation(simple_rel) }} - ',' simple_rel<<V>>)* {{ V.add_main_relation(simple_rel) }} +rule variables<<S>>: {{ vars = [] }} + var<<S>> {{ vars.append(var) }} + ( ',' var<<S>> {{ vars.append(var) }} + )* {{ return vars }} + +rule decl_vars<<R>>: E_TYPE var<<R>> ( {{ R.add_main_variable(E_TYPE, var) }} + ',' E_TYPE var<<R>>)* {{ R.add_main_variable(E_TYPE, var) }} -rule simple_rel<<V>>: var<<V>> R_TYPE {{ e = Relation(R_TYPE) ; e.append(var) }} - added_expr<<V>> {{ e.append(Comparison('=', added_expr)) ; return e }} +rule decl_rels<<R>>: simple_rel<<R>> ( {{ R.add_main_relation(simple_rel) }} + ',' simple_rel<<R>>)* {{ R.add_main_relation(simple_rel) }} -rule expr<<V>>: CMP_OP added_expr<<V>> {{ return Comparison(CMP_OP.upper(), added_expr) }} - - | added_expr<<V>> {{ return Comparison('=', added_expr) }} +rule simple_rel<<R>>: var<<R>> R_TYPE {{ e = Relation(R_TYPE) ; e.append(var) }} + expr_add<<R>> {{ e.append(Comparison('=', expr_add)) ; return e }} -rule added_expr<<V>>: muled_expr<<V>> {{ lhs = muled_expr }} - ( ADD_OP muled_expr<<V>> {{ lhs = MathExpression( ADD_OP, lhs, muled_expr ) }} - )* {{ return lhs }} +rule expr<<S>>: CMP_OP expr_add<<S>> {{ return Comparison(CMP_OP.upper(), expr_add) }} + + | expr_add<<S>> {{ return Comparison('=', expr_add) }} -rule muled_expr<<V>>: base_expr<<V>> {{ lhs = base_expr }} - ( MUL_OP base_expr<<V>> {{ lhs = MathExpression( MUL_OP, lhs, base_expr) }} - )* {{ return lhs }} +rule expr_add<<S>>: expr_mul<<S>> {{ node = expr_mul }} + ( ADD_OP expr_mul<<S>> {{ node = MathExpression( ADD_OP, node, expr_mul ) }} + )* {{ return node }} + + +rule expr_mul<<S>>: expr_base<<S>> {{ node = expr_base }} + ( MUL_OP expr_base<<S>> {{ node = MathExpression( MUL_OP, node, expr_base) }} + )* {{ return node }} -rule base_expr<<V>>: const {{ return const }} - - | var<<V>> {{ return var }} - - | etype<<V>> {{ return etype }} - - | func<<V>> {{ return func }} - - | r"\(" added_expr<<V>> r"\)" {{ return added_expr }} +rule expr_base<<S>>: const {{ return const }} + | var<<S>> {{ return var }} + | etype<<S>> {{ return etype }} + | func<<S>> {{ return func }} + | r"\(" expr_add<<S>> r"\)" {{ return expr_add }} -rule func<<V>>: FUNCTION r"\(" {{ F = Function(FUNCTION) }} - added_expr<<V>> ( {{ F.append(added_expr) }} - ',' added_expr<<V>> - )* {{ F.append(added_expr) }} - r"\)" {{ return F }} +rule func<<S>>: FUNCTION r"\(" {{ F = Function(FUNCTION) }} + expr_add<<S>> ( {{ F.append(expr_add) }} + ',' expr_add<<S>> + )* {{ F.append(expr_add) }} + r"\)" {{ return F }} -rule var<<V>>: VARIABLE {{ return VariableRef(V.get_variable(VARIABLE)) }} +rule var<<S>>: VARIABLE {{ return VariableRef(S.get_variable(VARIABLE)) }} -# | COLALIAS {{ return ColumnAlias(COLALIAS) }} - -rule etype<<V>>: E_TYPE {{ return V.get_etype(E_TYPE) }} +rule etype<<S>>: E_TYPE {{ return S.get_etype(E_TYPE) }} rule const: NULL {{ return Constant(None, None) }} diff --git a/parser.py b/parser.py --- a/parser.py +++ b/parser.py @@ -3,14 +3,50 @@ :organization: Logilab :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" + + +Select statement grammar +------------------------ + +query = <squery> | <union> + +union = (<squery>) UNION (<squery>) [UNION (<squery>)]* + +squery = Any <selection> + [GROUPBY <variables>] + [ORDERBY <sortterms>] + [WHERE <restriction>] + [HAVING <aggregat restriction>] + [WITH <subquery> [,<subquery]*] + +subquery = <variables> BEING (<query>) + +variables = <variable> [, <variable>]* -from rql.stmts import Union, Select, Delete, Insert, Update -from rql.nodes import * +Abbreviations in this code +-------------------------- -_OR = OR -_AND = AND +rules: +* rel -> relation +* decl -> declaration +* expr -> expression +* restr -> restriction +* var -> variable +* func -> function +* const -> constant +* cmp -> comparison + +variables: +* R -> syntax tree root +* S -> select node +* P -> parent node + +""" + +from warnings import warn +from rql.stmts import Union, Select, Delete, Insert, Set +from rql.nodes import * def unquote(string): @@ -38,9 +74,9 @@ ('INSERT', re.compile('(?i)INSERT')), ('UNION', re.compile('(?i)UNION')), ('DISTINCT', re.compile('(?i)DISTINCT')), - ('FROM', re.compile('(?i)FROM')), + ('WITH', re.compile('(?i)WITH')), ('WHERE', re.compile('(?i)WHERE')), - ('AS', re.compile('(?i)AS')), + ('BEING', re.compile('(?i)BEING')), ('OR', re.compile('(?i)OR')), ('AND', re.compile('(?i)AND')), ('NOT', re.compile('(?i)NOT')), @@ -91,330 +127,330 @@ return _insert elif _token == 'SET': SET = self._scan('SET', context=_context) - update = self.update(Update(), _context) + update = self.update(Set(), _context) self._scan("';'", context=_context) return update else: # in ['r"\\("', 'DISTINCT', 'E_TYPE'] - union = self.union(_context) - sort = self.sort(union, _context) + union = self.union(Union(), _context) limit_offset = self.limit_offset(union, _context) self._scan("';'", context=_context) return union - def _delete(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, '_delete', [V]) + def _delete(self, R, _parent=None): + _context = self.Context(_parent, self._scanner, '_delete', [R]) _token = self._peek('E_TYPE', 'VARIABLE', context=_context) if _token == 'VARIABLE': - rels_decl = self.rels_decl(V, _context) - restr = self.restr(V, _context) - return V + decl_rels = self.decl_rels(R, _context) + where = self.where(R, _context) + return R else: # == 'E_TYPE' - vars_decl = self.vars_decl(V, _context) - restr = self.restr(V, _context) - return V + decl_vars = self.decl_vars(R, _context) + where = self.where(R, _context) + return R - def _insert(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, '_insert', [V]) - vars_decl = self.vars_decl(V, _context) - insert_rels = self.insert_rels(V, _context) - return V + def _insert(self, R, _parent=None): + _context = self.Context(_parent, self._scanner, '_insert', [R]) + decl_vars = self.decl_vars(R, _context) + insert_rels = self.insert_rels(R, _context) + return R - def insert_rels(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'insert_rels', [V]) + def insert_rels(self, R, _parent=None): + _context = self.Context(_parent, self._scanner, 'insert_rels', [R]) _token = self._peek('":"', "';'", context=_context) if _token == '":"': self._scan('":"', context=_context) - rels_decl = self.rels_decl(V, _context) - restr = self.restr(V, _context) - return V + decl_rels = self.decl_rels(R, _context) + where = self.where(R, _context) + return R else: # == "';'" pass - def update(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'update', [V]) - rels_decl = self.rels_decl(V, _context) - restr = self.restr(V, _context) - return V + def update(self, R, _parent=None): + _context = self.Context(_parent, self._scanner, 'update', [R]) + decl_rels = self.decl_rels(R, _context) + where = self.where(R, _context) + return R - def union(self, _parent=None): - _context = self.Context(_parent, self._scanner, 'union', []) - select = self.select(Select(), _context) - root = Union(); root.append(select) - while self._peek('UNION', 'r"\\)"', 'ORDERBY', "';'", 'LIMIT', 'OFFSET', context=_context) == 'UNION': - UNION = self._scan('UNION', context=_context) - select = self.select(Select(), _context) - root.append(select) - return root - - def select(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'select', [V]) + def union(self, R, _parent=None): + _context = self.Context(_parent, self._scanner, 'union', [R]) _token = self._peek('r"\\("', 'DISTINCT', 'E_TYPE', context=_context) if _token != 'r"\\("': - _select = self._select(V, _context) - return _select + select = self.select(Select(), _context) + R.append(select); return R else: # == 'r"\\("' self._scan('r"\\("', context=_context) - _select = self._select(V, _context) + select = self.select(Select(), _context) self._scan('r"\\)"', context=_context) - return _select + R.append(select) + while self._peek('UNION', 'r"\\)"', "';'", 'LIMIT', 'OFFSET', context=_context) == 'UNION': + UNION = self._scan('UNION', context=_context) + self._scan('r"\\("', context=_context) + select = self.select(Select(), _context) + self._scan('r"\\)"', context=_context) + R.append(select) + return R - def _select(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, '_select', [V]) + def select(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'select', [S]) _token = self._peek('DISTINCT', 'E_TYPE', context=_context) if _token == 'DISTINCT': DISTINCT = self._scan('DISTINCT', context=_context) - select_base = self.select_base(V, _context) - V.distinct = True ; return V + select_ = self.select_(S, _context) + S.distinct = True ; return S else: # == 'E_TYPE' - select_base = self.select_base(V, _context) - return V - - def select_base(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'select_base', [V]) - E_TYPE = self._scan('E_TYPE', context=_context) - selected_terms = self.selected_terms(V, _context) - select_from = self.select_from(V, _context) - restr = self.restr(V, _context) - group = self.group(V, _context) - having = self.having(V, _context) - V.set_statement_type(E_TYPE) ; return V + select_ = self.select_(S, _context) + return S - def select_from(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'select_from', [V]) - _token = self._peek('FROM', 'WHERE', "','", 'GROUPBY', 'HAVING', "';'", 'r"\\)"', 'UNION', 'ORDERBY', 'LIMIT', 'OFFSET', context=_context) - if _token == 'FROM': - FROM = self._scan('FROM', context=_context) - subqueries = self.subqueries(V, _context) - else: - pass - - def subqueries(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'subqueries', [V]) - subquery = self.subquery(V, _context) - while self._peek("','", 'WHERE', 'GROUPBY', 'HAVING', "';'", 'r"\\)"', 'UNION', 'ORDERBY', 'LIMIT', 'OFFSET', context=_context) == "','": - self._scan("','", context=_context) - subquery = self.subquery(V, _context) + def select_(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'select_', [S]) + E_TYPE = self._scan('E_TYPE', context=_context) + selection = self.selection(S, _context) + groupby = self.groupby(S, _context) + orderby = self.orderby(S, _context) + where = self.where(S, _context) + having = self.having(S, _context) + with_ = self.with_(S, _context) + dgroupby = self.dgroupby(S, _context) + dorderby = self.dorderby(S, _context) + S.set_statement_type(E_TYPE); return S - def subquery(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'subquery', [V]) - self._scan('r"\\("', context=_context) - union = self.union(_context) - self._scan('r"\\)"', context=_context) - AS = self._scan('AS', context=_context) - aliases = self.aliases(_context) - V.add_subquery(union, aliases) + def selection(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'selection', [S]) + expr_add = self.expr_add(S, _context) + S.append_selected(expr_add) + while self._peek("','", 'GROUPBY', 'ORDERBY', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) == "','": + self._scan("','", context=_context) + expr_add = self.expr_add(S, _context) + S.append_selected(expr_add) - def aliases(self, _parent=None): - _context = self.Context(_parent, self._scanner, 'aliases', []) - _token = self._peek('VARIABLE', 'r"\\("', context=_context) - if _token == 'VARIABLE': - VARIABLE = self._scan('VARIABLE', context=_context) - return [VARIABLE] - else: # == 'r"\\("' - self._scan('r"\\("', context=_context) - VARIABLE = self._scan('VARIABLE', context=_context) - while self._peek('r"\\)"', "','", context=_context) == "','": - variables = [VARIABLE] - self._scan("','", context=_context) - VARIABLE = self._scan('VARIABLE', context=_context) - variables.append(VARIABLE) - self._scan('r"\\)"', context=_context) - return variables + def dorderby(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'dorderby', [S]) + orderby = self.orderby(S, _context) + if orderby: warn('ORDERBY is now before WHERE clause') - def selected_terms(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'selected_terms', [V]) - added_expr = self.added_expr(V, _context) - while self._peek("','", 'r"\\)"', 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'FROM', 'WHERE', 'GROUPBY', 'QMARK', 'HAVING', "';'", 'LIMIT', 'OFFSET', 'UNION', 'AND', 'ORDERBY', 'OR', context=_context) == "','": - V.append_selected(added_expr) - self._scan("','", context=_context) - added_expr = self.added_expr(V, _context) - V.append_selected(added_expr) + def dgroupby(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'dgroupby', [S]) + groupby = self.groupby(S, _context) + if groupby: warn('GROUPBY is now before WHERE clause') - def group(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'group', [V]) - _token = self._peek('GROUPBY', 'HAVING', 'r"\\)"', 'UNION', 'ORDERBY', "';'", 'LIMIT', 'OFFSET', context=_context) + def groupby(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'groupby', [S]) + _token = self._peek('GROUPBY', 'ORDERBY', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) if _token == 'GROUPBY': GROUPBY = self._scan('GROUPBY', context=_context) - G = Group() - var = self.var(V, _context) - while self._peek("','", 'R_TYPE', 'QMARK', 'HAVING', 'WHERE', '":"', 'MUL_OP', 'GROUPBY', "';'", 'r"\\)"', 'ADD_OP', 'UNION', 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'FROM', 'ORDERBY', 'LIMIT', 'OFFSET', 'AND', 'OR', context=_context) == "','": - G.append(var) - self._scan("','", context=_context) - var = self.var(V, _context) - G.append(var) ; V.append(G) + variables = self.variables(S, _context) + S.set_groupby(variables); return True + elif 1: + pass else: - pass + raise runtime.SyntaxError(_token[0], 'Could not match groupby') - def having(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'having', [V]) - _token = self._peek('HAVING', 'r"\\)"', 'UNION', 'ORDERBY', "';'", 'LIMIT', 'OFFSET', context=_context) + def having(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'having', [S]) + _token = self._peek('HAVING', 'WITH', 'GROUPBY', 'ORDERBY', 'WHERE', "';'", 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) if _token == 'HAVING': HAVING = self._scan('HAVING', context=_context) - G = Having() - cmp_expr = self.cmp_expr(V, _context) - while self._peek("','", 'r"\\)"', 'UNION', 'ORDERBY', "';'", 'LIMIT', 'OFFSET', context=_context) == "','": - G.append(cmp_expr) + nodes = [] + expr_cmp = self.expr_cmp(S, _context) + nodes.append(expr_cmp) + while self._peek("','", 'WITH', 'GROUPBY', 'ORDERBY', 'WHERE', 'HAVING', "';'", 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) == "','": self._scan("','", context=_context) - cmp_expr = self.cmp_expr(V, _context) - G.append(cmp_expr) ; V.append(G) - else: # in ['r"\\)"', 'UNION', 'ORDERBY', "';'", 'LIMIT', 'OFFSET'] + expr_cmp = self.expr_cmp(S, _context) + nodes.append(expr_cmp) + S.set_having(nodes) + elif 1: pass + else: + raise runtime.SyntaxError(_token[0], 'Could not match having') - def cmp_expr(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'cmp_expr', [V]) - added_expr = self.added_expr(V, _context) - c1 = added_expr - CMP_OP = self._scan('CMP_OP', context=_context) - cmp = Comparison(CMP_OP.upper(), c1); - added_expr = self.added_expr(V, _context) - cmp.append(added_expr); return cmp - - def sort(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'sort', [V]) - _token = self._peek('ORDERBY', "';'", 'LIMIT', 'OFFSET', context=_context) + def orderby(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'orderby', [S]) + _token = self._peek('ORDERBY', 'WHERE', 'HAVING', 'WITH', "';'", 'GROUPBY', 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) if _token == 'ORDERBY': ORDERBY = self._scan('ORDERBY', context=_context) - S = Sort(); V.set_sortterms(S) - sort_term = self.sort_term(V, _context) - while self._peek("','", "';'", 'LIMIT', 'OFFSET', context=_context) == "','": - S.append(sort_term) + nodes = [] + sort_term = self.sort_term(S, _context) + nodes.append(sort_term) + while self._peek("','", 'WHERE', 'HAVING', 'WITH', "';'", 'GROUPBY', 'ORDERBY', 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) == "','": + self._scan("','", context=_context) + sort_term = self.sort_term(S, _context) + nodes.append(sort_term) + S.set_orderby(nodes); return True + elif 1: + pass + else: + raise runtime.SyntaxError(_token[0], 'Could not match orderby') + + def with_(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'with_', [S]) + _token = self._peek('WITH', 'GROUPBY', 'ORDERBY', 'WHERE', 'HAVING', "';'", 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) + if _token == 'WITH': + WITH = self._scan('WITH', context=_context) + nodes = [] + subquery = self.subquery(S, _context) + nodes.append(subquery) + while self._peek("','", 'GROUPBY', 'ORDERBY', 'WHERE', 'HAVING', 'WITH', "';'", 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) == "','": self._scan("','", context=_context) - sort_term = self.sort_term(V, _context) - S.append(sort_term) - else: # in ["';'", 'LIMIT', 'OFFSET'] + subquery = self.subquery(S, _context) + nodes.append(subquery) + S.set_with(nodes) + elif 1: pass + else: + raise runtime.SyntaxError(_token[0], 'Could not match with_') - def sort_term(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'sort_term', [V]) - added_expr = self.added_expr(V, _context) + def subquery(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'subquery', [S]) + variables = self.variables(S, _context) + node = SubQuery() ; node.set_aliases(variables) + BEING = self._scan('BEING', context=_context) + self._scan('r"\\("', context=_context) + union = self.union(Union(), _context) + self._scan('r"\\)"', context=_context) + node.set_query(union); return node + + def expr_cmp(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'expr_cmp', [S]) + expr_add = self.expr_add(S, _context) + c1 = expr_add + CMP_OP = self._scan('CMP_OP', context=_context) + cmp = Comparison(CMP_OP.upper(), c1) + expr_add = self.expr_add(S, _context) + cmp.append(expr_add); return cmp + + def sort_term(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'sort_term', [S]) + expr_add = self.expr_add(S, _context) sort_meth = self.sort_meth(_context) - return SortTerm(added_expr, sort_meth) + return SortTerm(expr_add, sort_meth) def sort_meth(self, _parent=None): _context = self.Context(_parent, self._scanner, 'sort_meth', []) - _token = self._peek('SORT_DESC', 'SORT_ASC', "','", "';'", 'LIMIT', 'OFFSET', context=_context) + _token = self._peek('SORT_DESC', 'SORT_ASC', "','", 'WHERE', 'HAVING', 'WITH', "';'", 'GROUPBY', 'ORDERBY', 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) if _token == 'SORT_DESC': SORT_DESC = self._scan('SORT_DESC', context=_context) return 0 elif _token == 'SORT_ASC': SORT_ASC = self._scan('SORT_ASC', context=_context) return 1 - else: # in ["','", "';'", 'LIMIT', 'OFFSET'] + else: return 1 # default to SORT_ASC - def limit_offset(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'limit_offset', [V]) - limit = self.limit(V, _context) - offset = self.offset(V, _context) + def limit_offset(self, R, _parent=None): + _context = self.Context(_parent, self._scanner, 'limit_offset', [R]) + limit = self.limit(R, _context) + offset = self.offset(R, _context) - def limit(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'limit', [V]) + def limit(self, R, _parent=None): + _context = self.Context(_parent, self._scanner, 'limit', [R]) _token = self._peek('LIMIT', 'OFFSET', "';'", context=_context) if _token == 'LIMIT': LIMIT = self._scan('LIMIT', context=_context) INT = self._scan('INT', context=_context) - V.set_limit(int(INT)) + R.set_limit(int(INT)) else: # in ['OFFSET', "';'"] pass - def offset(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'offset', [V]) + def offset(self, R, _parent=None): + _context = self.Context(_parent, self._scanner, 'offset', [R]) _token = self._peek('OFFSET', "';'", context=_context) if _token == 'OFFSET': OFFSET = self._scan('OFFSET', context=_context) INT = self._scan('INT', context=_context) - V.set_offset(int(INT)) + R.set_offset(int(INT)) else: # == "';'" pass - def restr(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'restr', [V]) - _token = self._peek('WHERE', 'GROUPBY', "';'", 'HAVING', 'r"\\)"', 'UNION', 'ORDERBY', 'LIMIT', 'OFFSET', context=_context) + def where(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'where', [S]) + _token = self._peek('WHERE', 'HAVING', "';'", 'WITH', 'GROUPBY', 'ORDERBY', 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) if _token == 'WHERE': WHERE = self._scan('WHERE', context=_context) - rels = self.rels(V, _context) - V.append(rels) + restriction = self.restriction(S, _context) + S.set_where(restriction) + elif 1: + pass else: - pass + raise runtime.SyntaxError(_token[0], 'Could not match where') - def rels(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'rels', [V]) - ored_rels = self.ored_rels(V, _context) - lhs = ored_rels - while self._peek("','", 'r"\\)"', 'GROUPBY', "';'", 'HAVING', 'UNION', 'ORDERBY', 'LIMIT', 'OFFSET', context=_context) == "','": + 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', 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', context=_context) == "','": self._scan("','", context=_context) - ored_rels = self.ored_rels(V, _context) - lhs = AND(lhs, ored_rels) - return lhs + rels_or = self.rels_or(S, _context) + node = And(node, rels_or) + return node - def ored_rels(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'ored_rels', [V]) - anded_rels = self.anded_rels(V, _context) - lhs = anded_rels - while self._peek('OR', "','", 'r"\\)"', 'GROUPBY', "';'", 'HAVING', 'UNION', 'ORDERBY', 'LIMIT', 'OFFSET', context=_context) == 'OR': + def rels_or(self, S, _parent=None): + _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', 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', context=_context) == 'OR': OR = self._scan('OR', context=_context) - anded_rels = self.anded_rels(V, _context) - lhs = _OR(lhs,anded_rels) - return lhs + rels_and = self.rels_and(S, _context) + node = Or(node, rels_and) + return node - def anded_rels(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'anded_rels', [V]) - not_rels = self.not_rels(V, _context) - lhs = not_rels - while self._peek('AND', 'OR', "','", 'r"\\)"', 'GROUPBY', "';'", 'HAVING', 'UNION', 'ORDERBY', 'LIMIT', 'OFFSET', context=_context) == 'AND': + def rels_and(self, S, _parent=None): + _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', 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', context=_context) == 'AND': AND = self._scan('AND', context=_context) - not_rels = self.not_rels(V, _context) - lhs = _AND(lhs, not_rels) - return lhs + rels_not = self.rels_not(S, _context) + node = And(node, rels_not) + return node - def not_rels(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'not_rels', [V]) + def rels_not(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'rels_not', [S]) _token = self._peek('NOT', 'r"\\("', 'EXISTS', 'VARIABLE', context=_context) if _token == 'NOT': NOT = self._scan('NOT', context=_context) - rel = self.rel(V, _context) - not_ = Not(); not_.append(rel); return not_ + rel = self.rel(S, _context) + node = Not(); node.append(rel); return node else: # in ['r"\\("', 'EXISTS', 'VARIABLE'] - rel = self.rel(V, _context) + rel = self.rel(S, _context) return rel - def rel(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'rel', [V]) + def rel(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'rel', [S]) _token = self._peek('r"\\("', 'EXISTS', 'VARIABLE', context=_context) if _token != 'r"\\("': - base_rel = self.base_rel(V, _context) - return base_rel + rel_base = self.rel_base(S, _context) + return rel_base else: # == 'r"\\("' self._scan('r"\\("', context=_context) - rels = self.rels(V, _context) + restriction = self.restriction(S, _context) self._scan('r"\\)"', context=_context) - return rels + return restriction - def base_rel(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'base_rel', [V]) + def rel_base(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'rel_base', [S]) _token = self._peek('EXISTS', 'VARIABLE', context=_context) if _token == 'VARIABLE': - var = self.var(V, _context) - opt_left = self.opt_left(V, _context) - rtype = self.rtype(V, _context) + var = self.var(S, _context) + opt_left = self.opt_left(S, _context) + rtype = self.rtype(_context) rtype.append(var) ; rtype.set_optional(opt_left) - expr = self.expr(V, _context) - opt_right = self.opt_right(V, _context) + expr = self.expr(S, _context) + opt_right = self.opt_right(S, _context) rtype.append(expr) ; rtype.set_optional(opt_right) ; return rtype else: # == 'EXISTS' EXISTS = self._scan('EXISTS', context=_context) self._scan('r"\\("', context=_context) - rels = self.rels(V, _context) + restriction = self.restriction(S, _context) self._scan('r"\\)"', context=_context) - return Exists(rels) + return Exists(restriction) - def rtype(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'rtype', [V]) + def rtype(self, _parent=None): + _context = self.Context(_parent, self._scanner, 'rtype', []) R_TYPE = self._scan('R_TYPE', context=_context) return Relation(R_TYPE) - def opt_left(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'opt_left', [V]) + def opt_left(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'opt_left', [S]) _token = self._peek('QMARK', 'R_TYPE', context=_context) if _token == 'QMARK': QMARK = self._scan('QMARK', context=_context) @@ -422,118 +458,129 @@ else: # == 'R_TYPE' pass - def opt_right(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'opt_right', [V]) - _token = self._peek('QMARK', 'AND', 'OR', "','", 'r"\\)"', 'GROUPBY', "';'", 'HAVING', 'UNION', 'ORDERBY', 'LIMIT', 'OFFSET', context=_context) + def opt_right(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'opt_right', [S]) + _token = self._peek('QMARK', 'AND', 'OR', "','", 'r"\\)"', 'HAVING', "';'", 'WITH', 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', context=_context) if _token == 'QMARK': QMARK = self._scan('QMARK', context=_context) return 'right' else: pass - def vars_decl(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'vars_decl', [V]) + def variables(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'variables', [S]) + vars = [] + var = self.var(S, _context) + vars.append(var) + while self._peek("','", 'BEING', 'ORDERBY', 'WHERE', 'HAVING', 'WITH', "';'", 'GROUPBY', 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) == "','": + self._scan("','", context=_context) + var = self.var(S, _context) + vars.append(var) + return vars + + def decl_vars(self, R, _parent=None): + _context = self.Context(_parent, self._scanner, 'decl_vars', [R]) E_TYPE = self._scan('E_TYPE', context=_context) - var = self.var(V, _context) - while self._peek("','", 'R_TYPE', 'QMARK', 'WHERE', '":"', 'GROUPBY', "';'", 'MUL_OP', 'HAVING', 'ADD_OP', 'r"\\)"', 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'UNION', 'FROM', 'ORDERBY', 'LIMIT', 'OFFSET', 'AND', 'OR', context=_context) == "','": - V.add_main_variable(E_TYPE, var) + var = self.var(R, _context) + while self._peek("','", 'R_TYPE', 'QMARK', 'WHERE', '":"', 'HAVING', "';'", 'MUL_OP', 'BEING', 'WITH', 'GROUPBY', 'ORDERBY', 'ADD_OP', 'r"\\)"', 'CMP_OP', 'SORT_DESC', 'SORT_ASC', '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) - var = self.var(V, _context) - V.add_main_variable(E_TYPE, var) + var = self.var(R, _context) + R.add_main_variable(E_TYPE, var) - def rels_decl(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'rels_decl', [V]) - simple_rel = self.simple_rel(V, _context) - while self._peek("','", 'WHERE', 'GROUPBY', "';'", 'HAVING', 'r"\\)"', 'UNION', 'ORDERBY', 'LIMIT', 'OFFSET', context=_context) == "','": - V.add_main_relation(simple_rel) + 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', 'GROUPBY', 'ORDERBY', 'r"\\)"', 'LIMIT', 'OFFSET', context=_context) == "','": + R.add_main_relation(simple_rel) self._scan("','", context=_context) - simple_rel = self.simple_rel(V, _context) - V.add_main_relation(simple_rel) + simple_rel = self.simple_rel(R, _context) + R.add_main_relation(simple_rel) - def simple_rel(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'simple_rel', [V]) - var = self.var(V, _context) + def simple_rel(self, R, _parent=None): + _context = self.Context(_parent, self._scanner, 'simple_rel', [R]) + var = self.var(R, _context) R_TYPE = self._scan('R_TYPE', context=_context) e = Relation(R_TYPE) ; e.append(var) - added_expr = self.added_expr(V, _context) - e.append(Comparison('=', added_expr)) ; return e + expr_add = self.expr_add(R, _context) + e.append(Comparison('=', expr_add)) ; return e - def expr(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'expr', [V]) + def expr(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'expr', [S]) _token = self._peek('CMP_OP', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) if _token == 'CMP_OP': CMP_OP = self._scan('CMP_OP', context=_context) - added_expr = self.added_expr(V, _context) - return Comparison(CMP_OP.upper(), added_expr) + expr_add = self.expr_add(S, _context) + return Comparison(CMP_OP.upper(), expr_add) else: - added_expr = self.added_expr(V, _context) - return Comparison('=', added_expr) + expr_add = self.expr_add(S, _context) + return Comparison('=', expr_add) - def added_expr(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'added_expr', [V]) - muled_expr = self.muled_expr(V, _context) - lhs = muled_expr - while self._peek('ADD_OP', "','", 'r"\\)"', 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'FROM', 'QMARK', 'WHERE', 'GROUPBY', 'HAVING', "';'", 'LIMIT', 'OFFSET', 'AND', 'UNION', 'OR', 'ORDERBY', context=_context) == 'ADD_OP': + def expr_add(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'expr_add', [S]) + expr_mul = self.expr_mul(S, _context) + node = expr_mul + while self._peek('ADD_OP', 'r"\\)"', "','", 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'HAVING', 'WITH', "';'", 'AND', 'LIMIT', 'OFFSET', 'OR', context=_context) == 'ADD_OP': ADD_OP = self._scan('ADD_OP', context=_context) - muled_expr = self.muled_expr(V, _context) - lhs = MathExpression( ADD_OP, lhs, muled_expr ) - return lhs + expr_mul = self.expr_mul(S, _context) + node = MathExpression( ADD_OP, node, expr_mul ) + return node - def muled_expr(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'muled_expr', [V]) - base_expr = self.base_expr(V, _context) - lhs = base_expr - while self._peek('MUL_OP', 'ADD_OP', "','", 'r"\\)"', 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'FROM', 'QMARK', 'WHERE', 'GROUPBY', 'HAVING', "';'", 'LIMIT', 'OFFSET', 'AND', 'UNION', 'OR', 'ORDERBY', context=_context) == 'MUL_OP': + def expr_mul(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'expr_mul', [S]) + expr_base = self.expr_base(S, _context) + node = expr_base + while self._peek('MUL_OP', 'ADD_OP', 'r"\\)"', "','", 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'HAVING', 'WITH', "';'", 'AND', 'LIMIT', 'OFFSET', 'OR', context=_context) == 'MUL_OP': MUL_OP = self._scan('MUL_OP', context=_context) - base_expr = self.base_expr(V, _context) - lhs = MathExpression( MUL_OP, lhs, base_expr) - return lhs + expr_base = self.expr_base(S, _context) + node = MathExpression( MUL_OP, node, expr_base) + return node - def base_expr(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'base_expr', [V]) + def expr_base(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'expr_base', [S]) _token = self._peek('r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) if _token not in ['r"\\("', 'VARIABLE', 'E_TYPE', 'FUNCTION']: const = self.const(_context) return const elif _token == 'VARIABLE': - var = self.var(V, _context) + var = self.var(S, _context) return var elif _token == 'E_TYPE': - etype = self.etype(V, _context) + etype = self.etype(S, _context) return etype elif _token == 'FUNCTION': - func = self.func(V, _context) + func = self.func(S, _context) return func else: # == 'r"\\("' self._scan('r"\\("', context=_context) - added_expr = self.added_expr(V, _context) + expr_add = self.expr_add(S, _context) self._scan('r"\\)"', context=_context) - return added_expr + return expr_add - def func(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'func', [V]) + def func(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'func', [S]) FUNCTION = self._scan('FUNCTION', context=_context) self._scan('r"\\("', context=_context) F = Function(FUNCTION) - added_expr = self.added_expr(V, _context) - while self._peek("','", 'r"\\)"', 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'FROM', 'QMARK', 'WHERE', 'GROUPBY', 'HAVING', "';'", 'LIMIT', 'OFFSET', 'AND', 'UNION', 'OR', 'ORDERBY', context=_context) == "','": - F.append(added_expr) + expr_add = self.expr_add(S, _context) + while self._peek("','", 'r"\\)"', 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'HAVING', 'WITH', "';'", 'AND', 'LIMIT', 'OFFSET', 'OR', context=_context) == "','": + F.append(expr_add) self._scan("','", context=_context) - added_expr = self.added_expr(V, _context) - F.append(added_expr) + expr_add = self.expr_add(S, _context) + F.append(expr_add) self._scan('r"\\)"', context=_context) return F - def var(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'var', [V]) + def var(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'var', [S]) VARIABLE = self._scan('VARIABLE', context=_context) - return VariableRef(V.get_variable(VARIABLE)) + return VariableRef(S.get_variable(VARIABLE)) - def etype(self, V, _parent=None): - _context = self.Context(_parent, self._scanner, 'etype', [V]) + def etype(self, S, _parent=None): + _context = self.Context(_parent, self._scanner, 'etype', [S]) E_TYPE = self._scan('E_TYPE', context=_context) - return V.get_etype(E_TYPE) + return S.get_etype(E_TYPE) def const(self, _parent=None): _context = self.Context(_parent, self._scanner, 'const', []) diff --git a/stcheck.py b/stcheck.py --- a/stcheck.py +++ b/stcheck.py @@ -8,11 +8,13 @@ from logilab.common.compat import any -from rql import nodes from rql._exceptions import BadRQLQuery from rql.utils import function_description +from rql.nodes import (VariableRef, Constant, Not, Exists, Function, + Variable, variable_refs) from rql.stmts import Union + class GoTo(Exception): """exception used to control the visit of the tree""" def __init__(self, node): @@ -45,19 +47,17 @@ # result.append(term.eval(kwargs)) def _visit(self, node, errors): - skipfurther = None try: node.accept(self, errors) except GoTo, ex: - skipfurther = True self._visit(ex.node, errors) - if skipfurther is None: + else: for c in node.children: self._visit(c, errors) node.leave(self, errors) def _visit_selectedterm(self, node, errors): - for i, term in enumerate(node.selected_terms()): + for i, term in enumerate(node.selection): # selected terms are not included by the default visit, # accept manually each of them self._visit(term, errors) @@ -77,50 +77,64 @@ # statement nodes ######################################################### def visit_union(self, node, errors): - nbselected = len(node.children[0].selected) + nbselected = len(node.children[0].selection) for select in node.children[1:]: - if not len(select.selected) == nbselected: + if not len(select.selection) == nbselected: errors.append('when using union, all subqueries should have ' - 'the same number of selected terms') - if node.sortterms: - self._visit(node.sortterms, errors) - + 'the same number of selected terms') def leave_union(self, node, errors): pass def visit_select(self, node, errors): self._visit_selectedterm(node, errors) - #XXX from should be added to children, no ? - for subquery in node.from_: - self.visit_union(subquery, errors) - - def leave_select(self, selection, errors): - assert len(selection.children) <= 4 - selected = selection.selected +# #XXX from should be added to children, no ? +# for subquery in node.from_: +# self.visit_union(subquery, errors) +# if node.sortterms: +# self._visit(node.sortterms, errors) + def leave_select(self, node, errors): + selected = node.selection # check selected variable are used in restriction - if selection.get_restriction() is not None or len(selected) > 1: + if node.where is not None or len(selected) > 1: for term in selected: self._check_selected(term, 'selection', errors) + if node.groupby: + # check that selected variables are used in groups + for var in node.selection: + if isinstance(var, VariableRef) and not var in node.groupby: + errors.append('variable %s should be grouped' % var) + for group in node.groupby: + self._check_selected(group, 'group', errors) +# # check that variables referenced in the given term are selected +# for term in node.orderby: +# for vref in term.iget_nodes(VariableRef): +# # no stinfo yet, use references +# try: +# for ovref in node.defined_vars[vref.name].references(): +# rel = ovref.relation() +# if rel is not None: +# break +# else: +# msg = 'variable %s used in %s is not referenced by %s' +# errors.append(msg % (vref.name, termtype, node.as_string())) +# except KeyError: +# msg = 'variable %s used in %s is not referenced by %s' +# errors.append(msg % (vref.name, termtype, node.as_string())) def visit_insert(self, insert, errors): self._visit_selectedterm(insert, errors) - assert len(insert.children) <= 1 - def leave_insert(self, node, errors): pass def visit_delete(self, delete, errors): self._visit_selectedterm(delete, errors) - assert len(delete.children) <= 1 - def leave_delete(self, node, errors): pass - def visit_update(self, update, errors): + def visit_set(self, update, errors): self._visit_selectedterm(update, errors) - assert len(update.children) <= 1 - - def leave_update(self, node, errors): + assert len(update.children) <= 1 + def leave_set(self, node, errors): pass # tree nodes ############################################################## @@ -129,50 +143,25 @@ pass def leave_exists(self, node, errors): pass - - def visit_group(self, group, errors): - """check that selected variables are used in groups """ - # XXX that's not necessarily true, for instance: - # Any X, P, MAX(R) WHERE X content_for F, F path P, X revision R GROUPBY P - for var in group.stmt.selected: - if isinstance(var, nodes.VariableRef) and not var in group.children: - errors.append('variable %s should be grouped' % var) - self._check_selected(group, 'group', errors) - - def _check_union_selected(self, term, termtype, errors): - """check that variables referenced in the given term are selected""" - union = term.root - for vref in term.iget_nodes(nodes.VariableRef): - # no stinfo yet, use references - for select in union.children: - try: - for ovref in select.defined_vars[vref.name].references(): - rel = ovref.relation() - if rel is not None: - break - else: - msg = 'variable %s used in %s is not referenced by subquery %s' - errors.append(msg % (vref.name, termtype, select.as_string())) - except KeyError: - msg = 'variable %s used in %s is not referenced by subquery %s' - errors.append(msg % (vref.name, termtype, select.as_string())) - - def visit_having(self, node, errors): + + def visit_subquery(self, node, errors): pass - - def visit_sort(self, sort, errors): - """check that variables used in sort are selected on DISTINCT query""" - self._check_union_selected(sort, 'sort', errors) + def leave_subquery(self, node, errors): + pass def visit_sortterm(self, sortterm, errors): term = sortterm.term - if isinstance(term, nodes.Constant): + if isinstance(term, Constant): for select in sortterm.root.children: - if len(select.selected) < term.value: + if len(select.selection) < term.value: errors.append('order column out of bound %s' % term.value) + def leave_sortterm(self, node, errors): + pass def visit_and(self, et, errors): assert len(et.children) == 2, len(et.children) + def leave_and(self, node, errors): + pass def visit_or(self, ou, errors): assert len(ou.children) == 2, len(ou.children) @@ -190,11 +179,13 @@ if (lhs1.variable is rhs2.variable and rhs1.variable is lhs2.variable): ou.parent.replace(ou, r1) - for vref in r2.get_nodes(nodes.VariableRef): + for vref in r2.get_nodes(VariableRef): vref.unregister_reference() raise GoTo(r1) except AttributeError: pass + def leave_or(self, node, errors): + pass def visit_not(self, not_, errors): pass @@ -208,26 +199,29 @@ # special case "X identity Y" if relation.r_type == 'identity': lhs, rhs = relation.children - assert not isinstance(relation.parent, nodes.Not) + assert not isinstance(relation.parent, Not) assert rhs.operator == '=' # special case "C is NULL" elif relation.r_type == 'is' and relation.children[1].operator == 'IS': lhs, rhs = relation.children - assert isinstance(lhs, nodes.VariableRef), lhs - assert isinstance(rhs.children[0], nodes.Constant) + assert isinstance(lhs, VariableRef), lhs + assert isinstance(rhs.children[0], Constant) assert rhs.operator == 'IS', rhs.operator assert rhs.children[0].type == None - def leave_relation(self, relation, errors): pass - #assert isinstance(lhs, nodes.VariableRef), '%s: %s' % (lhs.__class__, + #assert isinstance(lhs, VariableRef), '%s: %s' % (lhs.__class__, # relation) def visit_comparison(self, comparison, errors): assert len(comparison.children) in (1,2), len(comparison.children) + def leave_comparison(self, node, errors): + pass def visit_mathexpression(self, mathexpr, errors): assert len(mathexpr.children) == 2, len(mathexpr.children) + def leave_mathexpression(self, node, errors): + pass def visit_function(self, function, errors): try: @@ -240,7 +234,7 @@ except BadRQLQuery, ex: errors.append(str(ex)) if funcdescr.aggregat: - if isinstance(function.children[0], nodes.Function) and \ + if isinstance(function.children[0], Function) and \ function.children[0].descr().aggregat: errors.append('can\'t nest aggregat functions') if funcdescr.name == 'IN': @@ -250,6 +244,8 @@ function.parent.remove(function) else: assert len(function.children) >= 1 + def leave_function(self, node, errors): + pass def visit_variableref(self, variableref, errors): assert len(variableref.children)==0 @@ -270,30 +266,7 @@ def leave_constant(self, node, errors): pass - def leave_comparison(self, node, errors): - pass - def leave_mathexpression(self, node, errors): - pass - def leave_or(self, node, errors): - pass - def leave_and(self, node, errors): - pass - def leave_sortterm(self, node, errors): - pass - def leave_function(self, node, errors): - pass - def leave_group(self, node, errors): - pass - def leave_having(self, node, errors): - pass - def leave_sort(self, node, errors): - pass - -def variable_refs(node): - for vref in node.iget_nodes(nodes.VariableRef): - if isinstance(vref.variable, nodes.Variable): - yield vref class RQLSTAnnotator(object): """ annotate RQL syntax tree to ease further code generation from it. @@ -308,21 +281,21 @@ def annotate(self, node): #assert not node.annotated - if isinstance(node, Union): - self._annotate_union(node) - else: - self._annotate_stmt(node) - node.annotated = True + node.accept(self) + #node.annotated = True - def _annotate_union(self, node): + visit_insert = visit_delete = visit_set = lambda s,n: None + + def visit_union(self, node): for select in node.children: - for subquery in select.from_: - self._annotate_union(subquery) - self._annotate_stmt(select) + self.visit_select(select) - def _annotate_stmt(self, node): - for i, term in enumerate(node.selected_terms()): - for func in term.iget_nodes(nodes.Function): + def visit_select(self, node): + if node.with_ is not None: + for subquery in node.with_: + self.visit_union(subquery.query) + for i, term in enumerate(node.selection): + for func in term.iget_nodes(Function): if func.descr().aggregat: node.has_aggregat = True break @@ -330,10 +303,9 @@ for vref in variable_refs(term): vref.variable.stinfo['selected'].add(i) vref.variable.set_scope(node) - restr = node.get_restriction() - if restr is not None: - restr.accept(self, node) - + if node.where is not None: + node.where.accept(self, node) + def rewrite_shared_optional(self, exists, var): """if variable is shared across multiple scopes, need some tree rewriting @@ -345,7 +317,7 @@ if vref.scope is exists: rel = vref.relation() vref.unregister_reference() - newvref = nodes.VariableRef(newvar) + newvref = VariableRef(newvar) vref.parent.replace(vref, newvref) # update stinfo structure which may have already been # partially processed @@ -396,7 +368,7 @@ lhs, rhs = relation.get_parts() # may be a constant once rqlst has been simplified lhsvar = getattr(lhs, 'variable', None) - if not isinstance(lhsvar, nodes.Variable): + if not isinstance(lhsvar, Variable): lhsvar = None if relation.is_types_restriction(): assert rhs.operator == '=' @@ -406,7 +378,7 @@ return if relation.optional is not None: exists = relation.scope - if not isinstance(exists, nodes.Exists): + if not isinstance(exists, Exists): exists = None if lhsvar is not None: if exists is not None: @@ -449,8 +421,8 @@ if key == 'uidrels': constnode = relation.get_variable_parts()[1] if not (relation.operator() != '=' or - isinstance(relation.parent, nodes.Not)): - if isinstance(constnode, nodes.Constant): + isinstance(relation.parent, Not)): + if isinstance(constnode, Constant): lhsvar.stinfo['constnode'] = constnode lhsvar.stinfo.setdefault(key, set()).add(relation) else: @@ -470,6 +442,6 @@ var.stinfo['attrvars'].add( (lhsvar, relation.r_type) ) # give priority to variable which is not in an EXISTS as # "main" attribute variable - if var.stinfo['attrvar'] is None or not isinstance(relation.scope, nodes.Exists): + if var.stinfo['attrvar'] is None or not isinstance(relation.scope, Exists): var.stinfo['attrvar'] = lhsvar or lhs diff --git a/stmts.py b/stmts.py --- a/stmts.py +++ b/stmts.py @@ -13,8 +13,8 @@ from logilab.common.decorators import cached from rql import BadRQLQuery, CoercionError, nodes -from rql.base import Node -from rql.utils import rqlvar_maker +from rql.base import BaseNode, Node +from rql.utils import rqlvar_maker, build_visitor_stub def _check_references(defined, varrefs): @@ -23,38 +23,30 @@ for vref in var.references(): # be careful, Variable and VariableRef define __cmp__ if not [v for v in varrefs if v is vref]: - raise AssertionError('buggy reference %r in %r (actual var: %r)' % - (varref, self, var)) + raise AssertionError('vref %r is not in the tree' % vref) refs[id(vref)] = 1 for vref in varrefs: if not refs.has_key(id(vref)): - raise AssertionError('unreferenced varref %r' % vref) + raise AssertionError('vref %r is not referenced' % vref) return True -class Statement(nodes.EditableMixIn, Node): - """base class for statement nodes""" - - # default values for optional instance attributes, set on the instance when - # used +class ScopeNode(BaseNode): solutions = None # list of possibles solutions for used variables - schema = None # ISchema _varmaker = None # variable names generator, built when necessary + where = None # where clause node def __init__(self): - Node.__init__(self) # dictionnary of defined variables in the original RQL syntax tree self.defined_vars = {} - # syntax tree meta-information - self.stinfo = {} + + def get_selected_variables(self): + return self.selected_terms() - def __str__(self): - return self.as_string(None, {}) - - def as_string(self, encoding=None, kwargs=None): - """return the tree as an encoded rql string""" - raise NotImplementedError() - + def set_where(self, node): + self.where = node + node.parent = self + def copy(self, copy_solutions=True, solutions=None): new = self.__class__() if self.schema is not None: @@ -63,47 +55,8 @@ new.solutions = solutions elif copy_solutions and self.solutions is not None: new.solutions = deepcopy(self.solutions) - for child in self.children: - new.append(child.copy(new)) return new - - # navigation helper methods ############################################# - - @property - def root(self): - """return the root node of the tree""" - return self - - @property - def stmt(self): - return self - @property - def scope(self): - return self - - def ored_rel(self, _fromnode=None): - return None - def neged_rel(self, _fromnode=None): - return None - - def get_selected_variables(self): - return self.selected_terms() - - def get_restriction(self): - """return all the subtree with restriction clauses. That maybe a Or, - And, or Relation instance. - return None if there is no restriction clauses. - """ - for c in self.children: - if not isinstance(c, nodes.Group) and not isinstance(c, nodes.Sort): - return c - break - return None - - def selected_terms(self): - raise NotImplementedError() - # construction helper methods ############################################# def get_etype(self, name): @@ -150,17 +103,50 @@ for solution in solutions: var.stinfo['possibletypes'].add(solution[var.name]) + +class Statement(object): + """base class for statement nodes""" + + # default values for optional instance attributes, set on the instance when + # used + schema = None # ISchema + +# def __init__(self): +# Node.__init__(self) +# # syntax tree meta-information +# self.stinfo = {} + + # navigation helper methods ############################################# + + @property + def root(self): + """return the root node of the tree""" + return self + + @property + def stmt(self): + return self + + @property + def scope(self): + return self + + def ored_rel(self, _fromnode=None): + return None + def neged_rel(self, _fromnode=None): + return None + def check_references(self): """test function""" varrefs = self.get_nodes(nodes.VariableRef) - varrefs += self.get_selected_variables() - for n in getattr(self, 'main_relations', ()): - varrefs += n.iget_nodes(nodes.VariableRef) - return _check_references(self.defined_vars, varrefs) + try: + return _check_references(self.defined_vars, varrefs) + except: + print repr(self) + raise - -class Union(Statement): +class Union(Statement, Node): """the select node is the root of the syntax tree for selection statement using UNION """ @@ -171,18 +157,26 @@ memorizing = 0 # recoverable modification attributes def __init__(self): - Statement.__init__(self) + Node.__init__(self) # limit / offset self.limit = None self.offset = 0 - # for sort variables - self.sortterms = None + + @property + def root(self): + """return the root node of the tree""" + if self.parent is None: + return self + return self.parent.root + + def get_description(self): + return [c.get_description() for c in self.children] + # repr / as_string / copy ################################################# + def __repr__(self): s = [repr(select) for select in self.children] s = ['\nUNION\n'.join(s)] - if self.sortterms is not None: - s.append(repr(self.sortterms)) if self.limit is not None: s.append('LIMIT %s' % self.limit) if self.offset: @@ -193,8 +187,6 @@ """return the tree as an encoded rql string""" s = [select.as_string(encoding, kwargs) for select in self.children] s = [' UNION '.join(s)] - if self.sortterms is not None: - s.append(self.sortterms.as_string(encoding, kwargs)) if self.limit is not None: s.append('LIMIT %s' % self.limit) if self.offset: @@ -207,22 +199,12 @@ for child in self.children: new.append(child.copy()) assert new.children[-1].parent is new - if self.sortterms is not None: - new.set_sortterms(self.sortterms.copy(new)) new.limit = self.limit new.offset = self.offset return new - - def accept(self, visitor, *args, **kwargs): - return visitor.visit_union(self, *args, **kwargs) + + # union specific methods ################################################## - def leave(self, visitor, *args, **kwargs): - return visitor.leave_union(self, *args, **kwargs) - - def set_sortterms(self, node): - self.sortterms = node - node.parent = self - def set_limit(self, limit): if limit is not None and (not isinstance(limit, (int, long)) or limit <= 0): raise BadRQLQuery('bad limit %s' % limit) @@ -238,65 +220,6 @@ from rql.undo import SetOffsetOperation self.undo_manager.add_operation(SetOffsetOperation(self.offset)) self.offset = offset - - def set_possible_types(self, solutions): - raise RuntimeError('Union has no solutions') - - @property - def root(self): - """return the root node of the tree""" - if self.parent is None: - return self - return self.parent.root - - # access to select statements property, which in certain condition - # should have homogeneous values (don't use this in other cases) - def get_restriction(self): - raise ValueError('Union has no restriction') - - def get_description(self): - return [c.get_description() for c in self.children] - -# @property -# @cached -# def groups(self): -# """return a list of grouped variables (i.e a Group object) or None if -# there is no grouped variable. -# """ -# groups = self.children[0].get_groups() -# if groups is None: -# for c in self.children[1:]: -# if c.get_groups() is not None: -# raise BadRQLQuery('inconsistent groups among subqueries') -# else: -# for c in self.children[1:]: -# if not groups.is_equivalent(c.get_groups()): -# raise BadRQLQuery('inconsistent groups among subqueries') -# return groups - -# @cached -# def selected_terms(self): -# selected = self.children[0].selected_terms() -# for c in self.children[1:]: -# cselected = c.selected_terms() -# for i, term in enumerate(selected): -# if not term.is_equivalent(cselected[i]): -# raise BadRQLQuery('inconsistent selection among subqueries') -# return selected - -# @property -# def selected(self): -# # consistency check done by selected_terms -# return self.children[0].selected - -# @property -# @cached -# def distinct(self): -# distinct = self.children[0].distinct -# for c in self.children[1:]: -# if c.distinct != distinct: -# raise BadRQLQuery('inconsistent distinct among subqueries') -# return distinct # recoverable modification methods ######################################## @@ -306,6 +229,10 @@ from rql.undo import SelectionManager return SelectionManager(self) + @property + def should_register_op(self): + return self.memorizing and not self.undoing + def save_state(self): """save the current tree""" self.undo_manager.push_state() @@ -317,104 +244,97 @@ assert self.memorizing >= 0 self.undo_manager.recover() - def add_sort_var(self, var, asc=True): - """add var in 'orderby' constraints - asc is a boolean indicating the sort order (ascendent or descendent) - """ - var = nodes.variable_ref(var) - var.register_reference() - term = nodes.SortTerm(var, asc) - self.add_sort_term(term) - - def add_sort_term(self, term): - if self.sortterms is None: - self.set_sortterms(nodes.Sort()) - self.sortterms.append(term) - if self.should_register_op: - from rql.undo import AddSortOperation - self.undo_manager.add_operation(AddSortOperation(term)) - - def remove_sort_terms(self): - if self.sortterms is not None: - for term in self.sortterms.children: - self.remove_sort_term(term) - - def remove_sort_term(self, term): - """remove a sort term and the sort node if necessary""" - if self.should_register_op: - from rql.undo import RemoveSortOperation - self.undo_manager.add_operation(RemoveSortOperation(term)) - self.remove_node(term) - if not self.sortterms.children: - self.sortterms = None - def check_references(self): """test function""" for select in self.children: select.check_references() - if self.sortterms: - varrefs = self.sortterms.get_nodes(nodes.VariableRef) - _check_references(self.defined_vars, varrefs) return True -class Select(Statement): +class Select(Statement, nodes.EditableMixIn, ScopeNode): """the select node is the base statement of the syntax tree for selection statement, always child of a UNION root. """ - + parent = None + distinct = False + # select clauses + groupby = () + orderby = () + having = () + with_ = () + # set by the annotator + has_aggregat = False + def __init__(self): Statement.__init__(self) - # distinct ? - self.distinct = False - # list of selected relations (maybe variables or functions) - self.selected = [] + ScopeNode.__init__(self) + self.selection = [] # subqueries alias self.aliases = {} - self.from_ = [] - # set by the annotator - self.has_aggregat = False # syntax tree meta-information - self.stinfo['rewritten'] = {} + self.stinfo = {'rewritten': {}} + @property + def root(self): + """return the root node of the tree""" + return self.parent + + def get_description(self): + """return the list of types or relations (if not found) associated to + selected variables + """ + descr = [] + for term in self.selection: + try: + descr.append(term.get_description()) + except CoercionError: + descr.append('Any') + return descr + + @property + def children(self): + children = self.selection[:] + if self.groupby: + children += self.groupby + if self.orderby: + children += self.orderby + if self.where: + children.append(self.where) + if self.having: + children += self.having + if self.with_: + children += self.with_ + return children + + # repr / as_string / copy ################################################# + def __repr__(self): - if self.distinct: - base = 'DISTINCT Any' - else: - base = 'Any' - s = ['%s %s WHERE' % (base, - ','.join([repr(v) for v in self.selected]))] - for child in self.children: - s.append(repr(child)) - return '\n'.join(s) + return self.as_string(userepr=True) - def as_string(self, encoding=None, kwargs=None): + def as_string(self, encoding=None, kwargs=None, userepr=False): """return the tree as an encoded rql string""" - if self.distinct: - base = 'DISTINCT Any' + if userepr: + as_string = repr else: - base = 'Any' - s = ['%s %s' % (base, ','.join(v.as_string(encoding, kwargs) - for v in self.selected))] - if self.from_: - s.append('FROM') - for subquery in self.from_: - s.append('(%s) AS' % subquery.as_string(encoding, kwargs)) - aliases = self.subquery_aliases(subquery) - if len(aliases) == 1: - s.append(aliases[0].name) - else: - s.append('(%s)' % ','.join(ca.name for ca in aliases)) - r = self.get_restriction() - if r is not None: - s.append('WHERE %s' % r.as_string(encoding, kwargs)) - groups = self.groups - if groups is not None: - s.append(groups.as_string(encoding, kwargs)) - having = self.having - if having is not None: - s.append(having.as_string(encoding, kwargs)) - return ' '.join(s) + as_string = lambda x: x.as_string(encoding, kwargs) + s = [','.join(as_string(term) for term in self.selection)] + if self.groupby: + s.append('GROUPBY ' + ','.join(as_string(term) + for term in self.groupby)) + if self.orderby: + s.append('ORDERBY ' + ','.join(as_string(term) + for term in self.orderby)) + if self.where: + s.append('WHERE ' + as_string(self.where)) + if self.having: + s.append('HAVING ' + ','.join(as_string(term) + for term in self.having)) + if self.with_: + s.append('WITH ' + ','.join(as_string(term) + for term in self.with_)) + if self.distinct: + return 'DISTINCT Any ' + ' '.join(s) + return 'Any ' + ' '.join(s) def copy(self, copy_solutions=True, solutions=None): new = Select() @@ -422,20 +342,73 @@ new.solutions = solutions elif copy_solutions and self.solutions is not None: new.solutions = deepcopy(self.solutions) - for child in self.from_: # copy subqueries first - new.add_subquery(child.copy(), [ca.name for ca in self.subquery_aliases(child)]) - for child in self.children: - new.append(child.copy(new)) - for child in self.selected: + if self.with_: + new.set_with([sq.copy(new) for sq in self.with_]) + for child in self.selection: new.append_selected(child.copy(new)) + if self.groupby: + new.set_groupby([sq.copy(new) for sq in self.groupby]) + if self.orderby: + new.set_orderby([sq.copy(new) for sq in self.orderby]) + if self.where: + new.set_where(self.where.copy(new)) + if self.having: + new.set_having([sq.copy(new) for sq in self.having]) new.distinct = self.distinct return new - def accept(self, visitor, *args, **kwargs): - return visitor.visit_select(self, *args, **kwargs) + # select specific methods ################################################# + + def set_statement_type(self, etype): + """set the statement type for this selection + this method must be called last (i.e. once selected variables has been + added) + """ + assert self.selection + # Person P -> Any P where P is Person + if etype != 'Any': + for var in self.get_selected_variables(): + self.add_type_restriction(var.variable, etype) + + def set_distinct(self, value): + """mark DISTINCT query""" + if self.should_register_op and value != self.distinct: + from rql.undo import SetDistinctOperation + self.undo_manager.add_operation(SetDistinctOperation(self.distinct, self)) + self.distinct = value - def leave(self, visitor, *args, **kwargs): - return visitor.leave_select(self, *args, **kwargs) + def set_orderby(self, terms): + self.orderby = terms + for node in terms: + node.parent = self + + def set_groupby(self, terms): + self.groupby = terms + 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_ = terms + for node in terms: + node.parent = self + if check and len(node.aliases) != len(node.query.children[0].selection): + raise BadRQLQuery('Should have the same number of aliases than ' + 'selected terms in sub-query') + for i, alias in enumerate(node.aliases): + alias = alias.name + if alias in self.aliases: + raise BadRQLQuery('Duplicated alias %s' % alias) + self.aliases[alias] = nodes.ColumnAlias(alias, i, node.query) + # alias may already have been used as a regular variable, replace it + if alias in self.defined_vars: + for vref in self.defined_vars.pop(alias).references(): + vref.variable = self.aliases[alias] + def get_variable(self, name): """get a variable instance from its name @@ -468,122 +441,66 @@ # quick accessors ######################################################### - def subquery_aliases(self, subquery): - aliases = [ca for ca in self.aliases.itervalues() if ca.query is subquery] - aliases.sort(key=lambda x: x.colnum) - return aliases - - @property - def root(self): - """return the root node of the tree""" - return self.parent - - @property - def groups(self): - """return a Group node or None if there is no grouped variable""" - for c in self.children: - if isinstance(c, nodes.Group): - return c - return None - - @property - def having(self): - """return a Having or None if there is no HAVING clause""" - for c in self.children: - if isinstance(c, nodes.Having): - return c - return None - +# def subquery_aliases(self, subquery): +# aliases = [ca for ca in self.aliases.itervalues() if ca.query is subquery] +# aliases.sort(key=lambda x: x.colnum) +# return aliases + def get_selected_variables(self): """returns all selected variables, including those used in aggregate functions """ - for term in self.selected_terms(): + for term in self.selection: for node in term.iget_nodes(nodes.VariableRef): yield node - def selected_terms(self): - """returns selected terms""" - return self.selected[:] - # construction helper methods ############################################# def append_selected(self, term): if isinstance(term, nodes.Constant) and term.type == 'etype': raise BadRQLQuery('Entity type are not allowed in selection') term.parent = self - self.selected.append(term) + self.selection.append(term) def replace(self, oldnode, newnode): - # XXX no vref handling ? - try: - Statement.replace(self, oldnode, newnode) - except ValueError: - i = self.selected.index(oldnode) - self.selected.pop(i) - self.selected.insert(i, newnode) - newnode.parent = self - - def set_statement_type(self, etype): - """set the statement type for this selection - this method must be called last (i.e. once selected variables has been - added) - """ - assert self.selected - # Person P -> Any P where P is Person - if etype != 'Any': - for var in self.get_selected_variables(): - self.add_type_restriction(var.variable, etype) - - def add_subquery(self, union, aliases, check=True): - if check and len(aliases) != len(union.children[0].selected): - raise BadRQLQuery('Should have the same number of aliases than ' - 'selected terms in sub-query') - self.from_.append(union) - union.parent = self - for i, alias in enumerate(aliases): - if alias in self.aliases: - raise BadRQLQuery('Duplicated alias %s' % alias) - self.aliases[alias] = nodes.ColumnAlias(alias, i, union) - # alias may already have been used as a regular variable, replace it - if alias in self.defined_vars: - for vref in self.defined_vars.pop(alias).references(): - vref.variable = self.aliases[alias] - - def get_description(self): - """return the list of types or relations (if not found) associated to - selected variables - """ - descr = [] - for term in self.selected: - try: - descr.append(term.get_description()) - except CoercionError: - descr.append('Any') - return descr - - def set_distinct(self, value): - """mark DISTINCT query""" - if self.should_register_op and value != self.distinct: - from rql.undo import SetDistinctOperation - self.undo_manager.add_operation(SetDistinctOperation(self.distinct, self)) - self.distinct = value - + assert oldnode is self.where + self.where = newnode + newnode.parent = self +# # XXX no vref handling ? +# try: +# Statement.replace(self, oldnode, newnode) +# except ValueError: +# i = self.selection.index(oldnode) +# self.selection.pop(i) +# self.selection.insert(i, newnode) + + def remove(self, node): + if node is self.where: + self.where = None + elif node in self.orderby: + self.remove_sort_term(node) + elif node in self.groupby: + self.remove_group_var(node) + else: + raise Exception('duh XXX') + node.parent = None + def undefine_variable(self, var): """undefine the given variable and remove all relations where it appears""" if hasattr(var, 'variable'): var = var.variable # remove relations where this variable is referenced - for varref in var.references(): - rel = varref.relation() + for vref in var.references(): + rel = vref.relation() if rel is not None: - self.parent.remove_node(rel) - elif isinstance(varref.parent, nodes.SortTerm): - self.parent.remove_sort_term(varref.parent) - elif isinstance(varref.parent, nodes.Group): - self.remove_group_var(varref) + self.remove_node(rel) + # XXX may have other nodes between vref and the sort term + elif isinstance(vref.parent, nodes.SortTerm): + self.remove_sort_term(vref.parent) + elif vref in self.groupby: + self.remove_group_var(vref) else: # selected variable - self.remove_selected(varref) + self.remove_selected(vref) # effective undefine operation if self.should_register_op: from rql.undo import UndefineVarOperation @@ -594,7 +511,7 @@ """get variable index in the list using identity (Variable and VariableRef define __cmp__ """ - for i, term in enumerate(self.selected): + for i, term in enumerate(self.selection): if term is var: return i raise IndexError() @@ -606,7 +523,7 @@ if self.should_register_op: from rql.undo import UnselectVarOperation self.undo_manager.add_operation(UnselectVarOperation(var, index)) - for vref in self.selected.pop(index).iget_nodes(nodes.VariableRef): + for vref in self.selection.pop(index).iget_nodes(nodes.VariableRef): vref.unregister_reference() def add_selected(self, term, index=None): @@ -619,7 +536,7 @@ var = nodes.variable_ref(var) var.register_reference() if index is not None: - self.selected.insert(index, term) + self.selection.insert(index, term) term.parent = self else: self.append_selected(term) @@ -631,89 +548,91 @@ """add var in 'orderby' constraints asc is a boolean indicating the group order (ascendent or descendent) """ - var = nodes.variable_ref(var) - var.register_reference() - groups = self.groups - if groups is None: - groups = nodes.Group() - self.append(groups) - groups.append(var) + if self.groupby is None: + self.groupby = [] + vref = nodes.variable_ref(var) + vref.register_reference() + self.groupby.append(vref) + vref.parent = self if self.should_register_op: from rql.undo import AddGroupOperation - self.undo_manager.add_operation(AddGroupOperation(var)) + self.undo_manager.add_operation(AddGroupOperation(vref)) - def remove_group_var(self, var): + def remove_group_var(self, vref): """remove the group variable and the group node if necessary""" - groups = self.groups - assert var in groups.children - if len(groups.children) == 1: - self.parent.remove_node(groups) - else: - self.parent.remove_node(var) + vref.unregister_reference() + self.groupby.remove(vref) + if self.should_register_op: + from rql.undo import RemoveGroupOperation + self.undo_manager.add_operation(RemoveGroupOperation(vref)) + if not self.groupby: + self.groupby = None + + def add_sort_var(self, var, asc=True): + """add var in 'orderby' constraints + asc is a boolean indicating the sort order (ascendent or descendent) + """ + vref = nodes.variable_ref(var) + vref.register_reference() + term = nodes.SortTerm(vref, asc) + self.add_sort_term(term) + + def add_sort_term(self, term): + if self.orderby is None: + self.orderby = [] + self.orderby.append(term) + term.parent = self + for vref in term.iget_nodes(nodes.VariableRef): + try: + vref.register_reference() + except AssertionError: + pass # already referenced + if self.should_register_op: + from rql.undo import AddSortOperation + self.undo_manager.add_operation(AddSortOperation(term)) + + def remove_sort_terms(self): + if self.orderby: + for term in self.orderby: + self.remove_sort_term(term) + + def remove_sort_term(self, term): + """remove a sort term and the sort node if necessary""" + if self.should_register_op: + from rql.undo import RemoveSortOperation + self.undo_manager.add_operation(RemoveSortOperation(term)) + for vref in term.iget_nodes(nodes.VariableRef): + vref.unregister_reference() + self.orderby.remove(term) + if not self.orderby: + self.orderby = None def select_only_variables(self): - self.selected = [vref for term in self.selected + self.selection = [vref for term in self.selection for vref in term.iget_nodes(nodes.VariableRef)] + -class Delete(Statement): +class Delete(Statement, ScopeNode): """the Delete node is the root of the syntax tree for deletion statement """ TYPE = 'delete' def __init__(self): Statement.__init__(self) + ScopeNode.__init__(self) self.main_variables = [] self.main_relations = [] - - def __repr__(self): - result = ['DELETE'] - if self.main_variables: - result.append(', '.join(['%r %r' %(etype, var) - for etype, var in self.main_variables])) - if self.main_relations: - if self.main_variables: - result.append(',') - result.append(', '.join([repr(rel) for rel in self.main_relations])) - r = self.get_restriction() - if r is not None: - result.append('WHERE %r' % r) - return ' '.join(result) - def as_string(self, encoding=None, kwargs=None): - """return the tree as an encoded rql string""" - result = ['DELETE'] - if self.main_variables: - result.append(', '.join(['%s %s' %(etype, var) - for etype, var in self.main_variables])) - if self.main_relations: - if self.main_variables: - result.append(',') - result.append(', '.join([rel.as_string(encoding, kwargs) - for rel in self.main_relations])) - r = self.get_restriction() - if r is not None: - result.append('WHERE %s' % r.as_string(encoding, kwargs)) - return ' '.join(result) + @property + def children(self): + children = self.selection[:] + children += self.main_relations + if self.where: + children.append(self.where) + return children - def copy(self): - new = Statement.copy(self) - for etype, var in self.main_variables: - vref = nodes.VariableRef(new.get_variable(var.name)) - new.add_main_variable(etype, vref) - for child in self.main_relations: - new.add_main_relation(child.copy(new)) - return new - - def accept(self, visitor, *args, **kwargs): - return visitor.visit_delete( self, *args, **kwargs ) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_delete( self, *args, **kwargs ) - - def get_selected_variables(self): - return self.selected_terms() - - def selected_terms(self): + @property + def selection(self): return [vref for et, vref in self.main_variables] def add_main_variable(self, etype, vref): @@ -730,61 +649,71 @@ assert isinstance(relation.children[1].children[0], nodes.VariableRef) relation.parent = self self.main_relations.append( relation ) + + # repr / as_string / copy ################################################# + + def __repr__(self): + result = ['DELETE'] + if self.main_variables: + result.append(', '.join(['%r %r' %(etype, var) + for etype, var in self.main_variables])) + if self.main_relations: + if self.main_variables: + result.append(',') + result.append(', '.join([repr(rel) for rel in self.main_relations])) + if self.where is not None: + result.append(repr(self.where)) + return ' '.join(result) + + def as_string(self, encoding=None, kwargs=None): + """return the tree as an encoded rql string""" + result = ['DELETE'] + if self.main_variables: + result.append(', '.join(['%s %s' %(etype, var) + for etype, var in self.main_variables])) + if self.main_relations: + if self.main_variables: + result.append(',') + result.append(', '.join([rel.as_string(encoding, kwargs) + for rel in self.main_relations])) + if self.where is not None: + result.append('WHERE ' + self.where.as_string(encoding, kwargs)) + return ' '.join(result) + + def copy(self): + new = Delete() + for etype, var in self.main_variables: + vref = nodes.VariableRef(new.get_variable(var.name)) + new.add_main_variable(etype, vref) + for child in self.main_relations: + new.add_main_relation(child.copy(new)) + if self.where: + new.set_where(self.where.copy(new)) + return new -class Insert(Statement): +class Insert(Statement, ScopeNode): """the Insert node is the root of the syntax tree for insertion statement """ TYPE = 'insert' def __init__(self): Statement.__init__(self) + ScopeNode.__init__(self) self.main_variables = [] self.main_relations = [] self.inserted_variables = {} - - def __repr__(self): - result = ['INSERT'] - result.append(', '.join(['%r %r' % (etype, var) - for etype, var in self.main_variables])) - if self.main_relations: - result.append(':') - result.append(', '.join([repr(rel) for rel in self.main_relations])) - restr = self.get_restriction() - if restr is not None: - result.append('WHERE %r' % restr) - return ' '.join(result) - def as_string(self, encoding=None, kwargs=None): - """return the tree as an encoded rql string""" - result = ['INSERT'] - result.append(', '.join(['%s %s' % (etype, var) - for etype, var in self.main_variables])) - if self.main_relations: - result.append(':') - result.append(', '.join([rel.as_string(encoding, kwargs) - for rel in self.main_relations])) - restr = self.get_restriction() - if restr is not None: - result.append('WHERE %s' % restr.as_string(encoding, kwargs)) - return ' '.join(result) + @property + def children(self): + children = self.selection[:] + children += self.main_relations + if self.where: + children.append(self.where) + return children - def copy(self): - new = Statement.copy(self) - for etype, var in self.main_variables: - vref = nodes.VariableRef(new.get_variable(var.name)) - new.add_main_variable(etype, vref) - for child in self.main_relations: - new.add_main_relation(child.copy(new)) - return new - - def accept(self, visitor, *args, **kwargs): - return visitor.visit_insert( self, *args, **kwargs ) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_insert( self, *args, **kwargs ) - - def selected_terms(self): + @property + def selection(self): return [vref for et, vref in self.main_variables] def add_main_variable(self, etype, vref): @@ -806,43 +735,96 @@ raise BadRQLQuery(msg % (var, var)) relation.parent = self self.main_relations.append( relation ) + + # repr / as_string / copy ################################################# - -class Update(Statement): - """the Update node is the root of the syntax tree for update statement - """ - TYPE = 'update' - - def __init__(self): - Statement.__init__(self) - self.main_relations = [] + def __repr__(self): + result = ['INSERT'] + result.append(', '.join(['%r %r' % (etype, var) + for etype, var in self.main_variables])) + if self.main_relations: + result.append(':') + result.append(', '.join([repr(rel) for rel in self.main_relations])) + if self.where is not None: + result.append('WHERE ' + repr(self.where)) + return ' '.join(result) def as_string(self, encoding=None, kwargs=None): """return the tree as an encoded rql string""" - result = ['SET'] - result.append(', '.join([rel.as_string(encoding, kwargs) - for rel in self.main_relations])) - r = self.get_restriction() - if r is not None: - result.append('WHERE %s' % r.as_string(encoding, kwargs)) + result = ['INSERT'] + result.append(', '.join(['%s %s' % (etype, var) + for etype, var in self.main_variables])) + if self.main_relations: + result.append(':') + result.append(', '.join([rel.as_string(encoding, kwargs) + for rel in self.main_relations])) + if self.where is not None: + result.append('WHERE ' + self.where.as_string(encoding, kwargs)) return ' '.join(result) def copy(self): - new = Statement.copy(self) + new = Insert() + for etype, var in self.main_variables: + vref = nodes.VariableRef(new.get_variable(var.name)) + new.add_main_variable(etype, vref) for child in self.main_relations: new.add_main_relation(child.copy(new)) + if self.where: + new.set_where(self.where.copy(new)) return new - def accept(self, visitor, *args, **kwargs): - return visitor.visit_update( self, *args, **kwargs ) - - def leave(self, visitor, *args, **kwargs): - return visitor.leave_update( self, *args, **kwargs ) + +class Set(Statement, ScopeNode): + """the Set node is the root of the syntax tree for update statement + """ + TYPE = 'set' - def selected_terms(self): + def __init__(self): + Statement.__init__(self) + ScopeNode.__init__(self) + self.main_relations = [] + + @property + def children(self): + children = self.main_relations[:] + if self.where: + children.append(self.where) + return children + + @property + def selection(self): return [] def add_main_relation(self, relation): """add a relation to the list of modified relations""" relation.parent = self self.main_relations.append( relation ) + + # repr / as_string / copy ################################################# + + def __repr__(self): + result = ['SET'] + result.append(', '.join(repr(rel) for rel in self.main_relations)) + if self.where is not None: + result.append('WHERE ' + repr(self.where)) + return ' '.join(result) + + def as_string(self, encoding=None, kwargs=None): + """return the tree as an encoded rql string""" + result = ['SET'] + result.append(', '.join(rel.as_string(encoding, kwargs) + for rel in self.main_relations)) + if self.where is not None: + result.append('WHERE ' + self.where.as_string(encoding, kwargs)) + return ' '.join(result) + + def copy(self): + new = Set() + for child in self.main_relations: + new.add_main_relation(child.copy(new)) + if self.where: + new.set_where(self.where.copy(new)) + return new + + +build_visitor_stub((Union, Select, Insert, Delete, Set)) diff --git a/test/unittest_analyze.py b/test/unittest_analyze.py --- a/test/unittest_analyze.py +++ b/test/unittest_analyze.py @@ -146,6 +146,8 @@ if DEBUG: print rql node = self.helper.parse(rql) + print rql + print node self.assertRaises(TypeResolverException, self.helper.compute_solutions, node, debug=DEBUG) @@ -161,7 +163,7 @@ def test_base_2(self): node = self.helper.parse('Person X') # check constant type of the is relation inserted - self.assertEqual(node.children[0].get_restriction().children[1].children[0].type, + self.assertEqual(node.children[0].where.children[1].children[0].type, 'etype') self.helper.compute_solutions(node, debug=DEBUG) sols = node.children[0].solutions @@ -256,14 +258,14 @@ def test_base_guess_3(self): - node = self.helper.parse('Any Z WHERE X name Z GROUPBY Z') + node = self.helper.parse('Any Z GROUPBY Z WHERE X name Z') self.helper.compute_solutions(node, debug=DEBUG) sols = sorted(node.children[0].solutions) self.assertEqual(sols, [{'X': 'Company', 'Z': 'String'}, {'X': 'Person', 'Z': 'String'}]) def test_var_name(self): - node = self.helper.parse('Any E1 WHERE E2 is Person, E2 name E1 GROUPBY E1') + node = self.helper.parse('Any E1 GROUPBY E1 WHERE E2 is Person, E2 name E1') self.helper.compute_solutions(node, debug=DEBUG) sols = sorted(node.children[0].solutions) self.assertEqual(sols, [{'E2': 'Person', 'E1': 'String'}]) @@ -304,7 +306,7 @@ self.assertEqual(sols, [{'P': 'Person'}]) def test_union(self): - node = self.helper.parse('Any P WHERE X eid 0, NOT X connait P UNION Any E1 WHERE E2 work_for E1, E2 eid 2') + node = self.helper.parse('(Any P WHERE X eid 0, NOT X connait P) UNION (Any E1 WHERE E2 work_for E1, E2 eid 2)') self.helper.compute_solutions(node, debug=DEBUG) sols = sorted(node.children[0].solutions) self.assertEqual(sols, [{'P': 'Person', 'X': 'Person'}], [{'E1': 'Company', 'E2': 'Person'}]) @@ -322,15 +324,14 @@ 'U': 'Person'}]) def test_subqueries(self): - node = self.helper.parse('Any L, Y, F ' - 'FROM (Any X,F WHERE X is Person, X firstname F ' - 'UNION Any X,F WHERE X is Company, X name F) AS (Y,F) ' - 'WHERE Y located L;') + node = self.helper.parse('Any L, Y, F WHERE Y located L ' + 'WITH Y,F BEING ((Any X,F WHERE X is Person, X firstname F) ' + 'UNION (Any X,F WHERE X is Company, X name F))') self.helper.compute_solutions(node, debug=DEBUG) sols = sorted(node.children[0].solutions) - self.assertEqual(node.children[0].from_[0].children[0].solutions, [{'X': 'Person', + self.assertEqual(node.children[0].with_[0].query.children[0].solutions, [{'X': 'Person', 'F': 'String'}]) - self.assertEqual(node.children[0].from_[0].children[1].solutions, [{'X': 'Company', + self.assertEqual(node.children[0].with_[0].query.children[1].solutions, [{'X': 'Company', 'F': 'String'}]) sols = sorted(node.children[0].solutions) self.assertEqual(sols, [{'Y': 'Company', 'L': 'Address', @@ -339,12 +340,12 @@ 'F': 'String'}]) def test_subqueries_aggregat(self): - node = self.helper.parse('Any L, SUM(X)*100/Y ' - 'FROM (Any SUM(X) WHERE X is Person) AS Y ' - 'WHERE X is Person, X located L GROUPBY L;') + node = self.helper.parse('Any L, SUM(X)*100/Y GROUPBY L ' + 'WHERE X is Person, X located L ' + 'WITH Y BEING (Any SUM(X) WHERE X is Person)') self.helper.compute_solutions(node, debug=DEBUG) sols = sorted(node.children[0].solutions) - self.assertEqual(node.children[0].from_[0].children[0].solutions, [{'X': 'Person'}]) + self.assertEqual(node.children[0].with_[0].query.children[0].solutions, [{'X': 'Person'}]) self.assertEqual(node.children[0].solutions, [{'X': 'Person', 'Y': 'Person', 'L': 'Address'}]) diff --git a/test/unittest_editextensions.py b/test/unittest_editextensions.py --- a/test/unittest_editextensions.py +++ b/test/unittest_editextensions.py @@ -12,7 +12,7 @@ rqlst.save_state() select = rqlst.children[0] var = select.make_variable() - select.remove_selected(select.selected[0]) + 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) @@ -30,7 +30,7 @@ rqlst.save_state() select = rqlst.children[0] var = select.make_variable() - select.remove_selected(select.selected[0]) + 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) diff --git a/test/unittest_nodes.py b/test/unittest_nodes.py --- a/test/unittest_nodes.py +++ b/test/unittest_nodes.py @@ -84,20 +84,22 @@ tree.recover() self.assertEquals(tree.offset, 0) - def test_union_add_sort_var(self): + def test_select_add_sort_var(self): tree = self._parse('Any X') tree.save_state() - tree.add_sort_var(tree.get_variable('X')) + select = tree.children[0] + select.add_sort_var(select.get_variable('X')) tree.check_references() self.assertEquals(tree.as_string(), 'Any X ORDERBY X') tree.recover() tree.check_references() self.assertEquals(tree.as_string(), 'Any X') - def test_union_remove_sort_terms(self): + def test_select_remove_sort_terms(self): tree = self._parse('Any X ORDERBY X') tree.save_state() - tree.remove_sort_terms() + select = tree.children[0] + select.remove_sort_terms() tree.check_references() self.assertEquals(tree.as_string(), 'Any X') tree.recover() @@ -134,7 +136,7 @@ tree = self._parse('Any X GROUPBY X') tree.save_state() select = tree.children[0] - select.remove_group_var(select.groups.children[0]) + select.remove_group_var(select.groupby[0]) tree.check_references() self.assertEquals(tree.as_string(), 'Any X') tree.recover() @@ -143,97 +145,94 @@ def test_select_base_1(self): tree = self._parse("Any X WHERE X is Person") - self.assertRaises(ValueError, tree.get_restriction) self.assertIsInstance(tree, stmts.Union) self.assertEqual(tree.limit, None) self.assertEqual(tree.offset, 0) select = tree.children[0] self.assertIsInstance(select, stmts.Select) self.assertEqual(select.distinct, False) - self.assertEqual(len(select.children), 1) - self.assertIsInstance(select.children[0], nodes.Relation) - self.assert_(select.children[0] is select.get_restriction()) + self.assertEqual(len(select.children), 2) + self.assert_(select.children[0] is select.selection[0]) + self.assert_(select.children[1] is select.where) + self.assertIsInstance(select.where, nodes.Relation) def test_select_base_2(self): tree = self._simpleparse("Any X WHERE X is Person") - self.assertEqual(len(tree.children), 1) + self.assertEqual(len(tree.children), 2) self.assertEqual(tree.distinct, False) def test_select_distinct(self): tree = self._simpleparse("DISTINCT Any X WHERE X is Person") - self.assertEqual(len(tree.children), 1) + self.assertEqual(len(tree.children), 2) self.assertEqual(tree.distinct, True) def test_select_null(self): tree = self._simpleparse("Any X WHERE X name NULL") - constant = tree.children[0].children[1].children[0] + constant = tree.where.children[1].children[0] self.assertEqual(constant.type, None) self.assertEqual(constant.value, None) def test_select_true(self): tree = self._simpleparse("Any X WHERE X name TRUE") - constant = tree.children[0].children[1].children[0] + constant = tree.where.children[1].children[0] self.assertEqual(constant.type, 'Boolean') self.assertEqual(constant.value, True) def test_select_false(self): tree = self._simpleparse("Any X WHERE X name FALSE") - constant = tree.children[0].children[1].children[0] + constant = tree.where.children[1].children[0] self.assertEqual(constant.type, 'Boolean') self.assertEqual(constant.value, False) def test_select_date(self): tree = self._simpleparse("Any X WHERE X born TODAY") - constant = tree.children[0].children[1].children[0] + constant = tree.where.children[1].children[0] self.assertEqual(constant.type, 'Date') self.assertEqual(constant.value, 'TODAY') def test_select_int(self): tree = self._simpleparse("Any X WHERE X name 1") - constant = tree.children[0].children[1].children[0] + constant = tree.where.children[1].children[0] self.assertEqual(constant.type, 'Int') self.assertEqual(constant.value, 1) def test_select_float(self): tree = self._simpleparse("Any X WHERE X name 1.0") - constant = tree.children[0].children[1].children[0] + constant = tree.where.children[1].children[0] self.assertEqual(constant.type, 'Float') self.assertEqual(constant.value, 1.0) def test_select_group(self): - tree = self._simpleparse("Any X WHERE X is Person, X name N GROUPBY N") + tree = self._simpleparse("Any X GROUPBY N WHERE X is Person, X name N") self.assertEqual(tree.distinct, False) - self.assertEqual(len(tree.children), 2) - self.assertIsInstance(tree.children[0], nodes.AND) - self.assertIsInstance(tree.children[1], nodes.Group) - self.assertIsInstance(tree.children[1].children[0], nodes.VariableRef) - self.assertEqual(tree.children[1].children[0].name, 'N') + self.assertEqual(len(tree.children), 3) + self.assertIsInstance(tree.where, nodes.And) + self.assertIsInstance(tree.groupby[0], nodes.VariableRef) + self.assertEqual(tree.groupby[0].name, 'N') def test_select_ord_default(self): - tree = self._parse("Any X WHERE X is Person, X name N ORDERBY N") - self.assertEqual(tree.sortterms.children[0].asc, 1) + tree = self._parse("Any X ORDERBY N WHERE X is Person, X name N") + self.assertEqual(tree.children[0].orderby[0].asc, 1) def test_select_ord_desc(self): - tree = self._parse("Any X WHERE X is Person, X name N ORDERBY N DESC") + tree = self._parse("Any X ORDERBY N DESC WHERE X is Person, X name N") select = tree.children[0] - self.assertEqual(len(select.children), 1) - self.assertIsInstance(select.children[0], nodes.AND) - sort = tree.sortterms - self.assertIsInstance(sort, nodes.Sort) - self.assertIsInstance(sort.children[0], nodes.SortTerm) - self.assertEqual(sort.children[0].term.name, 'N') - self.assertEqual(sort.children[0].asc, 0) + self.assertEqual(len(select.children), 3) + self.assertIsInstance(select.where, nodes.And) + sort = select.orderby + self.assertIsInstance(sort[0], nodes.SortTerm) + self.assertEqual(sort[0].term.name, 'N') + self.assertEqual(sort[0].asc, 0) self.assertEqual(select.distinct, False) def test_select_group_ord_asc(self): - tree = self._parse("Any X WHERE X is Person, X name N GROUPBY N ORDERBY N ASC", - "Any X WHERE X is Person, X name N GROUPBY N ORDERBY N") + tree = self._parse("Any X GROUPBY N ORDERBY N ASC WHERE X is Person, X name N", + "Any X GROUPBY N ORDERBY N WHERE X is Person, X name N") select = tree.children[0] - self.assertEqual(len(select.children), 2) - group = select.children[1] - self.assertIsInstance(group, nodes.Group) - self.assertIsInstance(group.children[0], nodes.VariableRef) - self.assertEqual(group.children[0].name, 'N') + self.assertEqual(len(select.children), 4) + group = select.groupby + self.assertIsInstance(group[0], nodes.VariableRef) + self.assertEqual(group[0].name, 'N') def test_select_limit_offset(self): tree = self._parse("Any X WHERE X name 1.0 LIMIT 10 OFFSET 10") @@ -241,17 +240,18 @@ self.assertEqual(tree.offset, 10) def test_copy(self): - tree = self._parse("Any X,LOWER(Y) WHERE X is Person, X name N, X date >= TODAY GROUPBY N ORDERBY N") + tree = self._parse("Any X,LOWER(Y) GROUPBY N ORDERBY N WHERE X is Person, X name N, X date >= TODAY") select = stmts.Select() - restriction = tree.children[0].get_restriction() + restriction = tree.children[0].where self.check_equal_but_not_same(restriction, restriction.copy(select)) - groups = tree.children[0].groups - self.check_equal_but_not_same(groups, groups.copy(select)) - sorts = tree.sortterms - self.check_equal_but_not_same(sorts, sorts.copy(select)) + group = tree.children[0].groupby[0] + self.check_equal_but_not_same(group, group.copy(select)) + sort = tree.children[0].orderby[0] + self.check_equal_but_not_same(sort, sort.copy(select)) def test_selected_index(self): - tree = self._simpleparse("Any X WHERE X is Person, X name N ORDERBY N DESC") + 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) @@ -260,7 +260,7 @@ def test_insert_base_1(self): tree = self._parse("INSERT Person X") self.assertIsInstance(tree, stmts.Insert) - self.assertEqual(tree.children, []) + self.assertEqual(len(tree.children), 1) # test specific attributes self.assertEqual(len(tree.main_relations), 0) self.assertEqual(len(tree.main_variables), 1) @@ -294,8 +294,8 @@ def test_insert_where(self): tree = self._parse("INSERT Person X : X name 'bidule', X friend Y WHERE Y name 'chouette'") - self.assertEqual(len(tree.children), 1) - self.assertIsInstance(tree.children[0], nodes.Relation) + self.assertEqual(len(tree.children), 4) + self.assertIsInstance(tree.where, nodes.Relation) # test specific attributes self.assertEqual(len(tree.main_relations), 2) for relation in tree.main_relations: @@ -309,9 +309,9 @@ def test_update_1(self): tree = self._parse("SET X name 'toto' WHERE X is Person, X name 'bidule'") - self.assertIsInstance(tree, stmts.Update) - self.assertEqual(len(tree.children), 1) - self.assertIsInstance(tree.children[0], nodes.AND) + self.assertIsInstance(tree, stmts.Set) + self.assertEqual(len(tree.children), 2) + self.assertIsInstance(tree.where, nodes.And) # deletion tests ######################################################### @@ -319,14 +319,14 @@ def test_delete_1(self): tree = self._parse("DELETE Person X WHERE X name 'toto'") self.assertIsInstance(tree, stmts.Delete) - self.assertEqual(len(tree.children), 1) - self.assertIsInstance(tree.children[0], nodes.Relation) + self.assertEqual(len(tree.children), 2) + self.assertIsInstance(tree.where, nodes.Relation) def test_delete_2(self): tree = self._parse("DELETE X friend Y WHERE X name 'toto'") self.assertIsInstance(tree, stmts.Delete) - self.assertEqual(len(tree.children), 1) - self.assertIsInstance(tree.children[0], nodes.Relation) + self.assertEqual(len(tree.children), 2) + self.assertIsInstance(tree.where, nodes.Relation) # as_string tests #################################################### @@ -370,14 +370,14 @@ # non regression tests #################################################### def test_get_description_and_get_type(self): - tree = parse("Any N,COUNT(X),NOW-D WHERE X name N, X creation_date D GROUPBY N;") + tree = parse("Any N,COUNT(X),NOW-D GROUPBY N WHERE X name N, X creation_date D;") annotator.annotate(tree) tree.schema = schema self.assertEqual(tree.get_description(), [['name', 'COUNT(name)', 'creation_date']]) - self.assertEqual(tree.children[0].selected[0].get_type(), 'Any') - self.assertEqual(tree.children[0].selected[1].get_type(), 'Int') - self.assertEqual(tree.children[0].defined_vars['D'].get_type({'D': 'Datetime'}), 'Datetime') - self.assertEqual(tree.children[0].selected[2].get_type({'D': 'Datetime'}), 'Interval') + self.assertEqual(tree.where.selected[0].get_type(), 'Any') + self.assertEqual(tree.where.selected[1].get_type(), 'Int') + self.assertEqual(tree.where.defined_vars['D'].get_type({'D': 'Datetime'}), 'Datetime') + self.assertEqual(tree.where.selected[2].get_type({'D': 'Datetime'}), 'Interval') def test_repr_encoding(self): tree = parse(u'Any N where NOT N has_text "bidüle"') @@ -394,15 +394,11 @@ 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), 4) + self.assertEquals(len(varrefs), 5) for varref in varrefs: - self.assertEquals(isinstance(varref, nodes.VariableRef), 1) - names = [ x.name for x in varrefs ] - names.sort() - self.assertEquals(names[0], 'X') - self.assertEquals(names[1], 'X') - self.assertEquals(names[2], 'Y') - self.assertEquals(names[3], 'Y') + self.assertIsInstance(varref, nodes.VariableRef) + self.assertEquals(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] @@ -414,15 +410,11 @@ 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), 4) + self.assertEquals(len(varrefs), 5) for varref in varrefs: - self.assertEquals(isinstance(varref, nodes.VariableRef), 1) - names = [ x.name for x in varrefs ] - names.sort() - self.assertEquals(names[0], 'X') - self.assertEquals(names[1], 'X') - self.assertEquals(names[2], 'Y') - self.assertEquals(names[3], 'Y') + self.assertIsInstance(varref, nodes.VariableRef) + self.assertEquals(sorted(x.name for x in varrefs), + ['X', 'X', 'X', 'Y', 'Y']) if __name__ == '__main__': unittest_main() diff --git a/test/unittest_parser.py b/test/unittest_parser.py --- a/test/unittest_parser.py +++ b/test/unittest_parser.py @@ -17,7 +17,8 @@ # 'SET X travaille Y;', "Personne P WHERE OFFSET 200;", - 'Any X WHERE X nom "toto" GROUPBY X ORDERBY X UNION Any X WHERE X firstname "toto" GROUPBY X ORDERBY X;', + 'Any X GROUPBY X ORDERBY X WHERE X nom "toto" UNION Any X GROUPBY X ORDERBY X WHERE X firstname "toto";', + '(Any X GROUPBY X WHERE X nom "toto") UNION (Any X GROUPBY X WHERE X firstname "toto") ORDERBY X;', 'Any X, X/Y FROM (Any SUM(X) WHERE X is Person) WHERE X is Person;', # missing AS for subquery @@ -34,9 +35,10 @@ 'Any X LIMIT -1;', 'Any X OFFSET -1;', 'Any X ORDERBY Y;', - 'Any N,COUNT(X) FROM (Any X, N WHERE X name N, X is State UNION ' - ' Any X, N WHERE X name N, X is Transition) AS X,N' # alias should be grouped - ' GROUPBY N HAVING COUNT(X)>1', + 'Any N,COUNT(X) GROUPBY N ' + ' HAVING COUNT(X)>1 ' + ' WITH X,N BEING (Any X, N WHERE X name N, X is State UNION ' + ' Any X, N WHERE X name N, X is Transition);', ) # FIXME: this shoud be generated from the spec file @@ -73,7 +75,7 @@ "Any X, COUNT(B) where B concerns X GROUPBY X ORDERBY 1;", - "Any X, COUNT(B) where B concerns X GROUPBY X HAVING COUNT(B) > 2 ORDERBY 1;", + "Any X, COUNT(B) GROUPBY X ORDERBY 1 WHERE B concerns X HAVING COUNT(B) > 2;", 'Any X, MAX(COUNT(B)) WHERE B concerns X GROUPBY X;', # syntaxically correct @@ -81,20 +83,20 @@ 'DELETE Any X WHERE X eid > 12;', # 'Any X WHERE 5 in_state X;', - 'Any X WHERE X eid > 12 UNION Any X WHERE X eid < 23;', '(Any X WHERE X eid > 12) UNION (Any X WHERE X eid < 23);', - 'Any X WHERE X nom "toto" UNION Any X WHERE X firstname "toto";', - 'Any X WHERE X nom "toto" GROUPBY X UNION Any X WHERE X firstname "toto" GROUPBY X ORDERBY X;', + '(Any X WHERE X nom "toto") UNION (Any X WHERE X firstname "toto");', + '(Any X WHERE X nom "toto" GROUPBY X) UNION (Any X WHERE X firstname "toto" GROUPBY X ORDERBY X);', - 'Any X, X/Y FROM (Any SUM(X) WHERE X is Person) AS Y WHERE X is Person;', - 'Any Y, COUNT(X) FROM (Person X UNION Document X) AS Y WHERE X bla Y GROUPBY Y;', + 'Any X, X/Y WHERE X is Person WITH Y BEING (Any SUM(X) WHERE X is Person);', + 'Any Y, COUNT(X) GROUPBY Y WHERE X bla Y WITH Y BEING ((Person X) UNION (Document X));', 'Any T2, COUNT(T1)' - ' FROM (Any X,N WHERE X name N, X transition_of E, E name %(name)s' - ' UNION ' - ' Any X,N WHERE X name N, X state_of E, E name %(name)s) AS (T1,T2)' ' GROUPBY T1' - ' ORDERBY 2 DESC, T2;', + ' ORDERBY 2 DESC, T2;' + ' WITH T1,T2 BEING (' + ' (Any X,N WHERE X name N, X transition_of E, E name %(name)s)' + ' UNION ' + ' (Any X,N WHERE X name N, X state_of E, E name %(name)s))', ) class ParserHercule(TestCase): @@ -117,34 +119,34 @@ def test_precedence_1(self): tree = self.parse("Any X WHERE X firstname 'lulu' AND X name 'toto' OR X name 'tutu';") - base = tree.children[0].children[0] - self.assertEqual(isinstance(base, nodes.OR), 1) - self.assertEqual(isinstance(base.children[0], nodes.AND), 1) + base = tree.children[0].where + self.assertEqual(isinstance(base, nodes.Or), 1) + self.assertEqual(isinstance(base.children[0], nodes.And), 1) self.assertEqual(isinstance(base.children[1], nodes.Relation), 1) self.assertEqual(str(tree), "Any X WHERE (X firstname 'lulu', X name 'toto') OR (X name 'tutu')") def test_precedence_2(self): tree = self.parse("Any X WHERE X firstname 'lulu', X name 'toto' OR X name 'tutu';") - base = tree.children[0].children[0] - self.assertEqual(isinstance(base, nodes.AND), 1) + base = tree.children[0].where + self.assertEqual(isinstance(base, nodes.And), 1) self.assertEqual(isinstance(base.children[0], nodes.Relation), 1) - self.assertEqual(isinstance(base.children[1], nodes.OR), 1) + self.assertEqual(isinstance(base.children[1], nodes.Or), 1) self.assertEqual(str(tree), "Any X WHERE X firstname 'lulu', (X name 'toto') OR (X name 'tutu')") def test_precedence_3(self): tree = self.parse("Any X WHERE X firstname 'lulu' AND (X name 'toto' or X name 'tutu');") - base = tree.children[0].children[0] - self.assertEqual(isinstance(base, nodes.AND), 1) + base = tree.children[0].where + self.assertEqual(isinstance(base, nodes.And), 1) self.assertEqual(isinstance(base.children[0], nodes.Relation), 1) - self.assertEqual(isinstance(base.children[1], nodes.OR), 1) + self.assertEqual(isinstance(base.children[1], nodes.Or), 1) self.assertEqual(str(tree), "Any X WHERE X firstname 'lulu', (X name 'toto') OR (X name 'tutu')") def test_precedence_4(self): tree = self.parse("Any X WHERE X firstname 'lulu' OR X name 'toto' AND X name 'tutu';") - base = tree.children[0].children[0] - self.assertEqual(isinstance(base, nodes.OR), 1) + base = tree.children[0].where + self.assertEqual(isinstance(base, nodes.Or), 1) self.assertEqual(isinstance(base.children[0], nodes.Relation), 1) - self.assertEqual(isinstance(base.children[1], nodes.AND), 1) + self.assertEqual(isinstance(base.children[1], nodes.And), 1) def test_not_precedence_0(self): tree = self.parse("Any X WHERE NOT X firstname 'lulu', X name 'toto';") @@ -160,33 +162,33 @@ def test_string_1(self): tree = self.parse(r"Any X WHERE X firstname 'lu\"lu';") - const = tree.children[0].children[0].children[1].children[0] + const = tree.children[0].where.children[1].children[0] self.assertEqual(const.value, r'lu\"lu') def test_string_2(self): tree = self.parse(r"Any X WHERE X firstname 'lu\'lu';") - const = tree.children[0].children[0].children[1].children[0] + const = tree.children[0].where.children[1].children[0] self.assertEqual(const.value, 'lu\'lu') def test_string_3(self): tree = self.parse(r'Any X WHERE X firstname "lu\'lu";') - const = tree.children[0].children[0].children[1].children[0] + const = tree.children[0].where.children[1].children[0] self.assertEqual(const.value, r"lu\'lu") def test_string_4(self): tree = self.parse(r'Any X WHERE X firstname "lu\"lu";') - const = tree.children[0].children[0].children[1].children[0] + const = tree.children[0].where.children[1].children[0] self.assertEqual(const.value, "lu\"lu") def test_math_1(self): tree = self.parse(r'Any X WHERE X date (TODAY + 1);') - math = tree.children[0].children[0].children[1].children[0] + math = tree.children[0].where.children[1].children[0] self.assert_(isinstance(math, nodes.MathExpression)) self.assertEqual(math.operator, '+') def test_math_2(self): tree = self.parse(r'Any X WHERE X date (TODAY + 1 * 2);') - math = tree.children[0].children[0].children[1].children[0] + math = tree.children[0].where.children[1].children[0] self.assert_(isinstance(math, nodes.MathExpression)) self.assertEqual(math.operator, '+') math2 = math.children[1] @@ -195,7 +197,7 @@ def test_math_3(self): tree = self.parse(r'Any X WHERE X date (TODAY + 1) * 2;') - math = tree.children[0].children[0].children[1].children[0] + math = tree.children[0].where.children[1].children[0] self.assert_(isinstance(math, nodes.MathExpression)) self.assertEqual(math.operator, '*') math2 = math.children[0] @@ -204,23 +206,23 @@ def test_substitute(self): tree = self.parse("Any X WHERE X firstname %(firstname)s;") - cste = tree.children[0].children[0].children[1].children[0] + 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') def test_optional_relation(self): tree = self.parse(r'Any X WHERE X related Y;') - related = tree.children[0].children[0] + related = tree.children[0].where self.assertEquals(related.optional, None) tree = self.parse(r'Any X WHERE X? related Y;') - related = tree.children[0].children[0] + related = tree.children[0].where self.assertEquals(related.optional, 'left') tree = self.parse(r'Any X WHERE X related Y?;') - related = tree.children[0].children[0] + related = tree.children[0].where self.assertEquals(related.optional, 'right') tree = self.parse(r'Any X WHERE X? related Y?;') - related = tree.children[0].children[0] + related = tree.children[0].where self.assertEquals(related.optional, 'both') def test_exists(self): @@ -229,7 +231,7 @@ self.assertEqual(tree.as_string(), "Any X WHERE X firstname 'lulu', " "EXISTS(X owned_by U, U in_group G, (G name 'lulufanclub') OR (G name 'managers'))") - exists = tree.get_nodes(nodes.Exists)[0] + exists = tree.children[0].where.get_nodes(nodes.Exists)[0] self.failUnless(exists.children[0].parent is exists) self.failUnless(exists.parent) diff --git a/test/unittest_stcheck.py b/test/unittest_stcheck.py --- a/test/unittest_stcheck.py +++ b/test/unittest_stcheck.py @@ -18,23 +18,20 @@ 'Any UPPER(Y) WHERE X name "toto"', - 'Any C where C located P, P eid %(x)s ORDERBY N', #15066 + 'Any C ORDERBY N where C located P, P eid %(x)s', #15066 # 'Any COUNT(X),P WHERE X concerns P', #9726 - 'Any X, MAX(COUNT(B)) WHERE B concerns X GROUPBY X;', - - 'Any X WHERE X nom "toto" UNION Any X,F WHERE X firstname F;', + 'Any X, MAX(COUNT(B)) GROUPBY X WHERE B concerns X;', - 'Any Y WHERE X eid 12, X concerns Y UNION Any Y WHERE X eid 13, X located Y ORDERBY X', - - 'Any X, X/Z FROM (Any SUM(X) WHERE X is Person) AS Y WHERE X is Person;', + '(Any X WHERE X nom "toto") UNION (Any X,F WHERE X firstname F);', + + 'Any X, X/Z WHERE X is Person; WITH Y BEING (Any SUM(X) WHERE X is Person)', ) OK_QUERIES = ( - 'Any N,COUNT(X) WHERE X name N GROUPBY N' + '(Any N,COUNT(X) GROUPBY N WHERE X name N)' ' UNION ' - 'Any N,COUNT(X) WHERE X firstname N GROUPBY N' - ' ORDERBY 2', + '(Any N,COUNT(X) GROUPBY N WHERE X firstname N)', ) class CheckClassTest(TestCase): @@ -76,13 +73,14 @@ 'Any X WHERE 12 work_for X'), ('Any X WHERE X work_for Y, Y eid IN (12)', 'Any X WHERE X work_for 12'), - ('Any X WHERE X work_for Y, Y eid IN (12) ORDERBY Y', + ('Any X ORDERBY Y WHERE X work_for Y, Y eid IN (12)', 'Any X WHERE X work_for 12'), ('Any X WHERE X eid 12', 'Any 12'), ('Any X WHERE X is Person, X eid 12', 'Any 12'), - ('Any X,Y WHERE X eid 0, Y eid 1, X work_for Y', 'Any 0,1 WHERE 0 work_for 1'), + ('Any X,Y WHERE X eid 0, Y eid 1, X work_for Y', + 'Any 0,1 WHERE 0 work_for 1'), # no more supported, use outerjoin explicitly # ('Any X,Y WHERE X work_for Y OR NOT X work_for Y', 'Any X,Y WHERE X? work_for Y?'), # ('Any X,Y WHERE NOT X work_for Y OR X work_for Y', 'Any X,Y WHERE X? work_for Y?'), @@ -90,13 +88,19 @@ ("DISTINCT Any P WHERE P connait S OR S connait P, S name 'chouette'", "DISTINCT Any P WHERE P connait S, S name 'chouette'"), # queries that should not be rewritten - ('DELETE Person X WHERE X eid 12', 'DELETE Person X WHERE X eid 12'), - ('Any X WHERE X work_for Y, Y eid IN (12, 13)', 'Any X WHERE X work_for Y, Y eid IN(12, 13)'), - ('Any X WHERE X work_for Y, NOT Y eid 12', 'Any X WHERE X work_for Y, NOT Y eid 12'), - ('Any X WHERE NOT X eid 12', 'Any X WHERE NOT X eid 12'), - ('Any N WHERE X eid 12, X name N', 'Any N WHERE X eid 12, X name N'), + ('DELETE Person X WHERE X eid 12', + 'DELETE Person X WHERE X eid 12'), + ('Any X WHERE X work_for Y, Y eid IN (12, 13)', + 'Any X WHERE X work_for Y, Y eid IN(12, 13)'), + ('Any X WHERE X work_for Y, NOT Y eid 12', + 'Any X WHERE X work_for Y, NOT Y eid 12'), + ('Any X WHERE NOT X eid 12', + 'Any X WHERE NOT X eid 12'), + ('Any N WHERE X eid 12, X name N', + 'Any N WHERE X eid 12, X name N'), - ('Any X WHERE X eid > 12', 'Any X WHERE X eid > 12'), + ('Any X WHERE X eid > 12', + 'Any X WHERE X eid > 12'), ('Any X WHERE X eid 12, X connait P?, X work_for Y', 'Any X WHERE X eid 12, X connait P?, X work_for Y'), @@ -111,11 +115,11 @@ ('Any X WHERE X eid 12, EXISTS(X name "hop" OR X work_for Y?)', "Any 12 WHERE EXISTS((A name 'hop') OR (A work_for Y?), 12 identity A)"), - ('Any X WHERE X eid 12 UNION Any X WHERE X eid 13 ORDERBY X', + ('(Any X WHERE X eid 12) UNION (Any X ORDERBY X WHERE X eid 13)', 'Any 12 UNION Any 13'), - ('Any X FROM (Any X WHERE X eid 12) AS X', - 'Any X FROM (Any 12) AS X'), + ('Any X WITH X BEING (Any X WHERE X eid 12)', + 'Any X WITH X BEING (Any 12)'), ): yield self._test_rewrite, rql, expected @@ -185,13 +189,13 @@ self.assertEquals(len(C.stinfo['relations']), 2) def test_subquery_annotation(self): - rqlst = self.parse('Any X FROM (Any X WHERE C is Company, EXISTS(X work_for C)) AS X').children[0] - C = rqlst.from_[0].children[0].defined_vars['C'] + 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) - rqlst = self.parse('Any X,ET FROM (Any X, ET WHERE C is ET, EXISTS(X work_for C)) AS (X,ET)').children[0] - C = rqlst.from_[0].children[0].defined_vars['C'] - self.failUnless(C.scope is rqlst.from_[0].children[0], C.scope) + 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) if __name__ == '__main__': diff --git a/undo.py b/undo.py --- a/undo.py +++ b/undo.py @@ -8,6 +8,7 @@ __docformat__ = "restructuredtext en" from rql.nodes import VariableRef, Variable, BinaryNode +from rql.stmts import Select class SelectionManager: """manage the operation stacks""" @@ -124,14 +125,20 @@ def __init__(self, node): NodeOperation.__init__(self, node) self.node_parent = node.parent - self.index = node.parent.children.index(node) + if isinstance(self.node_parent, Select): + assert self.node is self.node_parent.where + else: + self.index = node.parent.children.index(node) # 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 - self.parent_index = self.gd_parent.children.index(self.node_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) + def undo(self, selection): """undo the operation on the selection""" if self.binary_remove: @@ -142,8 +149,13 @@ # '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) - self.gd_parent.children[self.parent_index] = 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 + elif isinstance(self.node_parent, Select): + self.node_parent.where = self.node else: self.node_parent.insert(self.index, self.node) # register reference from the removed node @@ -155,14 +167,14 @@ def undo(self, selection): """undo the operation on the selection""" - selection.remove_sort_term(self.node) + self.stmt.remove_sort_term(self.node) class RemoveSortOperation(NodeOperation): - """defines how to undo 'add sort'""" + """defines how to undo 'remove sort'""" def undo(self, selection): """undo the operation on the selection""" - selection.add_sort_term(self.node) + self.stmt.add_sort_term(self.node) class AddGroupOperation(NodeOperation): """defines how to undo 'add group'""" @@ -170,6 +182,13 @@ def undo(self, selection): """undo the operation on the selection""" self.stmt.remove_group_var(self.node) + +class RemoveGroupOperation(NodeOperation): + """defines how to undo 'remove group'""" + + def undo(self, selection): + """undo the operation on the selection""" + self.stmt.add_group_var(self.node) # misc operations ############################################################# diff --git a/utils.py b/utils.py --- a/utils.py +++ b/utils.py @@ -1,8 +1,10 @@ """Miscellaneous utilities for rql -Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -http://www.logilab.fr/ -- mailto:contact@logilab.fr +:organization: Logilab +:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ +__docformat__ = "restructuredtext en" from rql._exceptions import BadRQLQuery @@ -31,10 +33,11 @@ yield var KEYWORDS = set(('INSERT', 'SET', 'DELETE', + 'UNION', 'WITH', 'BEING', 'WHERE', 'AND', 'OR', 'NOT' 'IN', 'LIKE', 'TRUE', 'FALSE', 'NULL', 'TODAY', - 'GROUPBY', 'ORDERBY', 'ASC', 'DESC', + 'GROUPBY', 'HAVING', 'ORDERBY', 'ASC', 'DESC', 'LIMIT', 'OFFSET')) @@ -56,7 +59,6 @@ except AttributeError, ex: yield term - def is_keyword(word): """return true if the given word is a RQL keyword""" return word.upper() in KEYWORDS @@ -98,6 +100,13 @@ # Visitor ##################################################################### +_accept = 'lambda self, visitor, *args, **kwargs: visitor.visit_%s(self, *args, **kwargs)' +_leave = 'lambda self, visitor, *args, **kwargs: visitor.leave_%s(self, *args, **kwargs)' +def build_visitor_stub(classes): + for cls in classes: + cls.accept = eval(_accept % (cls.__name__.lower())) + cls.leave = eval(_leave % (cls.__name__.lower())) + class RQLVisitorHandler: """handler providing a dummy implementation of all callbacks necessary to visit a RQL syntax tree @@ -109,18 +118,13 @@ pass def visit_delete(self, delete): pass - def visit_update(self, update): + def visit_set(self, update): pass - def visit_group(self, group): - pass - def visit_sort(self, sort): + def visit_select(self, selection): pass def visit_sortterm(self, sortterm): pass - - def visit_select(self, selection): - pass def visit_and(self, et): pass