Commit 450b448a authored by Vincent Pelletier's avatar Vincent Pelletier

Fix regression introduced in r33653.

As the generated order-by expressions now (rightly) differ based on data, the
exception generated when detecting the difference must be postponed to when
expression is actualy used, as it might not be used.
Also, test that postponing effectively works, and demonstrate another way to
express the same condition, but which allows sorting.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@33660 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent b8ac4a36
...@@ -55,6 +55,32 @@ def defaultDict(value): ...@@ -55,6 +55,32 @@ def defaultDict(value):
assert isinstance(value, dict) assert isinstance(value, dict)
return value.copy() return value.copy()
class MergeConflictError(ValueError):
pass
class MergeConflict(object):
"""
This class allows lazy errors.
SQLExpression detects merge conflicts when 2 different values exist for the
same key in 2 SQLExpression tree nodes.
This class allows to postpone raising an exception, to allow conflicting
values as long as they are not actualy used.
"""
# TODO (?): Include the traceback as of instanciation in error message,
# if it can help debugging.
def __init__(self, message):
self._message = message
def __call__(self):
raise MergeConflictError(self._message)
def conflictSafeGet(dikt, key, default=None):
result = dikt.get(key, default)
if isinstance(result, MergeConflict):
result() # Raises
return result
class SQLExpression(object): class SQLExpression(object):
implements(ISQLExpression) implements(ISQLExpression)
...@@ -180,12 +206,13 @@ class SQLExpression(object): ...@@ -180,12 +206,13 @@ class SQLExpression(object):
return result return result
@profiler_decorator @profiler_decorator
def getOrderByDict(self): def _getOrderByDict(self, delay_error=True):
result_dict = self.order_by_dict.copy() result_dict = self.order_by_dict.copy()
for sql_expression in self.sql_expression_list: for sql_expression in self.sql_expression_list:
order_by_dict = sql_expression.getOrderByDict() order_by_dict = sql_expression._getOrderByDict(delay_error=delay_error)
for key, value in order_by_dict.iteritems(): for key, value in order_by_dict.iteritems():
if key in result_dict and value != result_dict[key]: if key in result_dict and value != result_dict[key] \
and not isinstance(value, MergeConflict):
message = 'I don\'t know how to merge order_by_dict with ' \ message = 'I don\'t know how to merge order_by_dict with ' \
'conflicting entries for key %r: %r vs. %r' % (key, result_dict[key], value) 'conflicting entries for key %r: %r vs. %r' % (key, result_dict[key], value)
if DEBUG: if DEBUG:
...@@ -194,10 +221,16 @@ class SQLExpression(object): ...@@ -194,10 +221,16 @@ class SQLExpression(object):
sql_expression, sql_expression,
sql_expression.query, sql_expression.query,
', '.join('%r (%r)' % (x, x.query) for x in self.sql_expression_list)) ', '.join('%r (%r)' % (x, x.query) for x in self.sql_expression_list))
raise ValueError, message if delay_error:
order_by_dict[key] = MergeConflict(message)
else:
raise MergeConflictError, message
result_dict.update(order_by_dict) result_dict.update(order_by_dict)
return result_dict return result_dict
def getOrderByDict(self):
return self._getOrderByDict(delay_error=False)
@profiler_decorator @profiler_decorator
def getOrderByExpression(self): def getOrderByExpression(self):
""" """
...@@ -205,9 +238,8 @@ class SQLExpression(object): ...@@ -205,9 +238,8 @@ class SQLExpression(object):
Returns a rendered "order by" expression. See getOrderByList. Returns a rendered "order by" expression. See getOrderByList.
""" """
order_by_dict = self.getOrderByDict() order_by_dict = self._getOrderByDict()
get = order_by_dict.get return SQL_LIST_SEPARATOR.join(conflictSafeGet(order_by_dict, x, str(x)) \
return SQL_LIST_SEPARATOR.join(get(x, str(x)) \
for x in self.getOrderByList()) for x in self.getOrderByList())
@profiler_decorator @profiler_decorator
......
...@@ -35,6 +35,7 @@ from Products.ZSQLCatalog.SQLCatalog import SimpleQuery ...@@ -35,6 +35,7 @@ from Products.ZSQLCatalog.SQLCatalog import SimpleQuery
from Products.ZSQLCatalog.Query.EntireQuery import EntireQuery from Products.ZSQLCatalog.Query.EntireQuery import EntireQuery
from Products.ZSQLCatalog.Query.RelatedQuery import RelatedQuery from Products.ZSQLCatalog.Query.RelatedQuery import RelatedQuery
from DateTime import DateTime from DateTime import DateTime
from Products.ZSQLCatalog.SQLExpression import MergeConflictError
class ReferenceQuery: class ReferenceQuery:
""" """
...@@ -417,9 +418,21 @@ class TestSQLCatalog(unittest.TestCase): ...@@ -417,9 +418,21 @@ class TestSQLCatalog(unittest.TestCase):
{'keyword': '<"=a OR =b"'}) {'keyword': '<"=a OR =b"'})
self.catalog(ReferenceQuery(ReferenceQuery(operator='like', keyword='%"a" OR "b"%'), operator='and'), self.catalog(ReferenceQuery(ReferenceQuery(operator='like', keyword='%"a" OR "b"%'), operator='and'),
{'keyword': '"\\"a\\" OR \\"b\\""'}) {'keyword': '"\\"a\\" OR \\"b\\""'})
self.catalog(ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='match', fulltext='a'), # This example introduces impossible-to-merge search text criterion, which
ReferenceQuery(ReferenceQuery(operator='match', fulltext='b'), operator='not'), operator='and'), operator='and'), # is allowed as long as
{'fulltext': 'a NOT b'}) reference_query = ReferenceQuery(
ReferenceQuery(ReferenceQuery(operator='match', fulltext='a'),
ReferenceQuery(ReferenceQuery(operator='match', fulltext='b'),
operator='not'), operator='and'), operator='and')
self.catalog(reference_query, {'fulltext': 'a NOT b'})
# The same, with an order by, must raise
self.assertRaises(MergeConflictError, self.catalog, reference_query,
{'fulltext': 'a NOT b', 'order_by_list': [('fulltext', ), ]},
check_search_text=False)
# If one want to sort on, he must use the equivalent FullText syntax:
self.catalog(ReferenceQuery(ReferenceQuery(operator='match_boolean', fulltext='a -b'), operator='and'),
{'fulltext': 'a -b', 'order_by_list': [('fulltext', ), ]},
check_search_text=False)
self.catalog(ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='match', fulltext='a'), self.catalog(ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='match', fulltext='a'),
ReferenceQuery(ReferenceQuery(operator='match', fulltext='b'), operator='not'), operator='or'), operator='and'), ReferenceQuery(ReferenceQuery(operator='match', fulltext='b'), operator='not'), operator='or'), operator='and'),
{'fulltext': 'a OR NOT b'}) {'fulltext': 'a OR NOT b'})
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment