author | Sylvain Thénault <sylvain.thenault@logilab.fr> |
Wed, 27 Jul 2011 19:28:00 +0200 | |
changeset 651 | 2a267149fee6 |
parent 645 | bc901a7460d1 (current diff) |
parent 650 | 52b945da43ea (diff) |
child 660 | 64ade95d2b85 |
--- a/.hgtags Wed Jul 20 17:11:06 2011 +0200 +++ b/.hgtags Wed Jul 27 19:28:00 2011 +0200 @@ -63,3 +63,5 @@ 21e94bc12c1fcb7f97826fe6aae5dbe62cc4bd06 rql-debian-version-0.28.0-1 c45e9d1c0db45b2f3f158f6b1be4dbe25d28c84d rql-version-0.29.0 78e09096f881259f1f5d3080a78819997fe1eede rql-debian-version-0.29.0-1 +cf4fcca7c28946f384c0b15e59b77231d40148b5 rql-version-0.29.1 +0c9ac2a5635d3f6a224b799aecea836c4277ba21 rql-debian-version-0.29.1-1
--- a/ChangeLog Wed Jul 20 17:11:06 2011 +0200 +++ b/ChangeLog Wed Jul 27 19:28:00 2011 +0200 @@ -1,7 +1,7 @@ ChangeLog for RQL ================= --- +2011-07-27 -- 0.29.1 * #70264: remove_group_var renamed into remove_group_term and fixed implementation @@ -15,17 +15,27 @@ * #71157: bad analyze when using functions + * #71415: needs to allow outer join on rhs of final relation and in HAVING express + * Select.replace must properly reset old node's parent attribute * new undo_modification context manager on select nodes + + 2011-06-09 -- 0.29.0 * support != operator for non equality + * support for CAST function + * support for regexp-based pattern matching using a REGEXP operator + * may now GROUPBY functions / column number + * fix parsing of negative float + + 2011-01-12 -- 0.28.0 * enhance rewrite_shared_optional so one can specify where the new identity relation should be added (used by cw multi-sources planner)
--- a/__pkginfo__.py Wed Jul 20 17:11:06 2011 +0200 +++ b/__pkginfo__.py Wed Jul 27 19:28:00 2011 +0200 @@ -20,7 +20,7 @@ __docformat__ = "restructuredtext en" modname = "rql" -numversion = (0, 29, 0) +numversion = (0, 29, 1) version = '.'.join(str(num) for num in numversion) license = 'LGPL'
--- a/debian/changelog Wed Jul 20 17:11:06 2011 +0200 +++ b/debian/changelog Wed Jul 27 19:28:00 2011 +0200 @@ -1,3 +1,9 @@ +rql (0.29.1-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 27 Jul 2011 15:05:34 +0200 + rql (0.29.0-1) unstable; urgency=low * debian/control:
--- a/nodes.py Wed Jul 20 17:11:06 2011 +0200 +++ b/nodes.py Wed Jul 27 19:28:00 2011 +0200 @@ -490,20 +490,29 @@ <, <=, =, >=, > LIKE and ILIKE operators have a unique children. """ - __slots__ = ('operator',) + __slots__ = ('operator', 'optional') - def __init__(self, operator, value=None): + def __init__(self, operator, value=None, optional=None): Node.__init__(self) if operator == '~=': operator = 'ILIKE' assert operator in OPERATORS, operator self.operator = operator.encode() + self.optional = optional if value is not None: self.append(value) def initargs(self, stmt): """return list of arguments to give to __init__ to clone this node""" - return (self.operator,) + return (self.operator, None, self.optional) + + def set_optional(self, left, right): + if left and right: + self.optional = 'both' + elif left: + self.optional = 'left' + elif right: + self.optional = 'right' def is_equivalent(self, other): if not Node.is_equivalent(self, other):
--- a/parser.g Wed Jul 20 17:11:06 2011 +0200 +++ b/parser.g Wed Jul 27 19:28:00 2011 +0200 @@ -233,7 +233,7 @@ )* {{ return node }} rule rels_and<<S>>: rels_not<<S>> {{ node = rels_not }} - ( AND rels_not<<S>> {{ node = And(node, rels_not) }} + ( AND rels_not<<S>> {{ node = And(node, rels_not) }} )* {{ return node }} rule rels_not<<S>>: NOT rel<<S>> {{ return Not(rel) }} @@ -265,7 +265,7 @@ )* {{ return node }} rule exprs_and<<S>>: exprs_not<<S>> {{ node = exprs_not }} - ( AND exprs_not<<S>> {{ node = And(node, exprs_not) }} + ( AND exprs_not<<S>> {{ node = And(node, exprs_not) }} )* {{ return node }} rule exprs_not<<S>>: NOT balanced_expr<<S>> {{ return Not(balanced_expr) }} @@ -280,11 +280,12 @@ #// #// Any T2 WHERE T1 relation T2 HAVING (1+2) < COUNT(T1); rule balanced_expr<<S>>: r"\(" logical_expr<<S>> r"\)" {{ return logical_expr }} - | expr_add<<S>> expr_op<<S>> {{ expr_op.insert(0, expr_add); return expr_op }} + | expr_add<<S>> opt_left<<S>> + expr_op<<S>> opt_right<<S>> {{ expr_op.insert(0, expr_add); expr_op.set_optional(opt_left, opt_right); return expr_op }} # // cant use expr<<S>> without introducing some ambiguities rule expr_op<<S>>: CMP_OP expr_add<<S>> {{ return Comparison(CMP_OP.upper(), expr_add) }} - | in_expr<<S>> {{ return Comparison('=', in_expr) }} + | in_expr<<S>> {{ return Comparison('=', in_expr) }} #// common statements ###########################################################
--- a/parser.py Wed Jul 20 17:11:06 2011 +0200 +++ b/parser.py Wed Jul 27 19:28:00 2011 +0200 @@ -1,7 +1,7 @@ """yapps input grammar for RQL. :organization: Logilab -:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:copyright: 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr @@ -451,16 +451,16 @@ def opt_left(self, S, _parent=None): _context = self.Context(_parent, self._scanner, 'opt_left', [S]) - _token = self._peek('QMARK', 'R_TYPE', context=_context) + _token = self._peek('QMARK', 'R_TYPE', 'CMP_OP', "'IN'", context=_context) if _token == 'QMARK': QMARK = self._scan('QMARK', context=_context) return 'left' - else: # == 'R_TYPE' + else: # in ['R_TYPE', 'CMP_OP', "'IN'"] pass 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', 'LIMIT', 'OFFSET', 'WHERE', context=_context) + _token = self._peek('QMARK', 'AND', 'OR', "','", 'r"\\)"', 'WITH', 'GROUPBY', 'HAVING', 'ORDERBY', "';'", 'LIMIT', 'OFFSET', 'WHERE', context=_context) if _token == 'QMARK': QMARK = self._scan('QMARK', context=_context) return 'right' @@ -518,8 +518,10 @@ return logical_expr elif 1: expr_add = self.expr_add(S, _context) + opt_left = self.opt_left(S, _context) expr_op = self.expr_op(S, _context) - expr_op.insert(0, expr_add); return expr_op + opt_right = self.opt_right(S, _context) + expr_op.insert(0, expr_add); expr_op.set_optional(opt_left, opt_right); return expr_op else: raise runtime.SyntaxError(_token[0], 'Could not match balanced_expr') @@ -549,7 +551,7 @@ _context = self.Context(_parent, self._scanner, 'decl_vars', [R]) E_TYPE = self._scan('E_TYPE', context=_context) var = self.var(R, _context) - while self._peek("','", 'R_TYPE', 'QMARK', 'WHERE', '":"', 'HAVING', "';'", 'MUL_OP', 'BEING', 'WITH', 'GROUPBY', 'ORDERBY', 'ADD_OP', 'LIMIT', 'OFFSET', 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', "'IN'", 'AND', 'OR', context=_context) == "','": + while self._peek("','", 'R_TYPE', 'QMARK', 'WHERE', '":"', 'CMP_OP', "'IN'", 'HAVING', "';'", 'MUL_OP', 'BEING', 'WITH', 'GROUPBY', 'ORDERBY', 'ADD_OP', 'LIMIT', 'OFFSET', 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'AND', 'OR', context=_context) == "','": R.add_main_variable(E_TYPE, var) self._scan("','", context=_context) E_TYPE = self._scan('E_TYPE', context=_context) @@ -588,7 +590,7 @@ _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"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', "'IN'", 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == 'ADD_OP': + while self._peek('ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == 'ADD_OP': ADD_OP = self._scan('ADD_OP', context=_context) expr_mul = self.expr_mul(S, _context) node = MathExpression( ADD_OP, node, expr_mul ) @@ -598,7 +600,7 @@ _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"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', "'IN'", 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == 'MUL_OP': + while self._peek('MUL_OP', 'ADD_OP', 'QMARK', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == 'MUL_OP': MUL_OP = self._scan('MUL_OP', context=_context) expr_base = self.expr_base(S, _context) node = MathExpression( MUL_OP, node, expr_base) @@ -632,7 +634,7 @@ F = Function(FUNCTION) if self._peek('r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"': expr_add = self.expr_add(S, _context) - while self._peek("','", 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', "'IN'", 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == "','": + while self._peek("','", 'QMARK', 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == "','": F.append(expr_add) self._scan("','", context=_context) expr_add = self.expr_add(S, _context) @@ -647,7 +649,7 @@ F = Function('IN') if self._peek('r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"': expr_add = self.expr_add(S, _context) - while self._peek("','", 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', "'IN'", 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == "','": + while self._peek("','", 'QMARK', 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', 'R_TYPE', "'IN'", 'GROUPBY', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == "','": F.append(expr_add) self._scan("','", context=_context) expr_add = self.expr_add(S, _context) @@ -701,7 +703,7 @@ P = Hercule(HerculeScanner(text)) return runtime.wrap_error_reporter(P, rule) -if __name__ == 'old__main__': +if __name__ == '__main__': from sys import argv, stdin if len(argv) >= 2: if len(argv) >= 3:
--- a/stcheck.py Wed Jul 20 17:11:06 2011 +0200 +++ b/stcheck.py Wed Jul 27 19:28:00 2011 +0200 @@ -372,9 +372,9 @@ except KeyError: state.error('unknown relation `%s`' % rtype) else: - if relation.optional and rschema.final: - state.error("shouldn't use optional on final relation `%s`" - % relation.r_type) + if rschema.final and relation.optional not in (None, 'right'): + state.error("optional may only be set on the rhs on final relation `%s`" + % relation.r_type) if self.special_relations.get(rtype) == 'uid': if state.var_info.get(lhsvar, 0) & VAR_HAS_UID_REL: state.error('can only one uid restriction per variable '
--- a/test/unittest_parser.py Wed Jul 20 17:11:06 2011 +0200 +++ b/test/unittest_parser.py Wed Jul 27 19:28:00 2011 +0200 @@ -158,6 +158,10 @@ 'Any YEAR(XD),COUNT(X) GROUPBY 1 ORDERBY 1 WHERE X date XD;', 'Any -1.0;', + + 'Any U,G WHERE U login UL, G name GL, G is CWGroup HAVING UPPER(UL)=UPPER(GL)?;', + + 'Any U,G WHERE U login UL, G name GL, G is CWGroup HAVING UPPER(UL)?=UPPER(GL);', ) class ParserHercule(TestCase): @@ -315,7 +319,7 @@ for rql in SPEC_QUERIES: # print "Orig:", rql # print "Resu:", rqltree - yield self.assert_, self.parse(rql, True) + yield self.parse, rql, True def test_raise_badsyntax_error(self): for rql in BAD_SYNTAX_QUERIES: