deta_rust/database/
query.rs

1//! Tools for defining the query to be used when fetching items from the database.
2
3use super::common::{JsonValue, StringValue};
4use serde::Serialize;
5use std::borrow::Borrow;
6use std::convert::Into;
7
8/// Enum specifying the variants of conditions to be useed when querying (fetching) the items.
9/// The type contains factory methods to facilitate the construction of variants.
10/// Check [deta docs](https://docs.deta.sh/docs/base/sdk#queries) for more information.
11pub enum Condition {
12    Equal(JsonValue),
13    NotEqual(JsonValue),
14    LessThan(f64),
15    GreaterThan(f64),
16    LessThanOrEqual(f64),
17    GreaterThatOrEqual(f64),
18    Prefix(StringValue),
19    Range(f64, f64),
20    Contains(StringValue),
21    NotContains(StringValue),
22}
23
24fn set_postfix(key: StringValue, postfix: &str) -> StringValue {
25    format!("{}?{}", key, postfix).into()
26}
27
28impl Condition {
29    fn gen_pair(self, key: StringValue) -> (StringValue, JsonValue) {
30        match self {
31            Self::Equal(val) => (key, val),
32            Self::NotEqual(val) => (set_postfix(key, "ne"), val),
33            Self::LessThan(val) => (set_postfix(key, "lt"), val.into()),
34            Self::GreaterThan(val) => (set_postfix(key, "gt"), val.into()),
35            Self::LessThanOrEqual(val) => (set_postfix(key, "lte"), val.into()),
36            Self::GreaterThatOrEqual(val) => (set_postfix(key, "gte"), val.into()),
37            Self::Prefix(val) => (set_postfix(key, "pfx"), val.into()),
38            Self::Range(val1, val2) => (set_postfix(key, "r"), serde_json::json!([val1, val2])),
39            Self::Contains(val) => (set_postfix(key, "contains"), val.into()),
40            Self::NotContains(val) => (set_postfix(key, "not_contains"), val.into()),
41        }
42    }
43}
44
45/// Factory methods.
46impl Condition {
47    pub fn equal<T>(value: T) -> serde_json::Result<Condition>
48    where
49        T: Serialize,
50    {
51        let json_val = serde_json::to_value(value)?;
52        Ok(Self::Equal(json_val))
53    }
54
55    pub fn not_equal<T>(value: T) -> serde_json::Result<Condition>
56    where
57        T: Serialize,
58    {
59        let json_val = serde_json::to_value(value)?;
60        Ok(Self::NotEqual(json_val))
61    }
62
63    pub fn less_than<T>(value: T) -> Condition
64    where
65        T: Into<f64>,
66    {
67        Self::LessThan(value.into())
68    }
69
70    pub fn greater_than<T>(value: T) -> Condition
71    where
72        T: Into<f64>,
73    {
74        Self::GreaterThan(value.into())
75    }
76
77    pub fn less_than_or_equal<T>(value: T) -> Condition
78    where
79        T: Into<f64>,
80    {
81        Self::LessThanOrEqual(value.into())
82    }
83
84    pub fn greater_than_or_equal<T>(value: T) -> Condition
85    where
86        T: Into<f64>,
87    {
88        Self::GreaterThatOrEqual(value.into())
89    }
90
91    pub fn prefix<T>(value: T) -> Condition
92    where
93        T: Into<StringValue>,
94    {
95        Self::Prefix(value.into())
96    }
97
98    pub fn range<T>(start: T, end: T) -> Condition
99    where
100        T: Into<f64>,
101    {
102        Self::Range(start.into(), end.into())
103    }
104
105    pub fn contains<T>(value: T) -> Condition
106    where
107        T: Into<StringValue>,
108    {
109        Self::Contains(value.into())
110    }
111
112    pub fn not_contains<T>(value: T) -> Condition
113    where
114        T: Into<StringValue>,
115    {
116        Self::NotContains(value.into())
117    }
118}
119
120/// Useful conversion to wrap an Condition type value to [`serde_json::Result`](serde_json::Result)
121/// for standardization purposes inside the `Query` type.
122impl From<Condition> for serde_json::Result<Condition> {
123    fn from(condition: Condition) -> serde_json::Result<Condition> {
124        Ok(condition)
125    }
126}
127
128/// Builder type to build a query to perform.
129pub struct Query {
130    // Each element in the list makes up an OR.
131    // A single element represents an AND expression.
132    conditions: Vec<Vec<(StringValue, serde_json::Result<Condition>)>>,
133}
134
135impl Query {
136    /// Initializes the builder.
137    pub fn init() -> Self {
138        Self { conditions: vec![] }
139    }
140
141    /// Adds a new condition that the item must satisfy.
142    pub fn on<K, V>(mut self, key: K, condition: V) -> Self
143    where
144        K: Into<StringValue>,
145        V: Into<serde_json::Result<Condition>>,
146    {
147        if let None = self.conditions.last() {
148            self.conditions.push(vec![]);
149        }
150        if let Some(and) = self.conditions.last_mut() {
151            and.push((key.into(), condition.into()));
152        }
153        self
154    }
155
156    /// Separates alternative conditions (or statement).
157    pub fn either(mut self) -> Self {
158        if let Some(and) = self.conditions.last_mut() {
159            if and.len() > 0 {
160                self.conditions.push(vec![]);
161            }
162        }
163        self
164    }
165
166    pub(crate) fn render(self) -> serde_json::Result<JsonValue> {
167        let mut target = vec![];
168        for condition in self.conditions {
169            let mut target_obj = serde_json::json!({});
170            for and in condition {
171                let (key, val_result) = and;
172                let val = val_result?;
173                let (key, val) = val.gen_pair(key);
174                let key: &str = key.borrow();
175                target_obj[key] = val;
176            }
177            target.push(target_obj);
178        }
179        serde_json::to_value(target)
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use serde::Serialize;
187
188    #[test]
189    fn render_for_all_condition_types() {
190        let query = Query::init()
191            .on("name", Condition::equal("Anna"))
192            .on("surname", Condition::not_equal("Kowal"))
193            .on("count", Condition::less_than(10))
194            .on("likes", Condition::greater_than(10))
195            .on("watchers", Condition::greater_than_or_equal(78))
196            .on("customers", Condition::less_than_or_equal(4))
197            .on("homepage", Condition::prefix("https"))
198            .on("age", Condition::range(23, 78))
199            .on("title", Condition::not_contains("car"))
200            .on("description", Condition::contains("Tom"))
201            .render()
202            .unwrap();
203
204        let target_query = serde_json::json!([
205            {
206                "name": "Anna",
207                "surname?ne": "Kowal",
208                "count?lt": 10.,
209                "likes?gt": 10.,
210                "watchers?gte": 78.,
211                "customers?lte": 4.,
212                "homepage?pfx": "https",
213                "age?r": [23., 78.],
214                "title?not_contains": "car",
215                "description?contains": "Tom"
216            },
217        ]);
218
219        assert_eq!(query, target_query);
220    }
221
222    #[test]
223    fn render_with_either_statements() {
224        let query = Query::init()
225            .on("age", Condition::greater_than(50))
226            .either()
227            .on("hometown", Condition::equal("Greenville"))
228            .render()
229            .unwrap();
230
231        let target_query = serde_json::json!([
232            {
233                "age?gt": 50.,
234            },
235            {
236                "hometown": "Greenville",
237            }
238        ]);
239
240        assert_eq!(query, target_query);
241    }
242
243    #[test]
244    fn render_with_redundant_either_statements() {
245        let query = Query::init()
246            .either()
247            .on("age", Condition::equal(15))
248            .either()
249            .either()
250            .on("name", Condition::not_contains("om"))
251            .either()
252            .either()
253            .either();
254
255        assert_eq!(query.conditions.len(), 3);
256
257        let query = query.render().unwrap();
258
259        let target_query = serde_json::json!([
260            {
261                "age": 15,
262            },
263            {
264                "name?not_contains": "om",
265            },
266            {}
267        ]);
268
269        assert_eq!(query, target_query);
270    }
271
272    #[test]
273    fn render_with_complex_obects() {
274        #[derive(Serialize)]
275        struct PersonalData {
276            name: &'static str,
277            age: u8,
278        }
279
280        let query = Query::init()
281            .on(
282                "personal_data",
283                Condition::equal(PersonalData {
284                    name: "Jan",
285                    age: 43,
286                }),
287            )
288            .either()
289            .on("personal_data.name", Condition::equal("Janina"))
290            .on("personal_data.age", Condition::equal(51))
291            .render()
292            .unwrap();
293
294        let target_query = serde_json::json!([
295            {
296                "personal_data": {
297                    "name": "Jan",
298                    "age": 43
299                },
300            },
301            {
302                "personal_data.name": "Janina",
303                "personal_data.age": 51,
304
305            },
306        ]);
307
308        assert_eq!(query, target_query);
309    }
310}