[schema] edit syntax tree instead of playing with strings for RQLExpressions
Lets us support more complex expressions involving e.g. "HAVING" clauses.
Closes #3202855
--- a/schema.py Thu Oct 10 12:40:23 2013 +0200
+++ b/schema.py Wed Oct 09 16:30:27 2013 +0200
@@ -25,7 +25,7 @@
from logging import getLogger
from warnings import warn
-from logilab.common.decorators import cached, clear_cache, monkeypatch
+from logilab.common.decorators import cached, clear_cache, monkeypatch, cachedproperty
from logilab.common.logging_ext import set_log_methods
from logilab.common.deprecation import deprecated, class_moved, moved
from logilab.common.textutils import splitstrip
@@ -668,7 +668,7 @@
# only defining here to prevent pylint from complaining
info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
# to be defined in concrete classes
- full_rql = None
+ rqlst = None
predefined_variables = None
def __init__(self, expression, mainvars, eid):
@@ -687,7 +687,7 @@
self.mainvars = mainvars
self.expression = normalize_expression(expression)
try:
- self.rqlst = parse(self.full_rql, print_errors=False).children[0]
+ self.full_rql = self.rqlst.as_string()
except RQLSyntaxError:
raise RQLSyntaxError(expression)
for mainvar in mainvars:
@@ -730,6 +730,15 @@
def __setstate__(self, state):
self.__init__(*state)
+ @cachedproperty
+ def rqlst(self):
+ select = parse(self.minimal_rql, print_errors=False).children[0]
+ defined = set(split_expression(self.expression))
+ for varname in self.predefined_variables:
+ if varname in defined:
+ select.add_eid_restriction(select.get_variable(varname), varname.lower(), 'Substitute')
+ return select
+
# permission rql expression specific stuff #################################
@cached
@@ -848,25 +857,11 @@
# rql expressions for use in permission definition #############################
class ERQLExpression(RQLExpression):
- predefined_variables = 'UX'
+ predefined_variables = 'XU'
def __init__(self, expression, mainvars=None, eid=None):
RQLExpression.__init__(self, expression, mainvars or 'X', eid)
- @property
- def full_rql(self):
- rql = self.minimal_rql
- rqlst = getattr(self, 'rqlst', None) # may be not set yet
- if rqlst is not None:
- defined = rqlst.defined_vars
- else:
- defined = set(split_expression(self.expression))
- if 'X' in defined:
- rql += ', X eid %(x)s'
- if 'U' in defined:
- rql += ', U eid %(u)s'
- return rql
-
def check(self, _cw, eid=None, creating=False, **kwargs):
if 'X' in self.rqlst.defined_vars:
if eid is None:
@@ -905,29 +900,13 @@
class RRQLExpression(RQLExpression):
- predefined_variables = 'USO'
+ predefined_variables = 'SOU'
def __init__(self, expression, mainvars=None, eid=None):
if mainvars is None:
mainvars = guess_rrqlexpr_mainvars(expression)
RQLExpression.__init__(self, expression, mainvars, eid)
- @property
- def full_rql(self):
- rql = self.minimal_rql
- rqlst = getattr(self, 'rqlst', None) # may be not set yet
- if rqlst is not None:
- defined = rqlst.defined_vars
- else:
- defined = set(split_expression(self.expression))
- if 'S' in defined:
- rql += ', S eid %(s)s'
- if 'O' in defined:
- rql += ', O eid %(o)s'
- if 'U' in defined:
- rql += ', U eid %(u)s'
- return rql
-
def check(self, _cw, fromeid=None, toeid=None):
kwargs = {}
if 'S' in self.rqlst.defined_vars:
--- a/test/unittest_schema.py Thu Oct 10 12:40:23 2013 +0200
+++ b/test/unittest_schema.py Wed Oct 09 16:30:27 2013 +0200
@@ -132,6 +132,8 @@
self.assertRaises(RQLSyntaxError, ERQLExpression, '1')
expr = ERQLExpression('X travaille S, S owned_by U')
self.assertEqual(str(expr), 'Any X WHERE X travaille S, S owned_by U, X eid %(x)s, U eid %(u)s')
+ expr = ERQLExpression('X foo S, S bar U, X baz XE, S quux SE HAVING XE > SE')
+ self.assertEqual(str(expr), 'Any X WHERE X foo S, S bar U, X baz XE, S quux SE, X eid %(x)s, U eid %(u)s HAVING XE > SE')
def test_rrqlexpression(self):
self.assertRaises(Exception, RRQLExpression, '1')