From 83ad96b1b3fa3519bccf2fe82c9c683a8f16ce37 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:50:45 -0700 Subject: [PATCH] make allow_infix order-stable --- src/documents/search/_query.py | 16 ++++++++----- src/documents/tests/search/test_backend.py | 26 ++++++++++++---------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/documents/search/_query.py b/src/documents/search/_query.py index 15b672e1f..3584767bc 100644 --- a/src/documents/search/_query.py +++ b/src/documents/search/_query.py @@ -148,10 +148,10 @@ def _build_simple_token_query( ) -> tantivy.Query: escaped = regex.escape(token) # The simple analyzer keeps punctuation inside whitespace-delimited terms. - # Later query tokens may therefore begin either at the indexed term boundary - # or after punctuation within a term (for example, ``medical-history``). - # Do not allow an arbitrary infix for later tokens: a query ending in ``6`` - # must not match the middle of ``16``. + # Boundary-constrained query tokens may therefore begin either at the indexed + # term boundary or after punctuation within a term (for example, + # ``medical-history``). This avoids matching a numeric token such as ``6`` + # in the middle of ``16``. pattern = ( f".*{escaped}.*" if allow_infix @@ -282,10 +282,14 @@ def parse_simple_query( index, fields, token, - allow_infix=idx == 0, + # Preserve historical infix matching for single-token + # searches. In multi-token searches, constrain numeric + # tokens to boundaries to avoid partial-number overlap. + # This depends on token content, not query order. + allow_infix=len(tokens) == 1 or not token.isdecimal(), ), ) - for idx, token in enumerate(tokens) + for token in tokens ] simple_query = ( token_queries[0][1] diff --git a/src/documents/tests/search/test_backend.py b/src/documents/tests/search/test_backend.py index 3f8989180..8615dc0ee 100644 --- a/src/documents/tests/search/test_backend.py +++ b/src/documents/tests/search/test_backend.py @@ -163,10 +163,11 @@ class TestSearch: assert ( len(backend.search_ids("sswo", user=None, search_mode=SearchMode.TEXT)) == 1 ) - assert ( - len(backend.search_ids("sswo re", user=None, search_mode=SearchMode.TEXT)) - == 1 - ) + for query in ["sswo re", "re sswo"]: + assert ( + len(backend.search_ids(query, user=None, search_mode=SearchMode.TEXT)) + == 1 + ), query def test_text_mode_matches_all_terms_without_requiring_adjacency( self, @@ -230,11 +231,11 @@ class TestSearch: == 0 ) - def test_text_mode_anchors_later_query_tokens_to_token_starts( + def test_text_mode_anchors_numeric_tokens_regardless_of_query_order( self, backend: TantivyBackend, ) -> None: - """Multi-token simple search should not match later tokens in the middle of a word.""" + """Numeric tokens must not match in the middle of a larger number.""" exact_doc = Document.objects.create( title="Z-Berichte 6", content="monthly report", @@ -257,13 +258,14 @@ class TestSearch: backend.add_or_update(prefix_doc) backend.add_or_update(false_positive) - result_ids = set( - backend.search_ids("Z-Berichte 6", user=None, search_mode=SearchMode.TEXT), - ) + for query in ["Z-Berichte 6", "6 Z-Berichte"]: + result_ids = set( + backend.search_ids(query, user=None, search_mode=SearchMode.TEXT), + ) - assert exact_doc.id in result_ids - assert prefix_doc.id in result_ids - assert false_positive.id not in result_ids + assert exact_doc.id in result_ids, query + assert prefix_doc.id in result_ids, query + assert false_positive.id not in result_ids, query def test_text_mode_ignores_queries_without_searchable_tokens( self,