Problem
text_search() currently sanitizes all user input by quoting every whitespace-delimited token via sanitize_fts5_query() in crates/fathomdb-query/src/compile.rs:82-91:
fn sanitize_fts5_query(raw: &str) -> String {
let tokens: Vec<String> = raw
.split_whitespace()
.map(|token| {
let escaped = token.replace('"', "\"\"");
format!("\"{escaped}\"")
})
.collect();
tokens.join(" ")
}
This produces an implicit AND of quoted literals. The sanitization is intentional — it prevents FTS5 syntax injection (operators like AND, OR, NOT, NEAR, column filters col:, parentheses, and * wildcards are all neutralized inside quoted strings).
However, this means queries like "ship OR docs" become "ship" "OR" "docs" — an AND of three literal terms. Since the literal word OR rarely appears in indexed text, the query returns 0 results. Users have no way to express disjunction.
Proposal
Recognize a subset of FTS5 operators as safe and pass them through unquoted, while continuing to quote all other tokens. Candidate safe operators:
| Operator |
FTS5 syntax |
Risk |
Notes |
AND |
implicit (space) |
None |
Already the default behavior |
OR |
OR |
Low |
Broadens results, no injection vector |
NOT |
NOT |
Low |
Narrows results; could be surprising if first token |
NEAR |
NEAR / NEAR/N |
Medium |
Proximity search; the /N syntax adds parsing complexity |
* (prefix) |
token* |
Medium |
Triggers prefix scan; could be expensive on short prefixes |
| Column filter |
col: |
High |
Exposes internal column names; should remain blocked |
| Parentheses |
( ) |
Medium |
Grouping; adds nesting complexity |
A conservative first pass: support OR and NOT only. These are the highest-value operators with the lowest risk. NEAR, prefix wildcards, and parenthetical grouping can be considered later.
Suggested behavior
"ship OR docs" → "ship" OR "docs" (disjunction)
"ship NOT blocked" → "ship" NOT "blocked" (exclusion)
"ship docs" → "ship" "docs" (AND, unchanged)
"ship OR" → "ship" "OR" (trailing operator treated as literal)
"NOT ship" → NOT "ship" (leading NOT is valid FTS5)
Operators should be case-sensitive (OR recognized, or quoted as a literal) to match FTS5 convention and reduce false positives in natural language.
Context
Discovered while integrating memex with fathomdb's property FTS. Memex needs OR semantics for search queries but cannot do client-side term fanout as a workaround.
🤖 Generated with Claude Code
Problem
text_search()currently sanitizes all user input by quoting every whitespace-delimited token viasanitize_fts5_query()incrates/fathomdb-query/src/compile.rs:82-91:This produces an implicit AND of quoted literals. The sanitization is intentional — it prevents FTS5 syntax injection (operators like
AND,OR,NOT,NEAR, column filterscol:, parentheses, and*wildcards are all neutralized inside quoted strings).However, this means queries like
"ship OR docs"become"ship" "OR" "docs"— an AND of three literal terms. Since the literal wordORrarely appears in indexed text, the query returns 0 results. Users have no way to express disjunction.Proposal
Recognize a subset of FTS5 operators as safe and pass them through unquoted, while continuing to quote all other tokens. Candidate safe operators:
ANDORORNOTNOTNEARNEAR/NEAR/N/Nsyntax adds parsing complexity*(prefix)token*col:( )A conservative first pass: support
ORandNOTonly. These are the highest-value operators with the lowest risk.NEAR, prefix wildcards, and parenthetical grouping can be considered later.Suggested behavior
Operators should be case-sensitive (
ORrecognized,orquoted as a literal) to match FTS5 convention and reduce false positives in natural language.Context
Discovered while integrating memex with fathomdb's property FTS. Memex needs OR semantics for search queries but cannot do client-side term fanout as a workaround.
🤖 Generated with Claude Code