fix RQLUniqueConstraint behaviour by using a DISTINCT query and allowing
to specify variables that should be used in selection. See usage on
state_of / transition_of relations.
--- a/entity.py Wed Dec 02 11:04:40 2009 +0100
+++ b/entity.py Wed Dec 02 11:53:25 2009 +0100
@@ -769,6 +769,7 @@
insertsecurity = (rtype.has_local_role('add') and not
rtype.has_perm(self.req, 'add', **securitycheck_args))
constraints = rtype.rproperty(subjtype, objtype, 'constraints')
+ # XXX consider constraint.mainvars to check if constraint apply
if vocabconstraints:
# RQLConstraint is a subclass for RQLVocabularyConstraint, so they
# will be included as well
--- a/schema.py Wed Dec 02 11:04:40 2009 +0100
+++ b/schema.py Wed Dec 02 11:53:25 2009 +0100
@@ -552,16 +552,29 @@
restriction is additional rql restriction that will be added to
a predefined query, where the S and O variables respectivly represent
the subject and the object of the relation
+
+ mainvars is a string that should be used as selection variable (eg
+ `'Any %s WHERE ...' % mainvars`). If not specified, an attempt will be
+ done to guess it according to variable used in the expression.
"""
- def __init__(self, restriction):
+ def __init__(self, restriction, mainvars=None):
self.restriction = restriction
+ if mainvars is None:
+ mainvars = guess_rrqlexpr_mainvars(restriction)
+ self.mainvars = mainvars
+ assert not ';' in mainvars # XXX check mainvars as for RQLExpression?
def serialize(self):
- return self.restriction
+ # start with a comma for bw compat, see below
+ return ';' + self.mainvars + ';' + self.restriction
def deserialize(cls, value):
- return cls(value)
+ # XXX < 3.5.10 bw compat
+ if not value.startswith(';'):
+ return cls(value)
+ _, mainvars, restriction = value.split(';', 2)
+ return cls(restriction, mainvars)
deserialize = classmethod(deserialize)
def check(self, entity, rtype, value):
@@ -585,14 +598,21 @@
"""the rql constraint is similar to the RQLVocabularyConstraint but
are also enforced at the repository level
"""
+ distinct_query = False
+
def exec_query(self, session, eidfrom, eidto):
if eidto is None:
- rql = 'Any S WHERE S eid %(s)s, ' + self.restriction
- return session.unsafe_execute(rql, {'s': eidfrom}, 's',
- build_descr=False)
- rql = 'Any S,O WHERE S eid %(s)s, O eid %(o)s, ' + self.restriction
- return session.unsafe_execute(rql, {'s': eidfrom, 'o': eidto},
- ('s', 'o'), build_descr=False)
+ # checking constraint for an attribute relation
+ restriction = 'S eid %(s)s, ' + self.restriction
+ args, ck = {'s': eidfrom}, 's'
+ else:
+ restriction =rql = 'S eid %(s)s, O eid %(o)s, ' + self.restriction
+ args, ck = {'s': eidfrom, 'o': eidto}, ('s', 'o')
+ rql = 'Any %s WHERE %s' % (self.mainvars, restriction)
+ if self.distinct_query:
+ rql = 'DISTINCT ' + rql
+ return session.unsafe_execute(rql, args, ck, build_descr=False)
+
def error(self, eid, rtype, msg):
raise ValidationError(eid, {rtype: msg})
@@ -609,6 +629,8 @@
"""the unique rql constraint check that the result of the query isn't
greater than one
"""
+ distinct_query = True
+
def repo_check(self, session, eidfrom, rtype, eidto=None):
"""raise ValidationError if the relation doesn't satisfy the constraint
"""
@@ -797,20 +819,23 @@
PyFileReader.context['ERQLExpression'] = yobsolete(ERQLExpression)
+def guess_rrqlexpr_mainvars(expression):
+ defined = set(split_expression(expression))
+ mainvars = []
+ if 'S' in defined:
+ mainvars.append('S')
+ if 'O' in defined:
+ mainvars.append('O')
+ if 'U' in defined:
+ mainvars.append('U')
+ if not mainvars:
+ raise Exception('unable to guess selection variables')
+ return ','.join(mainvars)
+
class RRQLExpression(RQLExpression):
def __init__(self, expression, mainvars=None, eid=None):
if mainvars is None:
- defined = set(split_expression(expression))
- mainvars = []
- if 'S' in defined:
- mainvars.append('S')
- if 'O' in defined:
- mainvars.append('O')
- if 'U' in defined:
- mainvars.append('U')
- if not mainvars:
- raise Exception('unable to guess selection variables')
- mainvars = ','.join(mainvars)
+ mainvars = guess_rrqlexpr_mainvars(expression)
RQLExpression.__init__(self, expression, mainvars, eid)
# graph of links between variable, used by rql rewriter
self.vargraph = {}
--- a/schemas/workflow.py Wed Dec 02 11:04:40 2009 +0100
+++ b/schemas/workflow.py Wed Dec 02 11:53:25 2009 +0100
@@ -59,7 +59,7 @@
description=_('allowed transitions from this state'))
state_of = SubjectRelation('Workflow', cardinality='1*', composite='object',
description=_('workflow to which this state belongs'),
- constraints=[RQLUniqueConstraint('S name N, Y state_of O, Y name N')])
+ constraints=[RQLUniqueConstraint('S name N, Y state_of O, Y name N', 'Y')])
class BaseTransition(EntityType):
@@ -83,7 +83,7 @@
'allowed to pass this transition'))
transition_of = SubjectRelation('Workflow', cardinality='1*', composite='object',
description=_('workflow to which this transition belongs'),
- constraints=[RQLUniqueConstraint('S name N, Y transition_of O, Y name N')])
+ constraints=[RQLUniqueConstraint('S name N, Y transition_of O, Y name N', 'Y')])
class Transition(BaseTransition):