Skip to content

refactor(core): compile search filters over an explicit ProjectScope - #1562

Merged
phernandez merged 1 commit into
mainfrom
feat/1558-scoped-search-1-compile
Sep 15, 2026
Merged

phernandez merged 1 commit into
mainfrom
feat/1558-scoped-search-1-compile

Conversation

@phernandez

Copy link
Copy Markdown
Member

Why

Part 1 of the #1558 stack. Single-project search, the database-scoped search route (the contract from #1559 and cloud #2080), and MCP search_all_projects should be one read pipeline parameterized by project scope, so one project ranks identically through every route. This PR makes the scope an explicit value and moves FTS compilation out of the project-bound repositories so a shared reader can call it next.

No query behavior changes. Project repositories call the same compilers with a scope of one.

What changed

  • search_scope.ProjectScope: sorted, unique, positive project IDs, built once at the boundary with of() or single(). predicate(column, params) renders column IN (:scope_0, ...) and binds each ID once per statement; an empty scope renders 1 = 0. Absence is not representable.
  • search_filters: FilterDialect holds the two spellings that differ per backend (note-type JSON accessor, after_date comparison). CompiledFilter replaces the 5-tuple the compilers returned. shared_filter_conditions compiles the filters both backends previously carried as copies: scope, permalink, directory subtree, candidate keys, item type, category, note type, after_date, valid time.
  • sqlite_search_query / postgres_search_query: term preparation, relaxed renderers, syntax-error classifiers, and compile_fts_filter as module functions. The SQLite compiler is pure; the repository resolves the entity table's columns and passes them in.
  • note_type_filters / temporal_filters: take scope= and match search rows on their full (project_id, ...) identity.
  • SearchRepositoryBase: gains self.scope; drops the abstract _prepare_search_term hook nothing in the base called.
  • Tests: call the module functions directly through a small fts_query(search_repository) helper; Postgres tests patch the syntax-error classifier on the repository module where it is bound. New test_search_scope.py.

Not in this PR

  • PR 2: extract vector retrieval, fusion, relaxation retry, and chunk hydration into a scope-parameterized SearchReader; project search() delegates.
  • PR 3: QUERY /v2/search/ and the scoped service method, porting the feat(core): add explicit multi-project API search #1559 tests.
  • PR 4: vector order per request shape and ANN pre-filter, with a tenant benchmark.
  • PR 5: back MCP search_all_projects with the scoped reader.

This supersedes the extraction half of #1559. That PR should not merge as-is.

Testing

Check Result
just fast-check passed
uv run pytest tests/repository tests/services/test_search*.py tests/api/v2/test_search*.py tests/api/v2/test_cjk_search_guidance.py --no-cov (SQLite) 1277 passed, 38 skipped
BASIC_MEMORY_TEST_POSTGRES=1 uv run pytest over the search repository, relaxation, temporal, metadata-filter, and search router suites (testcontainers) 300 passed, 15 skipped
just doctor passed

🤖 Generated with Claude Code

https://claude.ai/code/session_019YW9ysxugGGBCNEGzsxtFV

First step of #1558: one scope-parameterized read path shared by the
single-project route, the database-scoped route, and MCP search_all_projects.
This commit moves the FTS compilation out of the project-bound repositories
and makes the project set an explicit value. No query behavior changes.

- search_scope.ProjectScope: sorted, unique, positive project IDs built once
  at the boundary; predicate() renders `IN (:scope_N)` or `1 = 0` for empty.
- search_filters: FilterDialect (the two spellings that differ per backend),
  CompiledFilter (named FROM/WHERE/params/order/score instead of a 5-tuple),
  and shared_filter_conditions for the filters both backends compile the
  same way. Removes the duplicated filter blocks the two repositories carried.
- sqlite_search_query / postgres_search_query: term preparation, relaxed
  renderers, syntax-error classifiers, and compile_fts_filter as module
  functions. The SQLite compiler is pure; the repository passes the entity
  columns in instead of the compiler opening a session.
- note_type_filters / temporal_filters take scope= and match search rows on
  their full (project_id, ...) identity.
- SearchRepositoryBase gains self.scope (a scope of one) and drops the
  abstract _prepare_search_term hook nothing in the base called.
- Tests call the module functions directly; Postgres tests patch the
  classifier on the repository module where it is bound.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_019YW9ysxugGGBCNEGzsxtFV
Signed-off-by: phernandez <[email protected]>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

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