def test_boost_equivalent(self): boost = Boost(Term('guide'), 5) equivalent = boost.children[0] self.assertIsInstance(equivalent, Term) self.assertAlmostEqual(equivalent.boost, 5) boost = Boost(Term('guide', boost=0.5), 5) equivalent = boost.children[0] self.assertIsInstance(equivalent, Term) self.assertAlmostEqual(equivalent.boost, 2.5) boost = Boost(Boost(Term('guide', 0.1), 3), 5) sub_boost = boost.children[0] self.assertIsInstance(sub_boost, Boost) sub_boost = sub_boost.children[0] self.assertIsInstance(sub_boost, Term) self.assertAlmostEqual(sub_boost.boost, 1.5) boost = Boost(And([Boost(Term('guide', 0.1), 3), Term('two', 2)]), 5) and_obj = boost.children[0] self.assertIsInstance(and_obj, And) sub_boost = and_obj.children[0] self.assertIsInstance(sub_boost, Boost) guide = sub_boost.children[0] self.assertIsInstance(guide, Term) self.assertAlmostEqual(guide.boost, 1.5) two = and_obj.children[1] self.assertIsInstance(two, Term) self.assertAlmostEqual(two.boost, 10)
def test_with_simple_and_phrase(self): filters, query = parse_query_string('this is simple "hello world"') self.assertDictEqual(filters, {}) self.assertEqual( repr(query), repr(And([PlainText("this is simple"), Phrase("hello world")])))
def normalize(search_query: SearchQuery) -> Tuple[SearchQuery]: """ Turns this query into a normalized version. For example, And(Not(PlainText("Arepa")), PlainText("Crepe")) would be turned into AndNot(PlainText("Crepe"), PlainText("Arepa")): "Crepe AND NOT Arepa". This is done because we need to get the NOT operator to the front of the query, so it can be used in the search, because the SQLite FTS5 module doesn't support the unary NOT operator. This means that, in order to support the NOT operator, we need to match against the non-negated version of the query, and then return everything that is not in the results of the non-negated query. """ if isinstance(search_query, Phrase): return search_query # We can't normalize a Phrase. if isinstance(search_query, PlainText): return search_query # We can't normalize a PlainText. if isinstance(search_query, And): normalized_subqueries: List[SearchQuery] = [normalize(subquery) for subquery in search_query.subqueries] # This builds a list of normalized subqueries. not_negated_subqueries = [subquery for subquery in normalized_subqueries if not isinstance(subquery, Not)] # All the non-negated subqueries. not_negated_subqueries = [subquery for subquery in not_negated_subqueries if not isinstance(subquery, MatchAll)] # We can ignore all MatchAll SearchQueries here, because they are redundant. negated_subqueries = [subquery.subquery for subquery in normalized_subqueries if isinstance(subquery, Not)] if negated_subqueries == []: # If there are no negated subqueries, return an And(), now without the redundant MatchAll subqueries. return And(not_negated_subqueries) for subquery in negated_subqueries: # If there's a negated MatchAll subquery, then nothing will get matched. if isinstance(subquery, MatchAll): return Not(MatchAll()) return AndNot(And(not_negated_subqueries), Or(negated_subqueries)) if isinstance(search_query, Or): normalized_subqueries: List[SearchQuery] = [normalize(subquery) for subquery in search_query.subqueries] # This builds a list of (subquery, negated) tuples. negated_subqueries = [subquery.subquery for subquery in normalized_subqueries if isinstance(subquery, Not)] if negated_subqueries == []: # If there are no negated subqueries, return an Or(). return Or(normalized_subqueries) for subquery in negated_subqueries: # If there's a MatchAll subquery, then anything will get matched. if isinstance(subquery, MatchAll): return MatchAll() not_negated_subqueries = [subquery for subquery in normalized_subqueries if not isinstance(subquery, Not)] # All the non-negated subqueries. not_negated_subqueries = [subquery for subquery in not_negated_subqueries if not isinstance(subquery, MatchAll)] # We can ignore all MatchAll SearchQueries here, because they are redundant. return AndNot(MatchAll(), And(negated_subqueries)) if isinstance(search_query, Not): normalized = normalize(search_query.subquery) return Not(normalized) # Normalize the subquery, then invert it. if isinstance(search_query, MatchAll): return search_query # We can't normalize a MatchAll.
def test_and(self): results = self.backend.search(And([PlainText('javascript'), PlainText('definitive')]), models.Book.objects.all()) self.assertSetEqual({r.title for r in results}, {'JavaScript: The Definitive Guide'}) results = self.backend.search(PlainText('javascript') & PlainText('definitive'), models.Book.objects.all()) self.assertSetEqual({r.title for r in results}, {'JavaScript: The Definitive Guide'})
def test_multiple_phrases(self): filters, query = parse_query_string('"hello world" "hi earth"') self.assertEqual( repr(query), repr(And([Phrase("hello world"), Phrase("hi earth")])))