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::lexer::{TokenLexerBuilder, TokenLexerMode};
11use crate::store::StoreItemBuilder;
12
13impl<'a> Query<'a> {
14    pub fn search(
15        query_id: &'a str,
16        collection: &'a str,
17        bucket: &'a str,
18        terms: &'a str,
19        limit: QuerySearchLimit,
20        offset: QuerySearchOffset,
21        lang: Option<QueryGenericLang>,
22    ) -> Result<Self, ()> {
23        match (
24            StoreItemBuilder::from_depth_2(collection, bucket),
25            TokenLexerBuilder::from(TokenLexerMode::from_query_lang(lang), terms),
26        ) {
27            (Ok(store), Ok(text_lexed)) => {
28                Ok(Query::Search(store, query_id, text_lexed, limit, offset))
29            }
30            _ => Err(()),
31        }
32    }
33
34    pub fn suggest(
35        query_id: &'a str,
36        collection: &'a str,
37        bucket: &'a str,
38        terms: &'a str,
39        limit: QuerySearchLimit,
40    ) -> Result<Self, ()> {
41        match (
42            StoreItemBuilder::from_depth_2(collection, bucket),
43            TokenLexerBuilder::from(TokenLexerMode::NormalizeOnly, terms),
44        ) {
45            (Ok(store), Ok(text_lexed)) => Ok(Query::Suggest(store, query_id, text_lexed, limit)),
46            _ => Err(()),
47        }
48    }
49
50    pub fn list(
51        query_id: &'a str,
52        collection: &'a str,
53        bucket: &'a str,
54        limit: QuerySearchLimit,
55        offset: QuerySearchOffset,
56    ) -> Result<Self, ()> {
57        match StoreItemBuilder::from_depth_2(collection, bucket) {
58            Ok(store) => Ok(Query::List(store, query_id, limit, offset)),
59            _ => Err(()),
60        }
61    }
62
63    pub fn push(
64        collection: &'a str,
65        bucket: &'a str,
66        object: &'a str,
67        text: &'a str,
68        lang: Option<QueryGenericLang>,
69    ) -> Result<Self, ()> {
70        match (
71            StoreItemBuilder::from_depth_3(collection, bucket, object),
72            TokenLexerBuilder::from(TokenLexerMode::from_query_lang(lang), text),
73        ) {
74            (Ok(store), Ok(text_lexed)) => Ok(Query::Push(store, text_lexed)),
75            _ => Err(()),
76        }
77    }
78
79    pub fn pop(
80        collection: &'a str,
81        bucket: &'a str,
82        object: &'a str,
83        text: &'a str,
84    ) -> Result<Self, ()> {
85        match (
86            StoreItemBuilder::from_depth_3(collection, bucket, object),
87            TokenLexerBuilder::from(TokenLexerMode::NormalizeOnly, text),
88        ) {
89            (Ok(store), Ok(text_lexed)) => Ok(Query::Pop(store, text_lexed)),
90            _ => Err(()),
91        }
92    }
93
94    pub fn count(
95        collection: &'a str,
96        bucket: Option<&'a str>,
97        object: Option<&'a str>,
98    ) -> Result<Self, ()> {
99        let store_result = match (bucket, object) {
100            (Some(bucket_inner), Some(object_inner)) => {
101                StoreItemBuilder::from_depth_3(collection, bucket_inner, object_inner)
102            }
103            (Some(bucket_inner), None) => StoreItemBuilder::from_depth_2(collection, bucket_inner),
104            _ => StoreItemBuilder::from_depth_1(collection),
105        };
106
107        match store_result {
108            Ok(store) => Ok(Query::Count(store)),
109            _ => Err(()),
110        }
111    }
112
113    pub fn flushc(collection: &'a str) -> Result<Self, ()> {
114        match StoreItemBuilder::from_depth_1(collection) {
115            Ok(store) => Ok(Query::FlushC(store)),
116            _ => Err(()),
117        }
118    }
119
120    pub fn flushb(collection: &'a str, bucket: &'a str) -> Result<Self, ()> {
121        match StoreItemBuilder::from_depth_2(collection, bucket) {
122            Ok(store) => Ok(Query::FlushB(store)),
123            _ => Err(()),
124        }
125    }
126
127    pub fn flusho(collection: &'a str, bucket: &'a str, object: &'a str) -> Result<Self, ()> {
128        match StoreItemBuilder::from_depth_3(collection, bucket, object) {
129            Ok(store) => Ok(Query::FlushO(store)),
130            _ => Err(()),
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn it_builds_search_query() {
141        assert!(Query::search("id1", "c:test:1", "b:test:1", "Michael Dake", 10, 20, None).is_ok());
142        assert!(Query::search("id2", "c:test:1", "", "Michael Dake", 1, 0, None).is_err());
143    }
144
145    #[test]
146    fn it_builds_suggest_query() {
147        assert!(Query::suggest("id1", "c:test:2", "b:test:2", "Micha", 5).is_ok());
148        assert!(Query::suggest("id2", "c:test:2", "", "Micha", 1).is_err());
149    }
150
151    #[test]
152    fn it_builds_list_query() {
153        assert!(Query::list("id1", "c:test:2", "b:test:2", 100, 0).is_ok());
154        assert!(Query::list("id2", "c:test:2", "", 10, 0).is_err());
155    }
156
157    #[test]
158    fn it_builds_push_query() {
159        assert!(
160            Query::push(
161                "c:test:3",
162                "b:test:3",
163                "o:test:3",
164                "My name is Michael Dake. I'm ordering in the US.",
165                None
166            )
167            .is_ok()
168        );
169        assert!(Query::push("c:test:3", "", "o:test:3", "My name is Michael Dake.", None).is_err());
170    }
171
172    #[test]
173    fn it_builds_pop_query() {
174        assert!(Query::pop("c:test:4", "b:test:4", "o:test:4", "ordering US").is_ok());
175        assert!(Query::pop("c:test:4", "", "o:test:4", "ordering US").is_err());
176    }
177
178    #[test]
179    fn it_builds_count_query() {
180        assert!(Query::count("c:test:5", None, None).is_ok());
181        assert!(Query::count("c:test:5", Some("b:test:5"), None).is_ok());
182        assert!(Query::count("c:test:5", Some("b:test:5"), Some("o:test:5")).is_ok());
183        assert!(Query::count("c:test:5", Some(""), Some("o:test:5")).is_err());
184    }
185
186    #[test]
187    fn it_builds_flushc_query() {
188        assert!(Query::flushc("c:test:6").is_ok());
189        assert!(Query::flushc("").is_err());
190    }
191
192    #[test]
193    fn it_builds_flushb_query() {
194        assert!(Query::flushb("c:test:7", "b:test:7").is_ok());
195        assert!(Query::flushb("c:test:7", "").is_err());
196    }
197
198    #[test]
199    fn it_builds_flusho_query() {
200        assert!(Query::flusho("c:test:8", "b:test:8", "o:test:8").is_ok());
201        assert!(Query::flusho("c:test:8", "b:test:8", "").is_err());
202    }
203}