--- a/.hgtags Wed Nov 09 18:56:49 2011 +0100
+++ b/.hgtags Thu Nov 08 14:46:01 2012 +0100
@@ -71,3 +71,7 @@
13cd741f8e144265c15bb225fad863234fc8ce16 rql-debian-version-0.30.1-1
bb70a998ced6a839fd1e28686e4ff5254feefde3 rql-version-0.31.0
f4f27e4c588e40ba9c6c9c5d92cb22f74f1207a6 rql-debian-version-0.31.0-1
+6135951b6c7e64cccfc473c69cd025305b4de7e8 rql-version-0.31.1
+543fe6d74b492f198d8bf7dc5e90ff3e3276fa8b rql-debian-version-0.31.1-1
+55af3a14cc29dedab697deabfc7602e140ac3dd8 rql-version-0.31.2
+513a02cce3c22ea14a13f878ccee21c9540a26da rql-debian-version-0.31.2-1
--- a/ChangeLog Wed Nov 09 18:56:49 2011 +0100
+++ b/ChangeLog Thu Nov 08 14:46:01 2012 +0100
@@ -1,16 +1,36 @@
ChangeLog for RQL
=================
---
+2012-03-29 -- 0.31.2
+ * #88559: speed up query solutions analysis
+
+ * moved valuable_references from Variable to Referencable, it makes sense for
+ ColumnAliases as well
+
+ * various cleanups
+
+
+
+2012-02-03 -- 0.31.1
+ * #87988: fixed bug in simplify with sub-queries
+
+
+
+2011-11-09 -- 0.31.0
* #78681: don't crash on column aliases used in outer join
+
* #81394: HAVING support in write queries (INSERT,SET,DELETE)
+
* #80799: fix wrong type analysis with 'NOT identity'
+
* when possible, use entity type as translation context of relation
(break cw < 3.13.10 compat)
+
* #81817: fix add_type_restriction for cases where some types restriction is already in there
+
+
2011-09-07 -- 0.30.1
-
* #74727: allow entity types to end with a capitalized letter
provided they contain a lower-cased letter
--- a/__init__.py Wed Nov 09 18:56:49 2011 +0100
+++ b/__init__.py Thu Nov 08 14:46:01 2012 +0100
@@ -136,8 +136,8 @@
def _simplify(self, select):
# recurse on subqueries first
for subquery in select.with_:
- for select in subquery.query.children:
- self._simplify(select)
+ for subselect in subquery.query.children:
+ self._simplify(subselect)
rewritten = False
for var in select.defined_vars.values():
stinfo = var.stinfo
--- a/__pkginfo__.py Wed Nov 09 18:56:49 2011 +0100
+++ b/__pkginfo__.py Thu Nov 08 14:46:01 2012 +0100
@@ -1,5 +1,5 @@
# pylint: disable-msg=W0622
-# copyright 2004-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2004-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of rql.
@@ -20,7 +20,7 @@
__docformat__ = "restructuredtext en"
modname = "rql"
-numversion = (0, 31, 0)
+numversion = (0, 31, 2)
version = '.'.join(str(num) for num in numversion)
license = 'LGPL'
@@ -83,10 +83,10 @@
'logilab-common >= 0.47.0',
'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
+ 'constraint >= 0.5.0', # fallback if the gecode compiled module is missing
]
# links to download yapps2 package that is not (yet) registered in pypi
dependency_links = [
- "http://ftp.logilab.org/pub/yapps/yapps2-2.1.1.zip#egg=yapps-2.1.1",
+ "http://download.logilab.org/pub/yapps/yapps2-2.1.1.zip#egg=yapps-2.1.1",
]
--- a/analyze.py Wed Nov 09 18:56:49 2011 +0100
+++ b/analyze.py Thu Nov 08 14:46:01 2012 +0100
@@ -22,6 +22,7 @@
from cStringIO import StringIO
+import os, sys
from rql import TypeResolverException, nodes
from pprint import pprint
@@ -29,6 +30,9 @@
from itertools import izip
try:
+ pure = bool(os.environ.get('RQL_USE_PURE_PYTHON_ANALYSE', 0))
+ if pure:
+ raise ImportError
import rql_solve
except ImportError:
rql_solve = None
@@ -316,9 +320,9 @@
def set_schema(self, schema):
self.schema = schema
# default domains for a variable
- self._base_domain = [str(etype) for etype in schema.entities()]
- self._nonfinal_domain = [str(etype) for etype in schema.entities()
- if not etype.final]
+ self._base_domain = set(str(etype) for etype in schema.entities())
+ self._nonfinal_domain = set(str(etype) for etype in schema.entities()
+ if not etype.final)
def solve(self, node, constraints):
# debug info
@@ -339,14 +343,17 @@
node.set_possible_types(sols, self.kwargs, self.var_solkey)
def _visit(self, node, constraints=None):
- """Recurse down the tree."""
+ """Recurse down the tree.
+
+ * node: rql node to process
+ * constraints: a XxxCSPProblem object.
+ """
func = getattr(self, 'visit_%s' % node.__class__.__name__.lower())
if constraints is None:
func(node)
- else:
- if func(node, constraints) is None:
- for c in node.children:
- self._visit(c, constraints)
+ elif func(node, constraints) is None:
+ for c in node.children:
+ self._visit(c, constraints)
def _uid_node_types(self, valnode):
types = set()
@@ -505,17 +512,21 @@
# filter according to domain necessary for column aliases
rhsdomain = constraints.domains[rhsvar]
res = []
- for fromtype, totypes in rschema.associations():
- if not fromtype in lhsdomain:
- continue
- ptypes = [str(t) for t in totypes if t in rhsdomain]
- res.append( [ ([lhsvar], [str(fromtype)]),
- ([rhsvar], ptypes) ] )
+ var_types = []
+ same_var = (rhsvar == lhsvar)
+
+ for frometype, toetypes in rschema.associations():
+ fromtype = str(frometype)
+ if fromtype in lhsdomain:
+ totypes = set(str(t) for t in toetypes)
+ ptypes = totypes & rhsdomain
+ res.append( [ ([lhsvar], [str(fromtype)]),
+ ([rhsvar], list(ptypes)) ] )
+ if same_var and (fromtype in totypes): #ptypes ?
+ var_types.append(fromtype)
constraints.or_and(res)
- if rhsvar == lhsvar:
- res = [str(fromtype) for fromtype, totypes in rschema.associations()
- if (fromtype in totypes and fromtype in lhsdomain)]
- constraints.var_has_types( lhsvar, res )
+ if same_var:
+ constraints.var_has_types( lhsvar, var_types)
else:
# XXX consider rhs.get_type?
lhsdomain = constraints.domains[lhs.name]
--- a/base.py Wed Nov 09 18:56:49 2011 +0100
+++ b/base.py Thu Nov 08 14:46:01 2012 +0100
@@ -22,8 +22,10 @@
__docformat__ = "restructuredtext en"
+from rql.utils import VisitableMixIn
-class BaseNode(object):
+
+class BaseNode(VisitableMixIn):
__slots__ = ('parent',)
def __str__(self):
--- a/debian/changelog Wed Nov 09 18:56:49 2011 +0100
+++ b/debian/changelog Thu Nov 08 14:46:01 2012 +0100
@@ -1,3 +1,15 @@
+rql (0.31.2-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 29 Mar 2012 13:58:45 +0200
+
+rql (0.31.1-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 03 Feb 2012 16:10:56 +0100
+
rql (0.31.0-1) unstable; urgency=low
* new upstream release
--- a/debian/control Wed Nov 09 18:56:49 2011 +0100
+++ b/debian/control Thu Nov 08 14:46:01 2012 +0100
@@ -3,7 +3,12 @@
Priority: optional
Maintainer: Logilab Packaging Team <contact@logilab.fr>
Uploaders: Sylvain Thenault <sylvain.thenault@logilab.fr>, Nicolas Chauvat <nicolas.chauvat@logilab.fr>
-Build-Depends: debhelper (>= 5.0.37.1), python-support, python-all-dev (>=2.5), python-all (>=2.5), libgecode-dev, python-sphinx, g++ (>= 4)
+Build-Depends: debhelper (>= 5.0.37.1),
+ python-support,
+ python-all-dev (>=2.5),
+ python-all (>=2.5),
+ libgecode-dev,
+ python-sphinx, g++ (>= 4)
XS-Python-Version: >= 2.5
Standards-Version: 3.9.1
Homepage: http://www.logilab.org/project/rql
@@ -11,7 +16,10 @@
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 (>= 1.6.0)
+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.13.9)
Provides: ${python:Provides}
Description: relationship query language (RQL) utilities
--- a/nodes.py Wed Nov 09 18:56:49 2011 +0100
+++ b/nodes.py Thu Nov 08 14:46:01 2012 +0100
@@ -1,4 +1,4 @@
-# copyright 2004-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2004-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of rql.
@@ -32,8 +32,8 @@
from rql import CoercionError, RQLException
from rql.base import BaseNode, Node, BinaryNode, LeafNode
-from rql.utils import (function_description, quote, uquote, build_visitor_stub,
- common_parent)
+from rql.utils import (function_description, quote, uquote, common_parent,
+ VisitableMixIn)
CONSTANT_TYPES = frozenset((None, 'Date', 'Datetime', 'Boolean', 'Float', 'Int',
'String', 'Substitute', 'etype'))
@@ -901,7 +901,7 @@
###############################################################################
-class Referenceable(object):
+class Referenceable(VisitableMixIn):
__slots__ = ('name', 'stinfo', 'stmt')
def __init__(self, name):
@@ -1076,6 +1076,13 @@
return rel
return None
+ def valuable_references(self):
+ """return the number of "valuable" references :
+ references is in selection or in a non type (is) relations
+ """
+ stinfo = self.stinfo
+ return len(stinfo['selected']) + len(stinfo['relations'])
+
class ColumnAlias(Referenceable):
__slots__ = ('colnum', 'query',
@@ -1125,14 +1132,4 @@
def __repr__(self):
return '%s(%#X)' % (self.name, id(self))
- def valuable_references(self):
- """return the number of "valuable" references :
- references is in selection or in a non type (is) relations
- """
- stinfo = self.stinfo
- return len(stinfo['selected']) + len(stinfo['relations'])
-
-build_visitor_stub((SubQuery, And, Or, Not, Exists, Relation,
- Comparison, MathExpression, UnaryExpression, Function,
- Constant, VariableRef, SortTerm, ColumnAlias, Variable))
--- a/stmts.py Wed Nov 09 18:56:49 2011 +0100
+++ b/stmts.py Thu Nov 08 14:46:01 2012 +0100
@@ -31,7 +31,7 @@
from rql import BadRQLQuery, CoercionError, nodes
from rql.base import BaseNode, Node
-from rql.utils import rqlvar_maker, build_visitor_stub
+from rql.utils import rqlvar_maker
_MARKER = object()
@@ -1113,4 +1113,3 @@
return new
-build_visitor_stub((Union, Select, Insert, Delete, Set))
--- a/test/unittest_parser.py Wed Nov 09 18:56:49 2011 +0100
+++ b/test/unittest_parser.py Thu Nov 08 14:46:01 2012 +0100
@@ -187,7 +187,7 @@
tree = self.parse(u"Any X WHERE X name 'Ångström';")
base = tree.children[0].where
comparison = base.children[1]
- self.failUnless(isinstance(comparison, nodes.Comparison))
+ self.assertTrue(isinstance(comparison, nodes.Comparison))
rhs = comparison.children[0]
self.assertEqual(type(rhs.value), unicode)
@@ -306,8 +306,8 @@
"Any X WHERE X firstname 'lulu', "
"EXISTS(X owned_by U, U in_group G, (G name 'lulufanclub') OR (G name 'managers'))")
exists = tree.children[0].where.get_nodes(nodes.Exists)[0]
- self.failUnless(exists.children[0].parent is exists)
- self.failUnless(exists.parent)
+ self.assertTrue(exists.children[0].parent is exists)
+ self.assertTrue(exists.parent)
def test_etype(self):
tree = self.parse('EmailAddress X;')
--- a/test/unittest_stcheck.py Wed Nov 09 18:56:49 2011 +0100
+++ b/test/unittest_stcheck.py Thu Nov 08 14:46:01 2012 +0100
@@ -1,4 +1,4 @@
-# copyright 2004-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2004-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of rql.
@@ -225,8 +225,8 @@
self.simplify(tree)
copy = tree.copy()
exists = copy.get_nodes(nodes.Exists)[0]
- self.failUnless(exists.children[0].parent is exists)
- self.failUnless(exists.parent)
+ self.assertTrue(exists.children[0].parent is exists)
+ self.assertTrue(exists.parent)
def test_copy_internals(self):
root = self.parse('Any X,U WHERE C owned_by U, NOT X owned_by U, X eid 1, C eid 2')
@@ -254,7 +254,7 @@
# def test_simplified(self):
# rqlst = self.parse('Any L WHERE 5 name L')
# self.annotate(rqlst)
-# self.failUnless(rqlst.defined_vars['L'].stinfo['attrvar'])
+# self.assertTrue(rqlst.defined_vars['L'].stinfo['attrvar'])
def test_is_rel_no_scope_1(self):
"""is relation used as type restriction should not affect variable's
@@ -262,13 +262,13 @@
"""
rqlst = self.parse('Any X WHERE C is Company, EXISTS(X work_for C)').children[0]
C = rqlst.defined_vars['C']
- self.failIf(C.scope is rqlst, C.scope)
+ self.assertFalse(C.scope is rqlst, C.scope)
self.assertEqual(len(C.stinfo['relations']), 1)
def test_is_rel_no_scope_2(self):
rqlst = self.parse('Any X, ET WHERE C is ET, EXISTS(X work_for C)').children[0]
C = rqlst.defined_vars['C']
- self.failUnless(C.scope is rqlst, C.scope)
+ self.assertTrue(C.scope is rqlst, C.scope)
self.assertEqual(len(C.stinfo['relations']), 2)
@@ -276,46 +276,46 @@
rqlst = self.parse('Any X WHERE C is Company, NOT X work_for C').children[0]
self.assertEqual(rqlst.as_string(), 'Any X WHERE C is Company, NOT EXISTS(X work_for C)')
C = rqlst.defined_vars['C']
- self.failIf(C.scope is rqlst, C.scope)
+ self.assertFalse(C.scope is rqlst, C.scope)
def test_not_rel_normalization_2(self):
rqlst = self.parse('Any X, ET WHERE C is ET, NOT X work_for C').children[0]
self.assertEqual(rqlst.as_string(), 'Any X,ET WHERE C is ET, NOT EXISTS(X work_for C)')
C = rqlst.defined_vars['C']
- self.failUnless(C.scope is rqlst, C.scope)
+ self.assertTrue(C.scope is rqlst, C.scope)
def test_not_rel_normalization_3(self):
rqlst = self.parse('Any X WHERE C is Company, X work_for C, NOT C name "World Company"').children[0]
self.assertEqual(rqlst.as_string(), "Any X WHERE C is Company, X work_for C, NOT C name 'World Company'")
C = rqlst.defined_vars['C']
- self.failUnless(C.scope is rqlst, C.scope)
+ self.assertTrue(C.scope is rqlst, C.scope)
def test_not_rel_normalization_4(self):
rqlst = self.parse('Any X WHERE C is Company, NOT (X work_for C, C name "World Company")').children[0]
self.assertEqual(rqlst.as_string(), "Any X WHERE C is Company, NOT EXISTS(X work_for C, C name 'World Company')")
C = rqlst.defined_vars['C']
- self.failIf(C.scope is rqlst, C.scope)
+ self.assertFalse(C.scope is rqlst, C.scope)
def test_not_rel_normalization_5(self):
rqlst = self.parse('Any X WHERE X work_for C, EXISTS(C identity D, NOT Y work_for D, D name "World Company")').children[0]
self.assertEqual(rqlst.as_string(), "Any X WHERE X work_for C, EXISTS(C identity D, NOT EXISTS(Y work_for D), D name 'World Company')")
D = rqlst.defined_vars['D']
- self.failIf(D.scope is rqlst, D.scope)
- self.failUnless(D.scope.parent.scope is rqlst, D.scope.parent.scope)
+ self.assertFalse(D.scope is rqlst, D.scope)
+ self.assertTrue(D.scope.parent.scope is rqlst, D.scope.parent.scope)
def test_subquery_annotation_1(self):
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.assertFalse(C.scope is rqlst, C.scope)
self.assertEqual(len(C.stinfo['relations']), 1)
def test_subquery_annotation_2(self):
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.assertTrue(C.scope is rqlst.with_[0].query.children[0], C.scope)
self.assertEqual(len(C.stinfo['relations']), 2)
X = rqlst.get_variable('X')
- self.failUnless(X.scope is rqlst, X.scope)
+ self.assertTrue(X.scope is rqlst, X.scope)
def test_no_attr_var_if_uid_rel(self):
with self.assertRaises(BadRQLQuery) as cm:
--- a/utils.py Wed Nov 09 18:56:49 2011 +0100
+++ b/utils.py Thu Nov 08 14:46:01 2012 +0100
@@ -133,7 +133,7 @@
SQL_FUNCTIONS_REGISTRY.register_function(funcdef)
def function_description(funcname):
- """Return the description (`FunctionDescription`) for a RQL function."""
+ """Return the description (:class:`FunctionDescr`) for a RQL function."""
return RQL_FUNCTIONS_REGISTRY.get_function(funcname)
def quote(value):
@@ -156,14 +156,19 @@
res.append(u'"')
return u''.join(res)
-# Visitor #####################################################################
+
+
+class VisitableMixIn(object):
-_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()))
+ def accept(self, visitor, *args, **kwargs):
+ visit_id = self.__class__.__name__.lower()
+ visit_method = getattr(visitor, 'visit_%s' % visit_id)
+ return visit_method(self, *args, **kwargs)
+
+ def leave(self, visitor, *args, **kwargs):
+ visit_id = self.__class__.__name__.lower()
+ visit_method = getattr(visitor, 'leave_%s' % visit_id)
+ return visit_method(self, *args, **kwargs)
class RQLVisitorHandler(object):
"""Handler providing a dummy implementation of all callbacks necessary