Commit 1c143ee4 authored by Jérome Perrin's avatar Jérome Perrin

Allow to force the key behaviour of by passing a key= argument to a Query constructor



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@17167 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 1c626480
......@@ -2017,6 +2017,20 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
result = sql_connection.manage_test(sql % sub_obj.getUid())
self.assertSameSet(['little_owner'], [x.owner for x in result])
def test_ExactMatchSearch(self):
# test exact match search with queries
doc = self._makeOrganisation(title='Foo%')
other_doc = self._makeOrganisation(title='FooBar')
ctool = self.getCatalogTool()
# by default, % in catalog search is a wildcard:
self.assertEquals(sorted([doc, other_doc]),
sorted([x.getObject() for x in
ctool(portal_type='Organisation', title='Foo%')]))
# ... but you can force searches with an exact match key
self.assertEquals([doc], [x.getObject() for x in
ctool(portal_type='Organisation', title=dict(query='Foo%',
key='ExactMatch'))])
def test_suite():
suite = unittest.TestSuite()
......
......@@ -181,6 +181,13 @@ class UidBuffer(TM):
tid = get_ident()
self.temporary_buffer.setdefault(tid, []).extend(iterable)
# valid search modes for queries
FULL_TEXT_SEARCH_MODE = 'FullText'
EXACT_MATCH_SEARCH_MODE = 'ExactMatch'
KEYWORD_SEARCH_MODE = 'Keyword'
class QueryMixin:
operator = None
......@@ -289,7 +296,7 @@ class Query(QueryMixin):
format - type date : %d/%m/%Y
type float : 1 234.12
"""
def __init__(self, format=None, operator=None, range=None,
def __init__(self, format=None, operator=None, range=None, key=None,
search_mode=None, table_alias_list=None, type=None, **kw):
self.format = format
if operator is None:
......@@ -304,6 +311,7 @@ class Query(QueryMixin):
self.key = key_list[0]
self.value = kw[self.key]
self.type = type
self.search_key = key
def __call__(self):
self.asSQLExpression()
......@@ -322,6 +330,8 @@ class Query(QueryMixin):
return result
def getSearchMode(self):
"""Search mode used for Full Text search
"""
return self.search_mode
def asSearchTextExpression(self):
......@@ -345,7 +355,8 @@ class Query(QueryMixin):
sql_expression = ''
value = self.getValue()
key = self.getKey()
ignore_key=0
search_key = self.search_key
ignore_key = 0
if key_alias_dict is not None:
# Try to find the alias
if key not in key_alias_dict:
......@@ -387,10 +398,10 @@ class Query(QueryMixin):
where_expression.append("%s > %s" % (key, query_max))
elif isSimpleType(value) or isinstance(value, DateTime) \
or (isinstance(value, (list, tuple)) and self.operator.upper() != 'IN'):
# Convert into lists any value which contain a ;
# Refer to _listGlobalActions DCWorkflow patch
# for example of use
if isinstance(value, basestring):
# Convert into lists any value which contain 'OR'
# Refer to _listGlobalActions DCWorkflow patch for example of use
if isinstance(value, basestring) \
and search_key != EXACT_MATCH_SEARCH_MODE:
value = value.split(' OR ')
value = map(lambda x:x.strip(), value)
value_list = value
......@@ -401,7 +412,7 @@ class Query(QueryMixin):
comparison_operator = None
if (value != '' or not ignore_empty_string) \
and isinstance(value, basestring):
if '%' in value:
if '%' in value and search_key != EXACT_MATCH_SEARCH_MODE:
comparison_operator = 'LIKE'
elif len(value) >= 1 and value[0:2] in ('<=','!=','>='):
comparison_operator = value[0:2]
......@@ -409,11 +420,15 @@ class Query(QueryMixin):
elif len(value) >= 1 and value[0] in ('=','>','<'):
comparison_operator = value[0]
value = value[1:]
elif key in keyword_search_keys:
elif search_key == KEYWORD_SEARCH_MODE or (
key in keyword_search_keys and
search_key != EXACT_MATCH_SEARCH_MODE):
# We must add % in the request to simulate the catalog
comparison_operator = 'LIKE'
value = '%%%s%%' % value
elif key in full_text_search_keys:
elif search_key == FULL_TEXT_SEARCH_MODE or (
key in full_text_search_keys
and search_key != EXACT_MATCH_SEARCH_MODE):
# We must add % in the request to simulate the catalog
# we first check if there is a special search_mode for this key
# incl. table name, or for all keys of that name,
......
......@@ -39,16 +39,13 @@ from Products.ZSQLCatalog.SQLCatalog import Query
from Products.ZSQLCatalog.SQLCatalog import ComplexQuery
from Products.ZSQLCatalog.SQLCatalog import NegatedQuery
try:
from transaction import get as get_transaction
except ImportError:
pass
class TestZSQLCatalog(unittest.TestCase):
"""Tests for ZSQL Catalog.
"""
def setUp(self):
self._catalog = ZSQLCatalog()
# TODO ?
class TestSQLCatalog(unittest.TestCase):
......@@ -100,6 +97,8 @@ class TestSQLCatalog(unittest.TestCase):
class TestQuery(unittest.TestCase):
"""Test SQL bits generated from Queries
"""
def testSimpleQuery(self):
q = Query(title='Foo')
self.assertEquals(
......@@ -107,6 +106,11 @@ class TestQuery(unittest.TestCase):
select_expression_list=[]),
q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
def testQueryMultipleKeys(self):
# using multiple keys is invalid and raises
# KeyError: 'Query must have only one key'
self.assertRaises(KeyError, Query, title='Foo', reference='bar')
def testNoneQuery(self):
q = Query(title=None)
self.assertEquals(
......@@ -168,6 +172,7 @@ class TestQuery(unittest.TestCase):
select_expression_list=[]),
q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
# format
def testDateFormat(self):
q = Query(date=DateTime(2001, 02, 03), format='%Y/%m/%d', type='date')
self.assertEquals(
......@@ -177,6 +182,7 @@ class TestQuery(unittest.TestCase):
select_expression_list=[]),
q.asSQLExpression(keyword_search_keys=[], full_text_search_keys=[]))
# full text
def testSimpleQueryFullText(self):
q = Query(title='Foo')
self.assertEquals(dict(where_expression="MATCH title AGAINST ('Foo' )",
......@@ -185,7 +191,28 @@ class TestQuery(unittest.TestCase):
q.asSQLExpression(keyword_search_keys=[],
full_text_search_keys=['title']))
def testSimpleQuerySearchKey(self):
def testSimpleQueryFullTextSearchMode(self):
q = Query(title='Foo',
search_mode='in_boolean_mode')
self.assertEquals(dict(
where_expression="MATCH title AGAINST ('Foo' IN BOOLEAN MODE)",
select_expression_list=
["MATCH title AGAINST ('Foo' IN BOOLEAN MODE) AS title_relevance"]),
q.asSQLExpression(keyword_search_keys=[],
full_text_search_keys=['title']))
def testSimpleQueryFullTextStat__(self):
# stat__ is an internal implementation artifact to prevent adding
# select_expression for countFolder
q = Query(title='Foo')
self.assertEquals(dict(
where_expression="MATCH title AGAINST ('Foo' )",
select_expression_list=[]),
q.asSQLExpression(keyword_search_keys=[],
full_text_search_keys=['title'],
stat__=1))
def testSimpleQueryKeywordSearchKey(self):
q = Query(title='Foo')
self.assertEquals(dict(where_expression="title LIKE '%Foo%'",
select_expression_list=[]),
......@@ -201,6 +228,7 @@ class TestQuery(unittest.TestCase):
q.asSQLExpression(keyword_search_keys=[],
full_text_search_keys=[]))
# complex queries
def testSimpleComplexQuery(self):
q1 = Query(title='Foo')
q2 = Query(reference='Bar')
......@@ -223,6 +251,33 @@ class TestQuery(unittest.TestCase):
q.asSQLExpression(keyword_search_keys=[],
full_text_search_keys=[]))
# forced keys
def testSimpleQueryForcedKeywordSearchKey(self):
q = Query(title='Foo', key='Keyword')
self.assertEquals("title LIKE '%Foo%'",
q.asSQLExpression(keyword_search_keys=[],
full_text_search_keys=[])['where_expression'])
def testSimpleQueryForcedFullText(self):
q = Query(title='Foo', key='FullText')
self.assertEquals("MATCH title AGAINST ('Foo' )",
q.asSQLExpression(keyword_search_keys=[],
full_text_search_keys=[])['where_expression'])
def testSimpleQueryForcedExactMatch(self):
q = Query(title='Foo', key='ExactMatch')
self.assertEquals("title = 'Foo'",
q.asSQLExpression(keyword_search_keys=['title'],
full_text_search_keys=[])['where_expression'])
def testSimpleQueryForcedExactMatchOR(self):
q = Query(title='Foo% OR %?ar', key='ExactMatch')
self.assertEquals("title = 'Foo% OR %?ar'",
q.asSQLExpression(keyword_search_keys=['title'],
full_text_search_keys=[])['where_expression'])
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestSQLCatalog))
......
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