diff --git a/ChangeLog b/ChangeLog index 549ca221e30e4ae1d0a7e968e3818ea142ebb725_Q2hhbmdlTG9n..0ab790535cea1cb811fdc32e7cb692f7629ae05a_Q2hhbmdlTG9n 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,7 +2,9 @@ ================= -- -* suport != operator for non equality +* support != operator for non equality +* support for CAST function +* support for regexp-based pattern matching using a REGEXP operator 2011-01-12 -- 0.28.0 * enhance rewrite_shared_optional so one can specify where the new identity diff --git a/__pkginfo__.py b/__pkginfo__.py index 549ca221e30e4ae1d0a7e968e3818ea142ebb725_X19wa2dpbmZvX18ucHk=..0ab790535cea1cb811fdc32e7cb692f7629ae05a_X19wa2dpbmZvX18ucHk= 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -81,7 +81,7 @@ install_requires = [ 'logilab-common >= 0.47.0', - 'logilab-database', + 'logilab-database >= 1.6.0', 'yapps == 2.1.1', # XXX to ensure we don't use the broken pypi version 'constraint', # fallback if the gecode compiled module is missing ] diff --git a/debian/control b/debian/control index 549ca221e30e4ae1d0a7e968e3818ea142ebb725_ZGViaWFuL2NvbnRyb2w=..0ab790535cea1cb811fdc32e7cb692f7629ae05a_ZGViaWFuL2NvbnRyb2w= 100644 --- a/debian/control +++ b/debian/control @@ -11,7 +11,7 @@ Package: python-rql Architecture: any XB-Python-Version: ${python:Versions} -Depends: ${python:Depends}, ${misc:Depends}, ${shlibs:Depends}, python-logilab-common (>= 0.35.3-1), yapps2-runtime, python-logilab-database +Depends: ${python:Depends}, ${misc:Depends}, ${shlibs:Depends}, python-logilab-common (>= 0.35.3-1), yapps2-runtime, python-logilab-database (>= 1.6.0) Conflicts: cubicweb-common (<= 3.8.3) Provides: ${python:Provides} Description: relationship query language (RQL) utilities diff --git a/nodes.py b/nodes.py index 549ca221e30e4ae1d0a7e968e3818ea142ebb725_bm9kZXMucHk=..0ab790535cea1cb811fdc32e7cb692f7629ae05a_bm9kZXMucHk= 100644 --- a/nodes.py +++ b/nodes.py @@ -28,6 +28,8 @@ from datetime import datetime, date, time, timedelta from time import localtime +from logilab.database import DYNAMIC_RTYPE + from rql import CoercionError from rql.base import BaseNode, Node, BinaryNode, LeafNode from rql.utils import (function_description, quote, uquote, build_visitor_stub, @@ -440,12 +442,7 @@ return False rhs = self.children[1] if isinstance(rhs, Comparison): - try: - rhs = rhs.children[0] - except: - print 'opppp', rhs - print rhs.root - raise + rhs = rhs.children[0] # else: relation used in SET OR DELETE selection return ((isinstance(rhs, Constant) and rhs.type == 'etype') or (isinstance(rhs, Function) and rhs.name == 'IN')) @@ -484,7 +481,7 @@ self.optional= value -OPERATORS = frozenset(('=', '!=', '<', '<=', '>=', '>', 'ILIKE', 'LIKE')) +OPERATORS = frozenset(('=', '!=', '<', '<=', '>=', '>', 'ILIKE', 'LIKE', 'REGEXP')) class Comparison(HSMixin, Node): """handle comparisons: @@ -625,7 +622,8 @@ solution is an optional variable/etype mapping """ - rtype = self.descr().rtype + func_descr = self.descr() + rtype = func_descr.rql_return_type(self) if rtype is None: # XXX support one variable ref child try: diff --git a/parser.g b/parser.g index 549ca221e30e4ae1d0a7e968e3818ea142ebb725_cGFyc2VyLmc=..0ab790535cea1cb811fdc32e7cb692f7629ae05a_cGFyc2VyLmc= 100644 --- a/parser.g +++ b/parser.g @@ -88,7 +88,7 @@ token FALSE: r'(?i)FALSE' token NULL: r'(?i)NULL' token EXISTS: r'(?i)EXISTS' - token CMP_OP: r'(?i)<=|<|>=|>|!=|=|~=|LIKE|ILIKE' + token CMP_OP: r'(?i)<=|<|>=|>|!=|=|~=|LIKE|ILIKE|REGEXP' token ADD_OP: r'\+|-' token MUL_OP: r'\*|/' token FUNCTION: r'[A-Za-z_]+\s*(?=\()' diff --git a/parser.py b/parser.py index 549ca221e30e4ae1d0a7e968e3818ea142ebb725_cGFyc2VyLnB5..0ab790535cea1cb811fdc32e7cb692f7629ae05a_cGFyc2VyLnB5 100644 --- a/parser.py +++ b/parser.py @@ -1,19 +1,2 @@ -# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of rql. -# -# rql is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# rql is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with rql. If not, see <http://www.gnu.org/licenses/>. """yapps input grammar for RQL. @@ -18,5 +1,8 @@ """yapps input grammar for RQL. +: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 @@ -109,7 +95,7 @@ ('FALSE', re.compile('(?i)FALSE')), ('NULL', re.compile('(?i)NULL')), ('EXISTS', re.compile('(?i)EXISTS')), - ('CMP_OP', re.compile('(?i)<=|<|>=|>|!=|=|~=|LIKE|ILIKE')), + ('CMP_OP', re.compile('(?i)<=|<|>=|>|!=|=|~=|LIKE|ILIKE|REGEXP')), ('ADD_OP', re.compile('\\+|-')), ('MUL_OP', re.compile('\\*|/')), ('FUNCTION', re.compile('[A-Za-z_]+\\s*(?=\\()')), @@ -709,7 +695,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: @@ -719,25 +705,3 @@ print parse(argv[1], f.read()) else: print >>sys.stderr, 'Args: <rule> [<filename>]' # End -- grammar generated by Yapps -"""Main parser command. - -""" -__docformat__ = "restructuredtext en" - -if __name__ == '__main__': - from sys import argv - - parser = Hercule(HerculeScanner(argv[1])) - e_types = {} - # parse the RQL string - try: - tree = parser.goal(e_types) - print '-'*80 - print tree - print '-'*80 - print repr(tree) - print e_types - except SyntaxError, ex: - # try to get error message from yapps - from yapps.runtime import print_error - print_error(ex, parser._scanner) diff --git a/stcheck.py b/stcheck.py index 549ca221e30e4ae1d0a7e968e3818ea142ebb725_c3RjaGVjay5weQ==..0ab790535cea1cb811fdc32e7cb692f7629ae05a_c3RjaGVjay5weQ== 100644 --- a/stcheck.py +++ b/stcheck.py @@ -455,8 +455,5 @@ def visit_constant(self, constant, state): #assert len(constant.children)==0 if constant.type == 'etype': - if constant.relation().r_type not in ('is', 'is_instance_of'): - msg ='using an entity type in only allowed with "is" relation' - state.error(msg) - if not constant.value in self.schema: + if constant.value not in self.schema: state.error('unknown entity type %s' % constant.value) @@ -462,4 +459,12 @@ state.error('unknown entity type %s' % constant.value) + rel = constant.relation() + if rel is not None: + if rel.r_type not in ('is', 'is_instance_of'): + msg ='using an entity type in only allowed with "is" relation' + state.error(msg) + elif not (isinstance(constant.parent, Function) and + constant.parent.name == 'CAST'): + state.error('Entity types can only be used inside a CAST()') def leave_constant(self, node, state): pass diff --git a/test/unittest_nodes.py b/test/unittest_nodes.py index 549ca221e30e4ae1d0a7e968e3818ea142ebb725_dGVzdC91bml0dGVzdF9ub2Rlcy5weQ==..0ab790535cea1cb811fdc32e7cb692f7629ae05a_dGVzdC91bml0dGVzdF9ub2Rlcy5weQ== 100644 --- a/test/unittest_nodes.py +++ b/test/unittest_nodes.py @@ -564,6 +564,12 @@ tree = sparse('Any X,R,D,Y WHERE X work_for R, R creation_date D, Y connait X') self.assertEqual(tree.get_description(0), [['Person, Student', 'work_for', 'creation_date', 'connait']]) + def test_get_description_cast(self): + tree = sparse('Any CAST(String, Y) WHERE X creation_date Y') + select = tree.children[0] + self.assertEqual(select.selection[0].get_type(), 'String') + self.assertEqual(tree.get_description(0), [['String']]) + class GetNodesFunctionTest(TestCase): def test_known_values_1(self): diff --git a/utils.py b/utils.py index 549ca221e30e4ae1d0a7e968e3818ea142ebb725_dXRpbHMucHk=..0ab790535cea1cb811fdc32e7cb692f7629ae05a_dXRpbHMucHk= 100644 --- a/utils.py +++ b/utils.py @@ -68,7 +68,7 @@ from logilab.common.decorators import monkeypatch -from logilab.database import SQL_FUNCTIONS_REGISTRY, FunctionDescr +from logilab.database import SQL_FUNCTIONS_REGISTRY, FunctionDescr, CAST RQL_FUNCTIONS_REGISTRY = SQL_FUNCTIONS_REGISTRY.copy() @@ -85,6 +85,19 @@ raise BadRQLQuery("backend %s doesn't support function %s" % (backend, self.name)) +@monkeypatch(FunctionDescr) +def rql_return_type(self, funcnode): + return self.rtype + +@monkeypatch(CAST) +def st_description(self, funcnode, mainindex, tr): + return self.rql_return_type(funcnode) + +@monkeypatch(CAST) +def rql_return_type(self, funcnode): + return funcnode.children[0].value + + def iter_funcnode_variables(funcnode): for term in funcnode.children: try: