Skip to main content

flams_search/
query.rs

1#[cfg(all(feature = "tantivy", not(feature = "vectorsearch")))]
2use flams_backend_types::search::FragmentQueryFilter;
3use flams_backend_types::search::{QueryFilter, SearchResult, SearchResultKind};
4#[cfg(all(feature = "tantivy", not(feature = "vectorsearch")))]
5use ftml_uris::{DocumentElementUri, DocumentUri};
6
7#[cfg(all(feature = "tantivy", not(feature = "vectorsearch")))]
8#[must_use]
9pub fn build_query(
10    query: &str,
11    index: &tantivy::Index,
12    filter: FragmentQueryFilter,
13) -> Option<Box<dyn tantivy::query::Query>> {
14    use std::fmt::Write;
15    let mut s = String::new();
16    if !filter.flags.allow_documents()
17        || !filter.flags.allow_paragraphs()
18        || !filter.flags.allow_definitions()
19        || !filter.flags.allow_examples()
20        || !filter.flags.allow_assertions()
21        || !filter.flags.allow_problems()
22    {
23        //s.push('(');
24        let mut had_first = false;
25        if filter.flags.allow_documents() {
26            had_first = true;
27            s.push_str("(kind:0");
28        }
29        if filter.flags.allow_paragraphs() {
30            s.push_str(if had_first { " OR kind:1" } else { "(kind:1" });
31            had_first = true;
32        }
33        if filter.flags.allow_definitions() {
34            s.push_str(if had_first { " OR kind:2" } else { "(kind:2" });
35            had_first = true;
36        }
37        if filter.flags.allow_examples() {
38            s.push_str(if had_first { " OR kind:3" } else { "(kind:3" });
39            had_first = true;
40        }
41        if filter.flags.allow_assertions() {
42            s.push_str(if had_first { " OR kind:4" } else { "(kind:4" });
43            had_first = true;
44        }
45        if filter.flags.allow_problems() {
46            s.push_str(if had_first { " OR kind:5" } else { "(kind:5" });
47        }
48        if had_first {
49            s.push_str(") AND ");
50        }
51    }
52    if filter.flags.is_definition_like() {
53        s.push_str("deflike:true AND ");
54    }
55    write!(s, "({query})").ok()?;
56    let schema = crate::schema::SearchSchema::get();
57    let mut parser = tantivy::query::QueryParser::for_index(
58        index,
59        vec![schema.fors, schema.uri, schema.title, schema.body],
60    );
61    //parser.set_field_fuzzy(SCHEMA.body, false, 1, true);
62    parser.set_conjunction_by_default();
63    parser.parse_query(&s).ok()
64}
65
66#[cfg(all(feature = "tantivy", not(feature = "vectorsearch")))]
67#[derive(Debug)]
68pub(crate) struct Wrapper<T>(pub T);
69
70#[cfg(all(feature = "tantivy", not(feature = "vectorsearch")))]
71impl tantivy::schema::document::ValueDeserialize for Wrapper<bool> {
72    fn deserialize<'de, D>(
73        deserializer: D,
74    ) -> Result<Self, tantivy::schema::document::DeserializeError>
75    where
76        D: tantivy::schema::document::ValueDeserializer<'de>,
77    {
78        Ok(Self(deserializer.deserialize_bool()?))
79    }
80}
81
82#[cfg(all(feature = "tantivy", not(feature = "vectorsearch")))]
83impl tantivy::schema::document::ValueDeserialize for Wrapper<SearchResultKind> {
84    fn deserialize<'de, D>(
85        deserializer: D,
86    ) -> Result<Self, tantivy::schema::document::DeserializeError>
87    where
88        D: tantivy::schema::document::ValueDeserializer<'de>,
89    {
90        deserializer
91            .deserialize_u64()?
92            .try_into()
93            .map(Wrapper)
94            .map_err(|()| {
95                tantivy::schema::document::DeserializeError::custom(format_args!("weird"))
96            })
97    }
98}
99
100#[cfg(all(feature = "tantivy", not(feature = "vectorsearch")))]
101impl tantivy::schema::document::DocumentDeserialize for Wrapper<SearchResult> {
102    fn deserialize<'de, D>(
103        mut deserializer: D,
104    ) -> Result<Self, tantivy::schema::document::DeserializeError>
105    where
106        D: tantivy::schema::document::DocumentDeserializer<'de>,
107    {
108        macro_rules! next {
109            ($name:literal) => {{
110                let Some((_, r)) = deserializer.next_field()?.map_err(|e| {
111                    tantivy::schema::document::DeserializeError::custom(format_args!(
112                        "weird A: {e} (in {})",
113                        $name
114                    ))
115                }) else {
116                    return Err(tantivy::schema::document::DeserializeError::custom(
117                        format_args!("Missing value {}", $name),
118                    ));
119                };
120                r
121            }};
122            ($name:literal!) => {{
123                let Some((_, Wrapper(r))) = deserializer.next_field().map_err(|e| {
124                    tantivy::schema::document::DeserializeError::custom(format_args!(
125                        "weird A: {e} (in {})",
126                        $name
127                    ))
128                })?
129                else {
130                    return Err(tantivy::schema::document::DeserializeError::custom(
131                        format_args!("Missing value {}", $name),
132                    ));
133                };
134                r
135            }};
136        }
137        let kind = next!("kind"!);
138        match kind {
139            SearchResultKind::Document => Ok(Self(SearchResult::Document(next!("uri"!)))),
140            kind => {
141                let uri = next!("uri"!);
142                let def_like = next!("deflike"!);
143                let mut fors = Vec::new();
144                while let Ok(Some((_, s))) = deserializer.next_field() {
145                    fors.push(s);
146                }
147                Ok(Self(SearchResult::Paragraph {
148                    uri,
149                    def_like,
150                    kind,
151                    fors,
152                }))
153            }
154        }
155    }
156}
157
158#[cfg(all(feature = "tantivy", not(feature = "vectorsearch")))]
159impl tantivy::schema::document::ValueDeserialize for Wrapper<DocumentUri> {
160    fn deserialize<'de, D>(
161        mut deserializer: D,
162    ) -> Result<Self, tantivy::schema::document::DeserializeError>
163    where
164        D: tantivy::schema::document::ValueDeserializer<'de>,
165    {
166        //SAFETY: it's a string
167        unsafe { String::from_utf8_unchecked(deserializer.deserialize_bytes()?) }
168            .parse()
169            .map_or_else(
170                |_| {
171                    Err(tantivy::schema::document::DeserializeError::custom(
172                        "Invalid DocumentUri",
173                    ))
174                },
175                |u| Ok(Wrapper(u)),
176            )
177    }
178}
179
180#[cfg(all(feature = "tantivy", not(feature = "vectorsearch")))]
181impl tantivy::schema::document::ValueDeserialize for Wrapper<DocumentElementUri> {
182    fn deserialize<'de, D>(
183        mut deserializer: D,
184    ) -> Result<Self, tantivy::schema::document::DeserializeError>
185    where
186        D: tantivy::schema::document::ValueDeserializer<'de>,
187    {
188        //SAFETY: it's a string
189        unsafe { String::from_utf8_unchecked(deserializer.deserialize_bytes()?) }
190            .parse()
191            .map_or_else(
192                |_| {
193                    Err(tantivy::schema::document::DeserializeError::custom(
194                        "Invalid DocumentElementUri",
195                    ))
196                },
197                |u| Ok(Wrapper(u)),
198            )
199    }
200}