Removed requirement stipulating first expression cannot be NOT.

This commit is contained in:
David Sauve 2009-08-18 20:26:59 -04:00
parent 2c406c7ace
commit 3fd41552c3
3 changed files with 29 additions and 50 deletions

View File

@ -161,11 +161,11 @@ class XapianSearchBackendTestCase(TestCase):
self.assertEqual([result.pk for result in self.sb.search('*')['results']], [1, 2, 3])
# NOT operator
self.assertEqual([result.pk for result in self.sb.search('NOT name:david1')['results']], [2, 3])
self.assertEqual([result.pk for result in self.sb.search('index NOT name:david1')['results']], [2, 3])
self.assertEqual([result.pk for result in self.sb.search('index NOT name:david1 NOT name:david2')['results']], [3])
# This is invalid -- NOT can't be first, so should return no results
self.assertEqual(self.sb.search('NOT name:david3 index')['hits'], 0)
self.assertEqual([result.pk for result in self.sb.search('(NOT name:david1)')['results']], [2, 3])
self.assertEqual([result.pk for result in self.sb.search('(NOT name:david1) AND index')['results']], [2, 3])
self.assertEqual([result.pk for result in self.sb.search('index AND (NOT name:david1)')['results']], [2, 3])
self.assertEqual([result.pk for result in self.sb.search('index AND (NOT name:david1 NOT name:david2)')['results']], [3])
self.assertEqual([result.pk for result in self.sb.search('(NOT name:david1 NOT name:david2)')['results']], [3])
# Ranges
self.assertEqual([result.pk for result in self.sb.search('index name:david2..david3')['results']], [2, 3])

View File

@ -79,7 +79,7 @@ class XapianSearchQueryTestCase(TestCase):
def test_build_query_not_first_single(self):
self.sq.add_filter('content', 'hello', use_not=True)
self.sq.add_filter('content', 'world')
self.assertEqual(self.sq.build_query(), 'world NOT hello')
self.assertEqual(self.sq.build_query(), 'NOT hello AND world')
def test_build_query_not_first_multiple(self):
self.sq.add_filter('content', 'hello', use_not=True)
@ -89,7 +89,7 @@ class XapianSearchQueryTestCase(TestCase):
def test_build_query_not_and_in(self):
self.sq.add_filter('id__in', [1, 2, 3], use_not=True)
self.sq.add_filter('content', 'java')
self.assertEqual(self.sq.build_query(), 'java (NOT id:1 AND NOT id:2 AND NOT id:3)')
self.assertEqual(self.sq.build_query(), '(NOT id:1 NOT id:2 NOT id:3) AND java')
def test_build_query_phrase(self):
self.sq.add_filter('content', 'hello world')

View File

