Commit d7cc8581 authored by Kazuhiko Shiozaki's avatar Kazuhiko Shiozaki

full text: move special query generation logic from SearchKey to ComparisonOperator.

parent 6d4c6e39
...@@ -33,6 +33,7 @@ from Products.ZSQLCatalog.SQLExpression import SQLExpression ...@@ -33,6 +33,7 @@ from Products.ZSQLCatalog.SQLExpression import SQLExpression
from Products.ZSQLCatalog.interfaces.operator import IOperator from Products.ZSQLCatalog.interfaces.operator import IOperator
from zope.interface.verify import verifyClass from zope.interface.verify import verifyClass
from Products.ZSQLCatalog.SQLCatalog import list_type_list from Products.ZSQLCatalog.SQLCatalog import list_type_list
import re
class ComparisonOperatorBase(OperatorBase): class ComparisonOperatorBase(OperatorBase):
def asSQLExpression(self, column, value_list, only_group_columns): def asSQLExpression(self, column, value_list, only_group_columns):
...@@ -98,16 +99,19 @@ verifyClass(IOperator, MultivaluedComparisonOperator) ...@@ -98,16 +99,19 @@ verifyClass(IOperator, MultivaluedComparisonOperator)
class MatchComparisonOperator(MonovaluedComparisonOperator): class MatchComparisonOperator(MonovaluedComparisonOperator):
def __init__(self, operator, mode=''): def __init__(self, operator, mode=''):
MonovaluedComparisonOperator.__init__(self, operator, '') MonovaluedComparisonOperator.__init__(self, operator, '')
self.where_expression_format_string = 'MATCH (%%(column)s) AGAINST (%%(value_list)s%s)' % (mode, ) self.mode = mode
self.where_expression_format_string = 'MATCH (%(column)s) AGAINST (%(value_list)s%(mode)s)'
def asSQLExpression(self, column, value_list, only_group_columns): def asSQLExpression(self, column, value_list, only_group_columns):
""" """
This operator can emit a select expression, so it overrides This operator can emit a select expression, so it overrides
asSQLExpression inseatd of just defining a render method. asSQLExpression inseatd of just defining a render method.
""" """
value_list = self.renderValue(value_list)
match_string = self.where_expression_format_string % { match_string = self.where_expression_format_string % {
'column': column, 'column': column,
'value_list': self.renderValue(value_list), 'value_list': value_list,
'mode':self.mode,
} }
select_dict = {} select_dict = {}
if not only_group_columns: if not only_group_columns:
...@@ -127,6 +131,63 @@ class MatchComparisonOperator(MonovaluedComparisonOperator): ...@@ -127,6 +131,63 @@ class MatchComparisonOperator(MonovaluedComparisonOperator):
verifyClass(IOperator, MatchComparisonOperator) verifyClass(IOperator, MatchComparisonOperator)
class MroongaComparisonOperator(MatchComparisonOperator):
fulltext_boolean_splitter = re.compile(r'(\s|\(.+?\)|".+?")')
fulltext_boolean_detector = re.compile(r'(^[+-]|^.+\*$|^["(].+[")]$)')
def renderValue(self, value_list):
"""
Special Query renderer for MroongaFullText queries:
* by default 'AND' search by using '*D+' pragma.
* similarity search for non-boolean queries by using '*S"..."' operator.
"""
if isinstance(value_list, list_type_list):
try:
value_list, = value_list
except ValueError:
raise ValueError, '%r: value_list must not contain more than one item. Got %r' % (self, value_list)
if self.mode == ' IN BOOLEAN MODE':
fulltext_query = '*D+ %s' % value_list
return self._renderValue(fulltext_query)
else:
match_query_list = []
match_boolean_query_list = []
for token in self.fulltext_boolean_splitter.split(value_list):
token = token.strip()
if not token:
continue
elif self.fulltext_boolean_detector.match(token):
match_boolean_query_list.append(token)
else:
match_query_list.append(token)
fulltext_query = '*D+'
if match_query_list:
fulltext_query += ' *S"%s"' % ' '.join(x.replace('"', '\\"') for x in match_query_list)
if match_boolean_query_list:
fulltext_query += ' %s' % ' '.join(match_boolean_query_list)
self.mode = ' IN BOOLEAN MODE'
return self._renderValue(fulltext_query)
verifyClass(IOperator, MroongaComparisonOperator)
class MroongaBooleanComparisonOperator(MroongaComparisonOperator):
def renderValue(self, value_list):
"""
value_list must either be a non-list or a single-value list.
"""
if isinstance(value_list, list_type_list):
try:
value_list, = value_list
except ValueError:
raise ValueError, '%r: value_list must not contain more than one item. Got %r' % (self, value_list)
fulltext_query = '*D+ %s' % value_list
print self._renderValue(fulltext_query)
return self._renderValue(fulltext_query)
verifyClass(IOperator, MatchComparisonOperator)
class SphinxSEComparisonOperator(MonovaluedComparisonOperator): class SphinxSEComparisonOperator(MonovaluedComparisonOperator):
def __init__(self, operator, mode=''): def __init__(self, operator, mode=''):
MonovaluedComparisonOperator.__init__(self, operator, '') MonovaluedComparisonOperator.__init__(self, operator, '')
...@@ -173,9 +234,10 @@ operator_dict = { ...@@ -173,9 +234,10 @@ operator_dict = {
'match': MatchComparisonOperator('match'), 'match': MatchComparisonOperator('match'),
'match_boolean': MatchComparisonOperator('match_boolean', mode=' IN BOOLEAN MODE'), 'match_boolean': MatchComparisonOperator('match_boolean', mode=' IN BOOLEAN MODE'),
'match_expansion': MatchComparisonOperator('match_expansion', mode=' WITH QUERY EXPANSION'), 'match_expansion': MatchComparisonOperator('match_expansion', mode=' WITH QUERY EXPANSION'),
'mroonga': MroongaComparisonOperator('mroonga'),
'mroonga_boolean': MroongaComparisonOperator('mroonga_boolean', mode=' IN BOOLEAN MODE'),
'sphinxse': SphinxSEComparisonOperator('sphinxse'), 'sphinxse': SphinxSEComparisonOperator('sphinxse'),
'in': MultivaluedComparisonOperator('in'), 'in': MultivaluedComparisonOperator('in'),
'is': MonovaluedComparisonOperator('is'), 'is': MonovaluedComparisonOperator('is'),
'is not': MonovaluedComparisonOperator('is not', '!='), 'is not': MonovaluedComparisonOperator('is not', '!='),
} }
...@@ -35,6 +35,6 @@ class MroongaBooleanFullTextKey(MroongaFullTextKey): ...@@ -35,6 +35,6 @@ class MroongaBooleanFullTextKey(MroongaFullTextKey):
This SearchKey generates SQL fulltext comparisons for Mroonga whose This SearchKey generates SQL fulltext comparisons for Mroonga whose
default comparison operator is match_boolean. default comparison operator is match_boolean.
""" """
default_comparison_operator = 'match_boolean' default_comparison_operator = 'mroonga_boolean'
verifyClass(ISearchKey, MroongaBooleanFullTextKey) verifyClass(ISearchKey, MroongaBooleanFullTextKey)
...@@ -27,80 +27,10 @@ ...@@ -27,80 +27,10 @@
############################################################################## ##############################################################################
from DefaultKey import DefaultKey from DefaultKey import DefaultKey
from FullTextKey import FullTextKey
from Products.ZSQLCatalog.Query.SimpleQuery import SimpleQuery
from Products.ZSQLCatalog.interfaces.search_key import ISearchKey from Products.ZSQLCatalog.interfaces.search_key import ISearchKey
from SearchKey import SearchKey
from zope.interface.verify import verifyClass from zope.interface.verify import verifyClass
import re
class MroongaFullTextKey(FullTextKey): class MroongaFullTextKey(DefaultKey):
""" default_comparison_operator = 'mroonga'
This SearchKey generates SQL fulltext comparisons for Mroonga.
"""
default_comparison_operator = 'match'
fulltext_boolean_splitter = re.compile(r'(\s|\(.+?\)|".+?")')
fulltext_boolean_detector = re.compile(r'(^[+-]|^.+\*$|^["(].+[")]$)')
def _processSearchValue(self, search_value, logical_operator,
comparison_operator):
"""
Special SearchValue processor for MroongaFullText queries:
if a searched token from 'match' operator group contains an
operator recognised in boolean mode, make the operator for
that value be 'match_boolean'.
"""
operator_value_dict, logical_operator, parsed = \
SearchKey._processSearchValue(self, search_value, logical_operator,
comparison_operator)
new_value_list = []
append = new_value_list.append
for value in operator_value_dict.pop('match', []):
if isinstance(value, basestring):
# special case for empty string.
if value == '':
operator_value_dict.setdefault('=', []).append(value)
continue
for token in self.fulltext_boolean_splitter.split(value):
token = token.strip()
if not token:
continue
elif self.fulltext_boolean_detector.match(token):
operator_value_dict.setdefault('match_boolean', []).append(token)
else:
append(token)
else:
append(value)
operator_value_dict['match'] = new_value_list
return operator_value_dict, logical_operator, parsed
def _buildQuery(self, operator_value_dict, logical_operator, parsed, group):
"""
Special Query builder for MroongaFullText queries:
* by default 'AND' search by using '*D+' pragma.
* similarity search for non-boolean queries by using '*S"..."' operator.
"""
column = self.getColumn()
query_list = []
append = query_list.append
match_query = operator_value_dict.pop('match', [])
match_boolean_query = operator_value_dict.pop('match_boolean', [])
fulltext_query = '*D+'
if match_query:
fulltext_query += ' *S"%s"' % ' '.join(x.replace('"', '\\"') for x in match_query)
if match_boolean_query:
if len(match_boolean_query) > 1 and logical_operator == 'or':
fulltext_query += ' %s' % ' OR '.join('(%s)' % x for x in match_boolean_query)
else:
fulltext_query += ' %s' % ' '.join(match_boolean_query)
if match_query or match_boolean_query:
append(SimpleQuery(search_key=self,
comparison_operator='match_boolean',
group=group, **{column: fulltext_query}))
# other comparison operators are handled by DefaultKey.
if operator_value_dict:
query_list += DefaultKey._buildQuery(
self, operator_value_dict, logical_operator, parsed, group)
return query_list
verifyClass(ISearchKey, MroongaFullTextKey) verifyClass(ISearchKey, MroongaFullTextKey)
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