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}