Skip to content

Conversation

@cjrh
Copy link
Collaborator

@cjrh cjrh commented Sep 6, 2025

This adds .and_must_match(), and_must_not_match(), and or_should_match() methods which produce boolean queries that can be chained in a fluent interface. Some care is taken to keep the chain flatter by reusing a BooleanQuery layer (and simply append the subsequent clauses).

Here's a silly example from the tests:

        index = ram_index
        searcher = index.searcher()

        # Queries for testing
        query_mice = Query.term_query(index.schema, "title", "mice")  # Matches "Of Mice and Men"
        query_old = Query.term_query(index.schema, "title", "old")  # Matches "The Old Man and the Sea"
        query_man = Query.term_query(index.schema, "title", "man")  # Matches "The Old Man and the Sea"

        # "The Old Man and the Sea" contains both "old" and "man"
        # (but with many chains)
        combined_must = (
            query_old
            .and_must_match(query_man)
            .and_must_not_match(query_mice)
        )
        result = searcher.search(combined_must, 10)
        assert len(result.hits) == 1

@cjrh cjrh requested review from Sidhant29 and wallies September 6, 2025 03:38
Comment on lines +73 to +74
let inner: Box<dyn tv::query::Query> = if let Some(boolean_query) =
self.inner.downcast_ref::<BooleanQuery>()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently this only checks whether self is a boolean query and will then reuse that to append the new clause; however, it is possible that other is also a boolean query. So a further optimization can be made to either append to other (if a BQ), or to fully merge self and other if they're both BQ.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(some care must be taken when doing the appends, in case other_occur is a MUST NOT)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OTOH we may want to intentionally NOT incorporate optimizations for other, in light of #287 (comment). It may be desired to only pull things into an existing BooleanQuery when it's on the "left hand side".

@cjrh
Copy link
Collaborator Author

cjrh commented Oct 20, 2025

One concern that I have for this fluent interface is that it might be a bit annoying to use programmatically. Something like this might be better:

        # "The Old Man and the Sea" contains both "old" and "man"
        # (but with many chains)
        combined_must = (
            query_old
            .and_must_match([query_man, ...])        # a list of "ands"
            .and_must_not_match([query_mice, ...])   # a list of "and nots"
        )
        result = searcher.search(combined_must, 10)

Although I suppose I could support both forms and the fluent interface, using arg unpacking:

        # "The Old Man and the Sea" contains both "old" and "man"
        # (but with many chains)
        combined_must = (
            query_old
            .and_must_match(query_man, <another query>, <another_query>, ...)        # a list of "ands"
            .and_must_not_match(query_mice, <another query>, <another query>, ...)   # a list of "and nots"
        )
        result = searcher.search(combined_must, 10)

Then the caller can easily unpack a list into the parameters when calling the and_must_match() or and_must_not_match() methods. This seems pretty flexible, I reckon I will go this route.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant