Skip to main content

nodedb_sql/
fts_types.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! FTS query type used by `SqlPlan::TextSearch`.
4//!
5//! `FtsQuery` is the structured representation of a full-text search query
6//! after lowering from PG surface syntax (`to_tsquery`, `plainto_tsquery`,
7//! `websearch_to_tsquery`, `@@`).  The executor in `nodedb-query` maps each
8//! variant to the corresponding `nodedb-fts` query mode.
9
10/// A structured full-text search query, lowered from PG tsquery syntax.
11///
12/// Variants that require engine support not yet available in `nodedb-fts`
13/// (`Phrase`, `Not`) are represented here but rejected by the executor with
14/// `SqlError::Unsupported` — they are never silently approximated.
15#[derive(Debug, Clone, PartialEq)]
16pub enum FtsQuery {
17    /// A single search term, optionally with fuzzy matching.
18    Plain { text: String, fuzzy: bool },
19    /// All sub-queries must match (tsquery `&` / `plainto_tsquery` space-AND).
20    And(Vec<FtsQuery>),
21    /// Any sub-query may match (tsquery `|`).
22    Or(Vec<FtsQuery>),
23    /// Excludes documents matching the inner query (tsquery `!`).
24    ///
25    /// Not yet supported by `nodedb-fts`; the executor returns
26    /// `Unsupported` when this variant is encountered.
27    Not(Box<FtsQuery>),
28    /// Strict consecutive-position phrase match (`phraseto_tsquery`).
29    ///
30    /// Not yet supported by `nodedb-fts`; the executor returns
31    /// `Unsupported` when this variant is encountered.
32    Phrase(Vec<String>),
33    /// Prefix search — matches all terms that begin with the given string
34    /// (tsquery `term:*`).
35    Prefix(String),
36}
37
38impl FtsQuery {
39    /// Extract the raw query text when the variant is `Plain`.
40    /// Used by the executor to pass the text to `nodedb-fts::FtsIndex::search`.
41    pub fn as_plain_text(&self) -> Option<&str> {
42        match self {
43            FtsQuery::Plain { text, .. } => Some(text.as_str()),
44            _ => None,
45        }
46    }
47
48    /// Return `true` if fuzzy matching is requested.
49    /// Only meaningful for `Plain`; all other variants return `false`.
50    pub fn is_fuzzy(&self) -> bool {
51        match self {
52            FtsQuery::Plain { fuzzy, .. } => *fuzzy,
53            _ => false,
54        }
55    }
56
57    /// Flatten an `And` or `Or` of `Plain` terms to a space-separated string
58    /// suitable for `FtsIndex::search`.  Returns `None` for structured queries
59    /// that cannot be expressed as a plain string.
60    pub fn to_plain_string(&self) -> Option<String> {
61        match self {
62            FtsQuery::Plain { text, .. } => Some(text.clone()),
63            FtsQuery::And(terms) | FtsQuery::Or(terms) => {
64                let parts: Option<Vec<String>> =
65                    terms.iter().map(|t| t.to_plain_string()).collect();
66                parts.map(|p| p.join(" "))
67            }
68            FtsQuery::Prefix(p) => Some(p.clone()),
69            FtsQuery::Not(_) | FtsQuery::Phrase(_) => None,
70        }
71    }
72}