Repository: paperless-ngx/paperless-ngx
Pull request: Fix: handle whoosh query correction errors
<!--
Note: All PRs with code changes should be targeted to the `dev` branch, pure documentation changes can target `main`
-->

## Proposed change

I was able to get a more useful traceback:

```
Internal Server Error: /api/search/
Traceback (most recent call last):
  File "paperless-ngx/.venv/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "paperless-ngx/.venv/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "paperless-ngx/.venv/lib/python3.10/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper
    return view_func(request, *args, **kwargs)
  File "paperless-ngx/.venv/lib/python3.10/site-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
  File "paperless-ngx/.venv/lib/python3.10/site-packages/rest_framework/views.py", line 515, in dispatch
    response = self.handle_exception(exc)
  File "paperless-ngx/.venv/lib/python3.10/site-packages/rest_framework/views.py", line 475, in handle_exception
    self.raise_uncaught_exception(exc)
  File "paperless-ngx/.venv/lib/python3.10/site-packages/rest_framework/views.py", line 486, in raise_uncaught_exception
    raise exc
  File "paperless-ngx/.venv/lib/python3.10/site-packages/rest_framework/views.py", line 512, in dispatch
    response = handler(request, *args, **kwargs)
  File "paperless-ngx/src/documents/views.py", line 1725, in get
    results = fts_query[0:1]
  File "paperless-ngx/src/documents/index.py", line 293, in __getitem__
    q, mask = self._get_query()
  File "paperless-ngx/src/documents/index.py", line 365, in _get_query
    corrected = self.searcher.correct_query(q, q_str)
  File "paperless-ngx/.venv/lib/python3.10/site-packages/whoosh/searching.py", line 977, in correct_query
    return sqc.correct_query(q, qstring)
  File "paperless-ngx/.venv/lib/python3.10/site-packages/whoosh/spelling.py", line 345, in correct_query
    return Correction(q, qstring, corrected_q, corrected_tokens)
  File "paperless-ngx/.venv/lib/python3.10/site-packages/whoosh/spelling.py", line 216, in __init__
    self.string = self.format_string(highlight.NullFormatter())
  File "paperless-ngx/.venv/lib/python3.10/site-packages/whoosh/spelling.py", line 238, in format_string
    out = formatter.format_fragment(fragment, replace=True)
  File "paperless-ngx/.venv/lib/python3.10/site-packages/whoosh/highlight.py", line 721, in format_fragment
    for t in sorted(
  File "paperless-ngx/.venv/lib/python3.10/site-packages/whoosh/highlight.py", line 723, in <lambda>
    key=lambda token: (token.startchar, -(token.endchar - token.startchar)),
TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
```

Closes #10119

## Type of change

<!--
What type of change does your PR introduce to Paperless-ngx?
NOTE: Please check only one box!
-->

- [ ] Bug fix: non-breaking change which fixes an issue.
- [ ] New feature / Enhancement: non-breaking change which adds functionality. _Please read the important note above._
- [ ] Breaking change: fix or feature that would cause existing functionality to not work as expected.
- [ ] Documentation only.
- [ ] Other. Please explain:

## Checklist:

<!--
NOTE: PRs that do not address the following will not be merged, please do not skip any relevant items.
-->

