Repository navigation
gmail: a search reads newer: as after: and older: as before: - #745
Conversation
# Conflicts: # backlot/routers/google.py
nuriyoo
left a comment
There was a problem hiding this comment.
This reads newer: and older: the way real does. I measured it against gmail.googleapis.com on 2026-10-15 with a Workspace user's gmail.readonly token, over a mailbox of 595 messages, comparing the ids each query returned rather than counts: messages.list answered newer:X with the ids of after:X, and older:X with those of before:X, in all 42 queries tried that give the operator a value. Those included 12 values (2026/10/01, 2026-10-01, 2026/10/1, 10/01/2026, seconds, and values real reads as no date such as abc and 2026/13/01), upper case, -newer:, the two together, each repeated, newer: beside after: in either order, and a space after the colon. threads.list agreed in the four I sent there. Your branch folds each alias before anything reads it, so it answers all of those as it answers the twin; a replay of the cases the parser handles differently (case, repeats in both orders, the empty value) over a five-message corpus agreed on messages and threads. Two small things, both suggested inline.
1. An empty newer: or older: is not an empty after: or before:. On real an empty one is a search term of its own name: newer: answers the messages newer does, and after: those of after (37 here), so the two differ. Your branch already answers it that way, since _GMAIL_OP needs a character after the colon, but nothing pins it: with the value made optional (\S* for \S+) both fold into an after: or before: with an empty value, which _gmail_date skips, and the test still passes. The two rows in the test suggestion fail on that change.
2. The comment's measurement. It cites the issue's counts; the suggestion records the id comparison above and the empty value, and the test docstring points at it. If you take it, the last sentence of the body's Why section can say the comment records the 2026-10-15 comparison.
Measuring this also showed how after: and before: read their own values (a month-first date, values real matches nothing for, a space after the colon, -after:), where Backlot differs; that is not this pull request's, and is filed as #751.
At e3140cc (Python 3.12, .[dev]):
pytest tests/test_search.py -k gmail 6 passed
the same with backlot/routers/google.py from main test_gmail_newer_older_are_after_before fails
9 mutations (each new key, each alias dropped or swapped, both 7 caught; `\S+` -> `\S*` and `.strip()` -> `.strip(" \t")` survive
to after, the alias lookup, the value made optional, strip)
With the two suggestions:
pytest (the whole suite) 6216 passed, 37 skipped
the same 9 mutations 8 caught; `.strip()` survives (a line this does not change)
ruff check, ruff format --check clean
…ty values are pinned On a Workspace mailbox on 2026-10-15 messages.list and threads.list answered newer:X with the ids after:X answers and older:X with those of before:X; an empty newer: or older: is a search term.
nuriyoo
left a comment
There was a problem hiding this comment.
Thanks for this. I applied the review's suggestion as written, as a maintainer commit, 64b7465: the comment on _GMAIL_KEY_ALIASES records the 2026-10-15 measurement, which compared the ids messages.list and threads.list answered, and the test pins an empty newer: and older: as search terms, as _GMAIL_OP already reads them. How real reads the date values themselves is filed separately. The description's line on the comment now names that measurement.
At 19f6889 (main at 52d03e0 merged in; the tree matches a local merge of the two):
pytest (the whole suite, Python 3.13, .[all,dev]) 9339 passed, 2 skipped
ruff check, ruff format --check clean
Closes #553.
What changed
A Gmail search now reads
newer:asafter:andolder:asbefore:._parse_gmail_qinbacklot/routers/google.pytakes both as operators and files each under the operator it is another name for, through_GMAIL_KEY_ALIASES, so the SQL push-down and_gmail_op_matchtreat them exactly as they treatafter:andbefore:. Before this, neither was in_GMAIL_KEYS, so each stayed in the free text, matched nothing full-text, and a query holding one answered an empty page.Why this is what the real API does
The measurement is the one in #553, against gmail.googleapis.com on 2026-10-09 by the issue's author, on a mailbox of 565 messages:
newer:2026/10/01answered the same 55 messages asafter:2026/10/01, andolder:2026/10/01filled a 500 page asbefore:2026/10/01did. The Gmail search reference lists the four operators as one entry. I have no Gmail token and did not call the real API. The comment beside_GMAIL_KEY_ALIASESrecords a maintainer's measurement on a Workspace mailbox on 2026-10-15, which compared the idsmessages.listandthreads.listanswered.Verification
Tests —
pyteston a.[dev]install, Ubuntu 24.04 (WSL), Python 3.12.3: 6070 passed, 37 skipped.test_gmail_newer_older_are_after_beforeintests/test_search.pyfails withbacklot/frommainand passes with this change. The issue's reproduction prints[]fornewer:2026/03/02andolder:2026/03/02onmain; here it prints the same subjects asafter:2026/03/02andbefore:2026/03/02.Optional surfaces that ran rather than skipped — none. This was a
.[dev]install only, sotests/test_sdk.py,tests/test_s3.py,tests/test_mcp.py,tests/test_llamaindex.pyand the reader, Google and mirage tests intests/test_integrations.pysat out. The change touchesbacklot/routers/google.pyandtests/test_search.pyonly.Lint —
ruff check . && ruff format --check .: clean.python scripts/prose_check.py upstream/main: no findings.A test fails without this change
A new endpoint serving corpus content is ACL-scoped, proved for both an admin and a scoped token
Comments state what was measured, not the history of the fix
The second box is not ticked because no endpoint is added.