Repository navigation
Complete managed full-text search: Turso parity, incremental storage, joins, browser - #72
Merged
Marc-André Moreau (mamoreau-devolutions) merged 1 commit intoSep 25, 2026
Conversation
… joins, browser Close the remaining gaps between Ahtola's `CREATE INDEX ... USING fts` and the pinned Turso v0.8.0-pre.7 FTS surface, and make it perform. Conformance - Execute turso/fts.sqltest (previously skipped as `@backend cli`); five MATCH-context errors lack only the CLI "Parse error:" prefix and are recorded. - Port every FTS test from upstream tests/integration/index_method and tests/fuzz/fts.rs (ManagedFtsUpstreamIntegrationTests, 48 tests). Tokenization - Reproduce Tantivy 0.26 analyzers exactly: char::is_alphanumeric runs, remove-long measured on the source span, full per-character lowercase, ASCII-only whitespace. - Read all character data from a generated Unicode 16.0 table (scripts/unicode) instead of host ICU; string.Normalize silently no-ops under InvariantGlobalization, which disabled unicode61 folding in browser builds. Planning - Push method-owned fts_match conjuncts into join arms so the FTS index serves joins in either order, with honest EXPLAIN QUERY PLAN rows. - Resolve ORDER BY result aliases under LIMIT 0. Storage and maintenance - Replace per-posting structs with immutable column-wise segments, a copy-on-write overlay, tiered merges and one-pass bulk builds (~3.5x less memory, 4x faster rebuild). - Carry state-sharing attachment forks and a cloned journal through table clones that preserve row lineage, and record exact change sets for whole-row-list rewrites (UPDATE, DELETE, UPSERT, compiled DELETE, REPLACE), so committed writes reconcile incrementally instead of rebuilding. - Reconcile published attachments into a fork and publish atomically behind a per-attachment gate, so concurrent readers never see in-place mutation. Query execution - Answer the residual fts_match re-check from the executed scan's match set and map hits through the table's cached rowid lookup (common-term queries ~18x faster, rare-term query floor 0.9 ms -> 0.03 ms at 50k rows). Browser, docs, benchmarks - Chromium package lane exercises FTS over OPFS including reopen. - Add TursoFtsWorkloadBenchmarks (vs SQLite FTS5) and document results, divergences (unfiltered score ordering keeps SQL semantics) and limits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Marc-André Moreau (mamoreau-devolutions)
merged commit Sep 25, 2026
64f9631
into
master
11 checks passed
Marc-André Moreau (mamoreau-devolutions)
deleted the
claude/ahtola-fts-support-c0581c
branch
September 25, 2026 21:39
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
Closes the remaining gaps between Ahtola's
CREATE INDEX … USING ftsand the pinned Tursov0.8.0-pre.7FTS surface, and makes it perform. Turso compiles FTS out of every WebAssembly build (#[cfg(all(feature = "fts", not(target_family = "wasm")))]); Ahtola's pure-managed index now works, and is validated, in the browser too.Conformance
turso/fts.sqltestnow executes (it was skipped wholesale as@backend cli, though its cases are plain SQL). All pass except fiveMATCH-context errors that lack only the CLIParse error:prefix. These are recorded like the existing vector marker, and the inventory's expected-failure count goes from 73 to 78.tests/integration/index_method/mod.rsandtests/fuzz/fts.rsis ported (ManagedFtsUpstreamIntegrationTests, 48 tests). Tantivy-internal counter assertions are dropped with a comment at each site.Tokenization (Tantivy-exact, host independent)
default/simplesplit on Rustchar::is_alphanumeric. Previously .NETIsLetterOrDigitsplit Indic/Thai/Arabic words at vowel signs and dropped²,½,Ⓐ.to_lowercase, andwhitespacesplits on ASCII whitespace only.scripts/unicode/Update-ManagedUnicodeData.ps1, fingerprint-tested).string.Normalizesilently returns its input underInvariantGlobalization, which had turnedunicode61accent folding off in typical Blazor builds.Planning
fts_matchconjunct on a join arm is pushed into that arm, so its FTS index serves the join in either order (upstreamtest_fts_join_order_optimization). EXPLAIN QUERY PLAN reports the arm's method search.ORDER BY <alias> LIMIT 0no longer raisesno such column. This is a general engine fix.Storage and incremental maintenance
Every write transaction and statement backup clones the table, and the clone's attachment used to start empty. The next query after any explicit transaction, file-backed write, UPDATE or DELETE therefore rebuilt the whole index.
ForkWithState()attachments plus a cloned journal.RecordBulkvouches only for the rewrite's own revision bumps.Query execution
fts_matchre-check on rows a method scan produced now answers from that scan's match set by rowid instead of re-tokenizing each row.Performance (50k docs, 46 tokens each, shared dev workstation, approximate)
BenchmarkDotNet (
TursoFtsWorkloadBenchmarks, 20k docs) puts search queries at 1.1–2.7× native SQLite FTS5.Reviewer notes
ORDER BY fts_score(…) DESC LIMIT nkeeps non-matching rows at0.0, preserving SQL semantics on every plan. Upstream's Score cursor returns only ranked hits, which is whatWHERE fts_match(…)returns here. Tell me if you want Turso's behavior instead.INSERTrecommendation. The fix is a separate transaction-architecture change: at least two paths mutate stored rows in place, so the copy can't simply be made shallow.turso-srcis untouched. Its local checkout is only initialized shallowly, and the pointer is unchanged.Validation
ManagedFtsUnicodePinningTestsManagedFtsJoinPlanningTestsManagedFtsSegmentStorageTests(differential fuzz against fresh builds with forks, merges and compaction)ManagedFtsIncrementalMaintenanceTests(zero rebuilds across ten committed write shapes, in memory and on file, plus rollback)./build.ps1 validate-browser-packagepasses in Chromium, including the newpersistent-fts=8OPFS round trip../build.ps1 validate-browser-trimpasses, with no Ahtola-originated warnings.dotnet formatfindings on changed lines.🤖 Generated with Claude Code