- [ ] I have read & agree with the [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/main/CONTRIBUTING.md).
- [ ] If applicable, I have included testing coverage for new code in this PR, for [backend](https://docs.paperless-ngx.com/development/#testing) and / or [front-end](https://docs.paperless-ngx.com/development/#testing-and-code-style) changes.
- [ ] If applicable, I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers.
- [ ] If applicable, I have checked that all tests pass, see [documentation](https://docs.paperless-ngx.com/development/#back-end-development).
- [ ] I have run all `pre-commit` hooks, see [documentation](https://docs.paperless-ngx.com/development/#code-formatting-with-pre-commit-hooks).
- [ ] I have made corresponding changes to the documentation as needed.
- [ ] I have checked my modifications for any breaking changes.


Problem statement:
[BUG] Advanced Search throws "unsupported operand type(s) for -: 'NoneType' and 'NoneType'" when searching for specific hyphenated patterns ending in digits/letters
### Description

When using the Advanced Search in Paperless-ngx (v2.16.2, Docker, PostgreSQL 16, default config), searching for certain patterns containing a hyphen followed by digits (or digits+letters) causes an internal error. The normal/global search works as expected.

### Steps to reproduce

1. Go to Advanced Search.
2. Enter any of the following search terms (in the global search they work fine):
        02091-C-71
        02091-C-712
        02091-C-71a
        02091-C-76hallo
3. Submit the search.

**Expected behavior**
The search should return matching documents, as it does in the global search.

**Actual behavior**
No results, instead a backend error appears:

`[WARNING] [paperless.api] An error occurred listing search results: unsupported operand type(s) for -: 'NoneType' and 'NoneType'`

**Notes**
Similar patterns without trailing digits/letters (e.g. 02091-C-7, 02091-C-) do not trigger the error.

This appears to be a parsing bug in the advanced search query handler (possibly Whoosh-related), misinterpreting the hyphenated pattern as a numeric or date range.

**Workaround**
The global search and the title/content search works as expected for these terms.

### Webserver logs

```bash
[2025-06-04 09:05:08,824] [DEBUG] [paperless.tasks] Training data unchanged.
[2025-06-04 09:59:41,968] [WARNING] [paperless.api] An error occurred listing search results: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
[2025-06-04 09:59:42,045] [WARNING] [paperless.api] An error occurred listing search results: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
[2025-06-04 09:59:42,617] [WARNING] [paperless.api] An error occurred listing search results: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
```

### Browser logs

```bash

```

### Paperless-ngx version

2.16.2

### Host OS

Linux-5.10.60-qnap-x86_64-with-glibc2.36

### Installation method

Docker - official image

### System status

```json
{
    "pngx_version": "2.16.2",
    "server_os": "Linux-5.10.60-qnap-x86_64-with-glibc2.36",
    "install_type": "docker",
    "storage": {
        "total": 24189255811072,
        "available": 1199967424512
    },
    "database": {
        "type": "postgresql",
        "url": "paperless",
        "status": "OK",
        "error": null,
        "migration_status": {
            "latest_migration": "paperless.0004_applicationconfiguration_barcode_asn_prefix_and_more",
            "unapplied_migrations": []
        }
    },
    "tasks": {
        "redis_url": "redis://broker:6379",
        "redis_status": "OK",
        "redis_error": null,
        "celery_status": "OK",
        "celery_url": "celery@1e7b1aa1c0aa",
        "celery_error": null,
        "index_status": "OK",
        "index_last_modified": "2025-06-04T00:01:37.076834+02:00",
        "index_error": null,
        "classifier_status": "OK",
        "classifier_last_trained": "2025-06-04T08:05:09.317602Z",
        "classifier_error": null,
        "sanity_check_status": "OK",
        "sanity_check_last_run": "2025-05-31T22:30:49.468318Z",
        "sanity_check_error": null
    }
}
```

### Browser

Firefox 139.0.1

### Configuration changes

ASN on

### Please confirm the following

- [x] I believe this issue is a bug that affects all users of Paperless-ngx, not something specific to my installation.
- [x] This issue is not about the OCR or archive creation of a specific file(s). Otherwise, please see above regarding OCR tools.
- [x] I have already searched for relevant existing issues and discussions before opening this report.
- [x] I have updated the title field above with a concise description.

Hints from issue discussion:
Indeed the bug comes from whoosh, not much we're going to do there.

But the solution is to put it in quotes.

Commit message: Fix: handle whoosh query correction errors

Patch to review:
diff --git a/src/documents/index.py b/src/documents/index.py
index 3a2b2cb5837..581a4b93bd3 100644
--- a/src/documents/index.py
+++ b/src/documents/index.py
@@ -362,9 +362,16 @@ def _get_query(self) -> tuple:
         )
         q = qp.parse(q_str)
 
-        corrected = self.searcher.correct_query(q, q_str)
-        if corrected.query != q:
-            corrected.query = corrected.string
+        try:
+            corrected = self.searcher.correct_query(q, q_str)
+            if corrected.query != q:
+                corrected.query = corrected.string
+        except Exception as e:
+            logger.info(
+                "Error while correcting query %s: %s",
+                f"{q_str!r}",
+                e,
+            )
 
         return q, None
 
diff --git a/src/documents/tests/test_api_search.py b/src/documents/tests/test_api_search.py
index 7ffce06defa..9e2bb747440 100644
--- a/src/documents/tests/test_api_search.py
+++ b/src/documents/tests/test_api_search.py
@@ -645,6 +645,26 @@ def test_search_spelling_correction(self):
 
         self.assertEqual(correction, None)
 
+    @mock.patch(
+        "whoosh.searching.Searcher.correct_query",
+        side_effect=Exception("Test error"),
+    )
+    def test_corrected_query_error(self, mock_correct_query):
+        """
+        GIVEN:
+            - A query that raises an error on correction
+        WHEN:
+            - API request for search with that query
+        THEN:
+            - The error is logged and the search proceeds
+        """
+        with self.assertLogs("paperless.index", level="INFO") as cm:
+            response = self.client.get("/api/documents/?query=2025-06-04")
+            self.assertEqual(response.status_code, status.HTTP_200_OK)
+            error_str = cm.output[0]
+            expected_str = "Error while correcting query '2025-06-04': Test error"
+            self.assertIn(expected_str, error_str)
+
     def test_search_more_like(self):
         """
         GIVEN:


Code review comments:
diff_hunk: @@ -362,9 +362,16 @@ def _get_query(self) -> tuple:
         )
         q = qp.parse(q_str)
 
-        corrected = self.searcher.correct_query(q, q_str)
-        if corrected.query != q:
-            corrected.query = corrected.string
path: src/documents/index.py
text: @author:
Someone tell me if I'm crazy, but I think this _doesnt do anything_. It even looks incorrect setting the `query = string` (it's almost certainly a type mismatch).

I think the fix here is OK but arguably this could be just... removed?