From d4c8c422759fd3f8c42ea5def769963f52184ac0 Mon Sep 17 00:00:00 2001 From: David Sauve Date: Thu, 30 Jul 2009 08:54:46 -0400 Subject: [PATCH] Fixed broken NOT query -- Thanks Supreet --- AUTHORS | 5 +- tests/xapian_backend.py | 112 +++++++++++++++++++++------------------- xapian_backend.py | 3 +- 3 files changed, 64 insertions(+), 56 deletions(-) diff --git a/AUTHORS b/AUTHORS index a197727..9054473 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,9 +3,10 @@ Primary Authors: * David Sauve -Thanks to +Thanks to: * Daniel Lindsley for the awesome Haystack API and putting up with all of my questions. * Trapeze Media for providing the time and resources to complete this project as well as Q&A. - * Supreet Sethi for suggestions regarding morphologic date comparisons. \ No newline at end of file + * Supreet Sethi for suggestions regarding morphologic date comparisons and + for fixing NOT query expressions. \ No newline at end of file diff --git a/tests/xapian_backend.py b/tests/xapian_backend.py index f874128..2d81907 100644 --- a/tests/xapian_backend.py +++ b/tests/xapian_backend.py @@ -29,18 +29,18 @@ class XapianSearchSite(sites.SearchSite): class XapianSearchBackendTestCase(TestCase): def setUp(self): super(XapianSearchBackendTestCase, self).setUp() - + temp_path = os.path.join('tmp', 'test_xapian_query') self.old_xapian_path = getattr(settings, 'HAYSTACK_XAPIAN_PATH', temp_path) settings.HAYSTACK_XAPIAN_PATH = temp_path - + self.site = XapianSearchSite() self.sb = SearchBackend(site=self.site) self.msi = XapianMockSearchIndex(MockModel, backend=self.sb) self.site.register(MockModel, XapianMockSearchIndex) - + self.sample_objs = [] - + for i in xrange(1, 4): mock = MockModel() mock.id = i @@ -49,19 +49,19 @@ class XapianSearchBackendTestCase(TestCase): mock.value = i * 5 mock.flag = bool(i % 2) self.sample_objs.append(mock) - + def tearDown(self): if os.path.exists(settings.HAYSTACK_XAPIAN_PATH): index_files = os.listdir(settings.HAYSTACK_XAPIAN_PATH) - + for index_file in index_files: os.remove(os.path.join(settings.HAYSTACK_XAPIAN_PATH, index_file)) - + os.removedirs(settings.HAYSTACK_XAPIAN_PATH) - + settings.HAYSTACK_XAPIAN_PATH = self.old_xapian_path super(XapianSearchBackendTestCase, self).tearDown() - + def xapian_search(self, query_string): database = xapian.Database(settings.HAYSTACK_XAPIAN_PATH) if query_string: @@ -73,9 +73,9 @@ class XapianSearchBackendTestCase(TestCase): enquire = xapian.Enquire(database) enquire.set_query(query) matches = enquire.get_mset(0, DEFAULT_MAX_RESULTS) - + document_list = [] - + for match in matches: document = match.get_document() object_data = pickle.loads(document.get_data()) @@ -83,155 +83,161 @@ class XapianSearchBackendTestCase(TestCase): object_data[key] = self.sb._from_python(value) object_data['id'] = force_unicode(document.get_value(0)) document_list.append(object_data) - + return document_list - + def test_update(self): self.sb.update(self.msi, self.sample_objs) self.sb.update(self.msi, self.sample_objs) # Duplicates should be updated, not appended -- http://github.com/notanumber/xapian-haystack/issues/#issue/6 - + self.assertEqual(len(self.xapian_search('')), 3) self.assertEqual([dict(doc) for doc in self.xapian_search('')], [{'flag': u't', 'name': u'david1', 'text': u'Indexed!\n1', 'pub_date': u'20090224000000', 'value': u'5', 'id': u'tests.mockmodel.1'}, {'flag': u'f', 'name': u'david2', 'text': u'Indexed!\n2', 'pub_date': u'20090223000000', 'value': u'10', 'id': u'tests.mockmodel.2'}, {'flag': u't', 'name': u'david3', 'text': u'Indexed!\n3', 'pub_date': u'20090222000000', 'value': u'15', 'id': u'tests.mockmodel.3'}]) - + def test_remove(self): self.sb.update(self.msi, self.sample_objs) self.assertEqual(len(self.xapian_search('')), 3) - + self.sb.remove(self.sample_objs[0]) self.assertEqual(len(self.xapian_search('')), 2) self.assertEqual([dict(doc) for doc in self.xapian_search('')], [{'flag': u'f', 'name': u'david2', 'text': u'Indexed!\n2', 'pub_date': u'20090223000000', 'value': u'10', 'id': u'tests.mockmodel.2'}, {'flag': u't', 'name': u'david3', 'text': u'Indexed!\n3', 'pub_date': u'20090222000000', 'value': u'15', 'id': u'tests.mockmodel.3'}]) - + def test_clear(self): self.sb.update(self.msi, self.sample_objs) self.assertEqual(len(self.xapian_search('')), 3) - + self.sb.clear() self.assertEqual(len(self.xapian_search('')), 0) - + self.sb.update(self.msi, self.sample_objs) self.assertEqual(len(self.xapian_search('')), 3) - + self.sb.clear([AnotherMockModel]) self.assertEqual(len(self.xapian_search('')), 3) - + self.sb.clear([MockModel]) self.assertEqual(len(self.xapian_search('')), 0) - + self.sb.update(self.msi, self.sample_objs) self.assertEqual(len(self.xapian_search('')), 3) - + self.sb.clear([AnotherMockModel, MockModel]) self.assertEqual(len(self.xapian_search('')), 0) def test_search(self): self.sb.update(self.msi, self.sample_objs) self.assertEqual(len(self.xapian_search('')), 3) - + + # Empty query self.assertEqual(self.sb.search(''), {'hits': 0, 'results': []}) + + # Wildcard -- All self.assertEqual(self.sb.search('*')['hits'], 3) self.assertEqual([result.pk for result in self.sb.search('*')['results']], [u'1', u'2', u'3']) - + + # NOT operator + self.assertEqual([result.pk for result in self.sb.search('NOT author:david1')['results']], [u'1', u'2', u'3']) + def test_field_facets(self): self.sb.update(self.msi, self.sample_objs) self.assertEqual(len(self.xapian_search('')), 3) - + self.assertEqual(self.sb.search('', facets=['name']), {'hits': 0, 'results': []}) results = self.sb.search('index', facets=['name']) self.assertEqual(results['hits'], 3) self.assertEqual(results['facets']['fields']['name'], [('david1', 1), ('david2', 1), ('david3', 1)]) - + # self.assertEqual(self.sb.search('', date_facets={'pub_date': {'start_date': datetime.date(2008, 2, 26), 'end_date': datetime.date(2008, 2, 26), 'gap': '/MONTH'}}), []) # results = self.sb.search('Index*', date_facets={'pub_date': {'start_date': datetime.date(2008, 2, 26), 'end_date': datetime.date(2008, 2, 26), 'gap': '/MONTH'}}) # self.assertEqual(results['hits'], 3) # self.assertEqual(results['facets'], {}) - # + # # self.assertEqual(self.sb.search('', query_facets={'name': '[* TO e]'}), []) # results = self.sb.search('Index*', query_facets={'name': '[* TO e]'}) # self.assertEqual(results['hits'], 3) # self.assertEqual(results['facets'], {}) - + def test_narrow_queries(self): self.sb.update(self.msi, self.sample_objs) self.assertEqual(len(self.xapian_search('')), 3) - + self.assertEqual(self.sb.search('', narrow_queries=['name:david1']), {'hits': 0, 'results': []}) results = self.sb.search('index', narrow_queries=['name:david1']) self.assertEqual(results['hits'], 1) - + def test_highlight(self): self.sb.update(self.msi, self.sample_objs) self.assertEqual(len(self.xapian_search('')), 3) - + self.assertEqual(self.sb.search('', highlight=True), {'hits': 0, 'results': []}) self.assertEqual(self.sb.search('Index', highlight=True)['hits'], 3) self.assertEqual([result.highlighted['text'] for result in self.sb.search('Index', highlight=True)['results']], ['Indexed!\n1', 'Indexed!\n2', 'Indexed!\n3']) - + def test_spelling_suggestion(self): self.sb.update(self.msi, self.sample_objs) self.assertEqual(len(self.xapian_search('')), 3) - + self.assertEqual(self.sb.search('indxe')['hits'], 0) self.assertEqual(self.sb.search('indxe')['spelling_suggestion'], 'indexed') - + self.assertEqual(self.sb.search('indxed')['hits'], 0) self.assertEqual(self.sb.search('indxed')['spelling_suggestion'], 'indexed') - + def test_stemming(self): self.sb.update(self.msi, self.sample_objs) self.assertEqual(len(self.xapian_search('')), 3) - + results = self.sb.search('index') self.assertEqual(results['hits'], 3) - + results = self.sb.search('indexing') self.assertEqual(results['hits'], 3) - + def test_more_like_this(self): self.sb.update(self.msi, self.sample_objs) self.assertEqual(len(self.xapian_search('')), 3) - + results = self.sb.more_like_this(self.sample_objs[0]) self.assertEqual(results['hits'], 2) self.assertEqual([result.pk for result in results['results']], [u'3', u'2']) - + def test_document_count(self): self.sb.update(self.msi, self.sample_objs) self.assertEqual(self.sb.document_count(), 3) - + def test_delete_index(self): self.sb.update(self.msi, self.sample_objs) self.assert_(self.sb.document_count() > 0) - + self.sb.delete_index() self.assertEqual(self.sb.document_count(), 0) - + def test_order_by(self): self.sb.update(self.msi, self.sample_objs) - + results = self.sb.search('*', sort_by=['pub_date']) self.assertEqual([result.pk for result in results['results']], [u'1', u'2', u'3']) - + results = self.sb.search('*', sort_by=['-pub_date']) self.assertEqual([result.pk for result in results['results']], [u'3', u'2', u'1']) - + results = self.sb.search('*', sort_by=['id']) self.assertEqual([result.pk for result in results['results']], [u'3', u'2', u'1']) - + results = self.sb.search('*', sort_by=['-id']) self.assertEqual([result.pk for result in results['results']], [u'1', u'2', u'3']) - + results = self.sb.search('*', sort_by=['value']) self.assertEqual([result.pk for result in results['results']], [u'3', u'2', u'1']) - + results = self.sb.search('*', sort_by=['-value']) self.assertEqual([result.pk for result in results['results']], [u'1', u'2', u'3']) - + results = self.sb.search('*', sort_by=['flag', 'id']) self.assertEqual([result.pk for result in results['results']], [u'3', u'1', u'2']) - + results = self.sb.search('*', sort_by=['flag', '-id']) self.assertEqual([result.pk for result in results['results']], [u'1', u'3', u'2']) - + def test__from_python(self): self.assertEqual(self.sb._from_python('abc'), u'abc') self.assertEqual(self.sb._from_python(1), u'1') diff --git a/xapian_backend.py b/xapian_backend.py index 4bf057a..be61d5c 100644 --- a/xapian_backend.py +++ b/xapian_backend.py @@ -567,7 +567,8 @@ class SearchBackend(BaseSearchBackend): | xapian.QueryParser.FLAG_PHRASE \ | xapian.QueryParser.FLAG_BOOLEAN \ | xapian.QueryParser.FLAG_LOVEHATE \ - | xapian.QueryParser.FLAG_WILDCARD + | xapian.QueryParser.FLAG_WILDCARD \ + | xapian.QueryParser.FLAG_PURE_NOT if getattr(settings, 'HAYSTACK_INCLUDE_SPELLING', False) is True: flags = flags | xapian.QueryParser.FLAG_SPELLING_CORRECTION return flags