@ -377,7 +377,7 @@ class SearchBackend(BaseSearchBackend):
return 0
return database.get_doccount()
def more_like_this(self, model_instance, additional_query_string=None,
def more_like_this(self, model_instance, additional_query_string=None,
start_offset=0, end_offset=DEFAULT_MAX_RESULTS, **kwargs):
"""
Given a model instance, returns a result set of similar documents.
@ -428,7 +428,7 @@ class SearchBackend(BaseSearchBackend):
xapian.Query.OP_AND, query, additional_query
)
enquire.set_query(query)
results = []
matches = enquire.get_mset(start_offset, end_offset)
@ -449,14 +449,14 @@ class SearchBackend(BaseSearchBackend):
},
'spelling_suggestion': None,
}
def build_schema(self, fields):
"""
Build the schema from fields.
Required arguments:
``fields`` -- A list of fields in the index
Returns a list of fields in dictionary format ready for inclusion in
an indexed meta-data.
"""
@ -467,7 +467,7 @@ class SearchBackend(BaseSearchBackend):
for field_name, field_class in fields.items():
if field_class.document is True:
content_field_name = field_name
if field_class.indexed is True:
field_data = {
'field_name': field_name,
@ -475,7 +475,7 @@ class SearchBackend(BaseSearchBackend):
'multi_valued': 'false',
'column': column,
}
if isinstance(field_class, (DateField, DateTimeField)):
field_data['type'] = 'date'
elif isinstance(field_class, IntegerField):
@ -484,12 +484,12 @@ class SearchBackend(BaseSearchBackend):
field_data['type'] = 'boolean'
elif isinstance(field_class, MultiValueField):
field_data['multi_valued'] = 'true'
schema_fields.append(field_data)
column += 1
return (content_field_name, schema_fields)
def _do_highlight(self, content, text, tag='em'):
"""
Highlight `text` in `content` with html `tag`.
@ -523,9 +523,9 @@ class SearchBackend(BaseSearchBackend):
for result in results:
field_value = getattr(result, field)
facet_list[field_value] = facet_list.get(field_value, 0) + 1
facet_dict[field] = facet_list.items()
return facet_dict
def _do_date_facets(self, results, date_facets):
@ -764,12 +764,12 @@ class SearchBackend(BaseSearchBackend):
| xapian.QueryParser.FLAG_LOVEHATE
if '*' in query_string:
flags = flags | xapian.QueryParser.FLAG_WILDCARD
if query_string.upper().startswith('NOT'):
if 'NOT' in query_string.upper():
flags = flags | xapian.QueryParser.FLAG_PURE_NOT
if getattr(settings, 'HAYSTACK_INCLUDE_SPELLING', False) is True:
flags = flags | xapian.QueryParser.FLAG_SPELLING_CORRECTION
return flags
def _sorter(self, sort_by):
"""
Private method that takes a list of fields to sort by and returns a
@ -879,8 +879,6 @@ class SearchQuery(BaseSearchQuery):
else:
query_chunks = []
self._move_not_filters()
for the_filter in self.query_filters:
if the_filter.is_and():
query_chunks.append('AND')
@ -919,19 +917,20 @@ class SearchQuery(BaseSearchQuery):
query_chunks.append(filter_types[the_filter.filter_type] % (the_filter.field, value))
elif the_filter.is_not():
in_options = []
for possible_value in value:
in_options.append("NOT %s:%s" % (the_filter.field, possible_value))
query_chunks.append("(%s)" % " AND ".join(in_options))
query_chunks.append("AND")
query_chunks.append("(%s)" % " ".join(in_options))
else:
in_options = []
for possible_value in value:
in_options.append("%s:%s" % (the_filter.field, possible_value))
query_chunks.append("(%s)" % " OR ".join(in_options))
if query_chunks[0] in ('AND', 'OR'):
# Pull off an undesirable leading "AND" or "OR".
del(query_chunks[0])
@ -945,7 +944,7 @@ class SearchQuery(BaseSearchQuery):
else:
final_query = query
return final_query
def run(self):
@ -1001,28 +1000,8 @@ class SearchQuery(BaseSearchQuery):
if self.end_offset is not None:
kwargs['end_offset'] = self.end_offset - self.start_offset
results = self.backend.more_like_this(self._mlt_instance, additional_query_string, **kwargs)
self._results = results.get('results', [])
self._hit_count = results.get('hits', 0)
def _move_not_filters(self):
"""
Private method that will move any NOT expressions to the end of the
`query_filters` list if there are more than one. This is because we
don't want to use FLAG_PURE_NOT when mixing with other expressions and
NOT can't be first when there are multiple expressions.
Further explanation: http://xapian.org/docs/queryparser.html
"""
if self.query_filters[0].is_not() and len(self.query_filters) > 1:
not_filter_list = []
n = 0
for m in range(0, len(self.query_filters)):
if self.query_filters[n].is_not():
not_filter_list.append(self.query_filters[n])
del self.query_filters[n]
else:
n += 1
self.query_filters.extend(not_filter_list)