feat(fts): 8a — standalone algorithms (tokenizer + BM25 + posting list)#78
Merged
Conversation
Phase 8 sub-phase 8a per docs/phase-8-plan.md. Adds the standalone
inverted-index trio (no SQL integration, no persistence) under
src/sql/fts/, mirroring the role Phase 7d.1's src/sql/hnsw.rs played
for vector search:
- tokenizer.rs — ASCII split + lowercase (Q3: ASCII MVP)
- bm25.rs — BM25+ scoring, k1=1.5 / b=0.75 fixed (Q4 + Q5: no
stemming, no stop list)
- posting_list.rs — in-memory inverted index keyed on i64 rowid;
insert / remove / query / matches / score
Public surface is infallible (no Result wrappers, no project-error
import) and depends only on std — direct reuse target for 8b's
fts_match / bm25_score scalar fns and the try_fts_probe optimizer
hook. Single-line wire-up in src/sql/mod.rs (pub mod fts;).
22 new unit tests, all inline #[cfg(test)]:
- tokenizer: empty/punctuation/lowercase/alphanumeric/non-ASCII
- bm25: zero-cases, TF monotonicity, length normalization, IDF
domination, hand-computed 3-doc reference, query-token
compounding
- posting_list: empty/insert/remove/reinsert idempotence, multi-term
any-term semantics, deterministic 1k-doc corpus,
tie-break by rowid asc
Out of scope (later sub-phases):
- IndexMethod::Fts arm + executor dispatch → 8b
- fts_match / bm25_score scalar fns → 8b
- try_fts_probe optimizer hook → 8b
- KIND_FTS_POSTING cell + v4→v5 file-format bump → 8c
- fts cargo feature gate (defer until MCP tool) → 8e
- docs sweep → 8f
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 3, 2026
Merged
joaoh82
added a commit
that referenced
this pull request
May 3, 2026
First major-version bump of SQLRite. The 0.1.x cycle covered Phase 0 → Phase 7 (modernization → AI-era extensions). v0.2.0 marks Phase 8 — full-text search + hybrid retrieval — and the on-disk file-format change that comes with it. What ships in v0.2.0 ==================== Phase 8 — Full-text search + hybrid retrieval (PRs #78–#83): - 8a — Standalone FTS algorithms (tokenizer + BM25 + posting list) under src/sql/fts/, mirroring Phase 7d.1's HNSW shape. - 8b — SQL surface: CREATE INDEX … USING fts (col), fts_match(col, 'q'), bm25_score(col, 'q'), the try_fts_probe optimizer hook, and INSERT/DELETE/UPDATE lifecycle wiring (rebuild-on-save). - 8c — Cell-encoded persistence (KIND_FTS_POSTING, 0x06), stage_fts_btree / load_fts_postings, and the on-demand v4 → v5 file-format bump. Existing v4 databases without FTS keep writing v4; the first save with an FTS index attached promotes to v5. Decoders accept both. - 8d — Hybrid retrieval worked example at examples/hybrid-retrieval/ — BM25 + vector cosine via raw arithmetic (no new typed function), hand-baked vectors so it stays reproducible without a model. - 8e — sqlrite-mcp bm25_search tool, symmetric with vector_search. The MCP server now exposes 8 tools (was 7). - 8f — Final docs sweep: new docs/fts.md canonical reference; FTS sections added to supported-sql.md, architecture.md, file-format.md (KIND_FTS_POSTING layout + v5 history entry), sql-engine.md, mcp.md (count bump 7→8), smoke-test.md; _index.md and roadmap.md updated to mark Phase 8 complete. File-format change ================== This is the headline reason for the major bump: - v4 (Phase 7) — value block dispatch gained the Vector tag (Phase 7a). Phase 7's JSON, HNSW, and ask additions all lived inside the v4 envelope. - v5 (Phase 8c) — adds KIND_FTS_POSTING (0x06). On-demand: only written when the database has at least one FTS index attached. Decoders accept v4 and v5; v0.2.0 readers can open v0.1.x files without surprise. v0.1.x readers cannot open v5 files (clean "unsupported version" error). Testing ======= Engine 303 + MCP 19 + 73 across the other crates — all green at the new version. Inter-crate deps bumped from 0.1 to 0.2 in three places (Cargo.toml's optional sqlrite-ask, sqlrite-mcp's sqlrite-engine ref, sdk/wasm's sqlrite-ask ref); cargo build refreshed Cargo.lock. Manifests bumped (12 files via scripts/bump-version.sh): - Cargo.toml - sqlrite-ffi/Cargo.toml - sqlrite-ask/Cargo.toml - sqlrite-mcp/Cargo.toml - sdk/python/Cargo.toml + pyproject.toml - sdk/nodejs/Cargo.toml + package.json - sdk/wasm/Cargo.toml - desktop/src-tauri/Cargo.toml + tauri.conf.json - desktop/package.json Plus inter-crate dep refs, the Cargo.toml comment block, and sqlrite-ask/README.md's installation snippet. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First sub-phase of Phase 8 — full-text search. 8a ships the standalone algorithms only, no SQL integration yet. Mirrors the shape of Phase 7d.1's
src/sql/hnsw.rs: pure algorithm, infallible API, zero project deps.src/sql/fts/with three filestokenizer.rs— ASCII split + lowercase (Q3: ASCII MVP)bm25.rs— BM25+ scoring (k1=1.5,b=0.75fixed; Q4 + Q5: no stemming, no stop list)posting_list.rs— in-memoryBTreeMap<term, BTreeMap<rowid, freq>>index withinsert/remove/query/matches/scoresrc/sql/mod.rs(pub mod fts;)The public surface is shaped to be directly callable from 8b without rework:
PostingList::matches/scoreback the per-rowfts_match/bm25_scorescalar fns, andPostingList::querypowers the bulktry_fts_probeoptimizer hook.Out of scope (later sub-phases)
IndexMethod::Ftsenum arm + executor dispatchfts_match/bm25_scorescalar fn dispatchtry_fts_probeoptimizer hookfts_indexes: Vec<FtsIndex>onTable+ INSERT/DELETE wiringKIND_FTS_POSTINGcell encoding + v4→v5 file-format bumpftscargo feature gatebm25_searchtool)docs/fts.md,supported-sql.md, etc.)Test plan
cargo build --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --all-targets— cleancargo test sql::fts— 22 / 22 passingcargo test --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs— full suite greencargo fmt --all -- --check— no diffcargo clippy --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --all-targets— no new warnings on FTS codecargo doc --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --no-deps— module docs buildNotable test coverage
f64::EPSILON * 16. Catches any future formula drift.b > 0working).🤖 Generated with Claude Code