Skip to content

Support safe FTS5 operators (AND, OR, NOT) in text_search queries #37

Description

@coreyt

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions