Skip to main content

sonic/query/
builder.rs

1// Sonic
2//
3// Fast, lightweight and schema-less search backend
4// Copyright: 2019, Valerian Saliou <valerian@valeriansaliou.name>
5// Copyright: 2026, Rémi Bardon <remi@remibardon.name>
6// License: Mozilla Public License v2.0 (MPL v2.0)
7
8use super::Query;
9use super::types::{QueryGenericLang, QuerySearchLimit, QuerySearchOffset};
10use crate::config::ConfigNormalization;
11use crate::lexer::{TokenLexerBuilder, TokenLexerMode};
12use crate::store::StoreItemBuilder;
13
14impl<'a> Query<'a> {
15    #[allow(clippy::too_many_arguments)] // This will be reworked at some point.
16    pub fn search(
17        query_id: &'a str,
18        collection: &'a str,
19        bucket: &'a str,
20        terms: &'a str,
21        limit: QuerySearchLimit,
22        offset: QuerySearchOffset,
23        lang: Option<QueryGenericLang>,
24        normalization_config: ConfigNormalization,
25    ) -> Result<Self, ()> {
26        match (
27            StoreItemBuilder::from_depth_2(collection, bucket),
28            TokenLexerBuilder::from(
29                TokenLexerMode::from_query_lang(&lang),
30                lang.and_then(QueryGenericLang::into_lang_opt),
31                terms,
32                normalization_config,
33            ),
34        ) {
35            (Ok(store), Ok(text_lexed)) => {
36                Ok(Query::Search(store, query_id, text_lexed, limit, offset))
37            }
38            _ => Err(()),
39        }
40    }
41
42    pub fn suggest(
43        query_id: &'a str,
44        collection: &'a str,
45        bucket: &'a str,
46        terms: &'a str,
47        limit: QuerySearchLimit,
48        normalization_config: ConfigNormalization,
49    ) -> Result<Self, ()> {
50        match (
51            StoreItemBuilder::from_depth_2(collection, bucket),
52            TokenLexerBuilder::from(
53                TokenLexerMode::NormalizeOnly,
54                None,
55                terms,
56                normalization_config,
57            ),
58        ) {
59            (Ok(store), Ok(text_lexed)) => Ok(Query::Suggest(store, query_id, text_lexed, limit)),
60            _ => Err(()),
61        }
62    }
63
64    pub fn list(
65        query_id: &'a str,
66        collection: &'a str,
67        bucket: &'a str,
68        limit: QuerySearchLimit,
69        offset: QuerySearchOffset,
70    ) -> Result<Self, ()> {
71        match StoreItemBuilder::from_depth_2(collection, bucket) {
72            Ok(store) => Ok(Query::List(store, query_id, limit, offset)),
73            _ => Err(()),
74        }
75    }
76
77    pub fn push(
78        collection: &'a str,
79        bucket: &'a str,
80        object: &'a str,
81        text: &'a str,
82        lang: Option<QueryGenericLang>,
83        normalization_config: ConfigNormalization,
84    ) -> Result<Self, ()> {
85        match (
86            StoreItemBuilder::from_depth_3(collection, bucket, object),
87            TokenLexerBuilder::from(
88                TokenLexerMode::from_query_lang(&lang),
89                lang.and_then(QueryGenericLang::into_lang_opt),
90                text,
91                normalization_config,
92            ),
93        ) {
94            (Ok(store), Ok(text_lexed)) => Ok(Query::Push(store, text_lexed)),
95            _ => Err(()),
96        }
97    }
98
99    pub fn pop(
100        collection: &'a str,
101        bucket: &'a str,
102        object: &'a str,
103        text: &'a str,
104        normalization_config: ConfigNormalization,
105    ) -> Result<Self, ()> {
106        match (
107            StoreItemBuilder::from_depth_3(collection, bucket, object),
108            TokenLexerBuilder::from(
109                TokenLexerMode::NormalizeOnly,
110                None,
111                text,
112                normalization_config,
113            ),
114        ) {
115            (Ok(store), Ok(text_lexed)) => Ok(Query::Pop(store, text_lexed)),
116            _ => Err(()),
117        }
118    }
119
120    pub fn count(
121        collection: &'a str,
122        bucket: Option<&'a str>,
123        object: Option<&'a str>,
124    ) -> Result<Self, ()> {
125        let store_result = match (bucket, object) {
126            (Some(bucket_inner), Some(object_inner)) => {
127                StoreItemBuilder::from_depth_3(collection, bucket_inner, object_inner)
128            }
129            (Some(bucket_inner), None) => StoreItemBuilder::from_depth_2(collection, bucket_inner),
130            _ => StoreItemBuilder::from_depth_1(collection),
131        };
132
133        match store_result {
134            Ok(store) => Ok(Query::Count(store)),
135            _ => Err(()),
136        }
137    }
138
139    pub fn flushc(collection: &'a str) -> Result<Self, ()> {
140        match StoreItemBuilder::from_depth_1(collection) {
141            Ok(store) => Ok(Query::FlushC(store)),
142            _ => Err(()),
143        }
144    }
145
146    pub fn flushb(collection: &'a str, bucket: &'a str) -> Result<Self, ()> {
147        match StoreItemBuilder::from_depth_2(collection, bucket) {
148            Ok(store) => Ok(Query::FlushB(store)),
149            _ => Err(()),
150        }
151    }
152
153    pub fn flusho(collection: &'a str, bucket: &'a str, object: &'a str) -> Result<Self, ()> {
154        match StoreItemBuilder::from_depth_3(collection, bucket, object) {
155            Ok(store) => Ok(Query::FlushO(store)),
156            _ => Err(()),
157        }
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    fn test_normalization_config() -> ConfigNormalization {
166        ConfigNormalization {
167            diacritic_folding_enabled: false,
168            stemming_enabled: false,
169        }
170    }
171
172    #[test]
173    fn it_builds_search_query() {
174        #[rustfmt::skip]
175        assert!(Query::search(
176            "id1", "c:test:1", "b:test:1", "Michael Dake", 10, 20, None,
177            test_normalization_config(),
178        ).is_ok());
179
180        #[rustfmt::skip]
181        assert!(Query::search(
182            "id2", "c:test:1", "", "Michael Dake", 1, 0, None,
183            test_normalization_config(),
184        ).is_err());
185    }
186
187    #[test]
188    fn it_builds_suggest_query() {
189        #[rustfmt::skip]
190        assert!(Query::suggest("id1", "c:test:2", "b:test:2", "Micha", 5, test_normalization_config()).is_ok());
191
192        #[rustfmt::skip]
193        assert!(Query::suggest("id2", "c:test:2", "", "Micha", 1, test_normalization_config()).is_err());
194    }
195
196    #[test]
197    fn it_builds_list_query() {
198        assert!(Query::list("id1", "c:test:2", "b:test:2", 100, 0).is_ok());
199        assert!(Query::list("id2", "c:test:2", "", 10, 0).is_err());
200    }
201
202    #[test]
203    fn it_builds_push_query() {
204        #[rustfmt::skip]
205        assert!(Query::push(
206            "c:test:3", "b:test:3", "o:test:3", "My name is Michael Dake. I'm ordering in the US.", None,
207            test_normalization_config(),
208        ).is_ok());
209
210        #[rustfmt::skip]
211        assert!(Query::push(
212            "c:test:3", "", "o:test:3", "My name is Michael Dake.", None,
213            test_normalization_config(),
214        ).is_err());
215    }
216
217    #[test]
218    fn it_builds_pop_query() {
219        #[rustfmt::skip]
220        assert!(Query::pop("c:test:4", "b:test:4", "o:test:4", "ordering US", test_normalization_config()).is_ok());
221
222        #[rustfmt::skip]
223        assert!(Query::pop("c:test:4", "", "o:test:4", "ordering US", test_normalization_config()).is_err());
224    }
225
226    #[test]
227    fn it_builds_count_query() {
228        assert!(Query::count("c:test:5", None, None).is_ok());
229        assert!(Query::count("c:test:5", Some("b:test:5"), None).is_ok());
230        assert!(Query::count("c:test:5", Some("b:test:5"), Some("o:test:5")).is_ok());
231        assert!(Query::count("c:test:5", Some(""), Some("o:test:5")).is_err());
232    }
233
234    #[test]
235    fn it_builds_flushc_query() {
236        assert!(Query::flushc("c:test:6").is_ok());
237        assert!(Query::flushc("").is_err());
238    }
239
240    #[test]
241    fn it_builds_flushb_query() {
242        assert!(Query::flushb("c:test:7", "b:test:7").is_ok());
243        assert!(Query::flushb("c:test:7", "").is_err());
244    }
245
246    #[test]
247    fn it_builds_flusho_query() {
248        assert!(Query::flusho("c:test:8", "b:test:8", "o:test:8").is_ok());
249        assert!(Query::flusho("c:test:8", "b:test:8", "").is_err());
250    }
251}