Skip to main content

mentat/
query_builder.rs

1// Copyright 2018 Mozilla
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4// this file except in compliance with the License. You may obtain a copy of the
5// License at http://www.apache.org/licenses/LICENSE-2.0
6// Unless required by applicable law or agreed to in writing, software distributed
7// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9// specific language governing permissions and limitations under the License.
10
11#![macro_use]
12use std::collections::BTreeMap;
13
14pub use core_traits::{Binding, Entid, TypedValue, ValueType};
15
16use mentat_core::{DateTime, Keyword, Utc};
17
18use super::{HasSchema, QueryInputs, QueryOutput, Queryable, RelResult, Store, Variable};
19
20use public_traits::errors::{MentatError, Result};
21
22pub struct QueryBuilder<'a> {
23    query: String,
24    values: BTreeMap<Variable, TypedValue>,
25    types: BTreeMap<Variable, ValueType>,
26    store: &'a mut Store,
27}
28
29impl<'a> QueryBuilder<'a> {
30    pub fn new<T>(store: &mut Store, query: T) -> QueryBuilder<'_>
31    where
32        T: Into<String>,
33    {
34        QueryBuilder {
35            query: query.into(),
36            values: BTreeMap::new(),
37            types: BTreeMap::new(),
38            store,
39        }
40    }
41
42    pub fn bind_value<T>(&mut self, var: &str, value: T) -> &mut Self
43    where
44        T: Into<TypedValue>,
45    {
46        self.values
47            .insert(Variable::from_valid_name(var), value.into());
48        self
49    }
50
51    pub fn bind_ref_from_kw(&mut self, var: &str, value: Keyword) -> Result<&mut Self> {
52        let entid = self
53            .store
54            .conn()
55            .current_schema()
56            .get_entid(&value)
57            .ok_or_else(|| MentatError::UnknownAttribute(value.to_string()))?;
58        self.values.insert(
59            Variable::from_valid_name(var),
60            TypedValue::Ref(entid.into()),
61        );
62        Ok(self)
63    }
64
65    pub fn bind_ref<T>(&mut self, var: &str, value: T) -> &mut Self
66    where
67        T: Into<Entid>,
68    {
69        self.values.insert(
70            Variable::from_valid_name(var),
71            TypedValue::Ref(value.into()),
72        );
73        self
74    }
75
76    pub fn bind_long(&mut self, var: &str, value: i64) -> &mut Self {
77        self.values
78            .insert(Variable::from_valid_name(var), TypedValue::Long(value));
79        self
80    }
81
82    pub fn bind_instant(&mut self, var: &str, value: i64) -> &mut Self {
83        self.values
84            .insert(Variable::from_valid_name(var), TypedValue::instant(value));
85
86        self
87    }
88
89    pub fn bind_date_time(&mut self, var: &str, value: DateTime<Utc>) -> &mut Self {
90        self.values
91            .insert(Variable::from_valid_name(var), TypedValue::Instant(value));
92        self
93    }
94
95    pub fn bind_type(&mut self, var: &str, value_type: ValueType) -> &mut Self {
96        self.types
97            .insert(Variable::from_valid_name(var), value_type);
98        self
99    }
100
101    pub fn execute(&mut self) -> Result<QueryOutput> {
102        let values = ::std::mem::take(&mut self.values);
103        let types = ::std::mem::take(&mut self.types);
104        let query_inputs = QueryInputs::new(types, values)?;
105        let read = self.store.begin_read()?;
106        read.q_once(&self.query, query_inputs)
107    }
108
109    pub fn execute_scalar(&mut self) -> Result<Option<Binding>> {
110        let results = self.execute()?;
111        results.into_scalar().map_err(|e| e.into())
112    }
113
114    pub fn execute_coll(&mut self) -> Result<Vec<Binding>> {
115        let results = self.execute()?;
116        results.into_coll().map_err(|e| e.into())
117    }
118
119    pub fn execute_tuple(&mut self) -> Result<Option<Vec<Binding>>> {
120        let results = self.execute()?;
121        results.into_tuple().map_err(|e| e.into())
122    }
123
124    pub fn execute_rel(&mut self) -> Result<RelResult<Binding>> {
125        let results = self.execute()?;
126        results.into_rel().map_err(|e| e.into())
127    }
128}
129
130#[cfg(test)]
131mod test {
132    use super::{QueryBuilder, Store, TypedValue};
133
134    // #225: the builder holds Datalog query text, named `query` (Mozilla renamed it from
135    // `sql` in 013629dec); the text runs as given.
136    #[test]
137    fn test_query_is_datalog_text() {
138        let mut store = Store::open("").expect("store connection");
139        let text = "[:find ?e . :where [?e :db/ident :db/ident]]";
140        let mut builder = QueryBuilder::new(&mut store, text);
141        assert_eq!(builder.query, text);
142        assert!(builder.execute_scalar().expect("scalar").is_some());
143    }
144
145    #[test]
146    fn test_scalar_query() {
147        let mut store = Store::open("").expect("store connection");
148        store
149            .transact(
150                r#"[
151            [:db/add "s" :db/ident :foo/boolean]
152            [:db/add "s" :db/valueType :db.type/boolean]
153            [:db/add "s" :db/cardinality :db.cardinality/one]
154        ]"#,
155            )
156            .expect("successful transaction");
157
158        let report = store
159            .transact(
160                r#"[
161            [:db/add "u" :foo/boolean true]
162            [:db/add "p" :foo/boolean false]
163        ]"#,
164            )
165            .expect("successful transaction");
166
167        let yes = *report.tempids.get("u").expect("found it");
168
169        let entid = QueryBuilder::new(
170            &mut store,
171            r#"[:find ?x .
172                                                      :in ?v
173                                                      :where [?x :foo/boolean ?v]]"#,
174        )
175        .bind_value("?v", true)
176        .execute_scalar()
177        .expect("ScalarResult")
178        .and_then(|t| t.into_entid());
179        assert_eq!(entid, Some(yes));
180    }
181
182    #[test]
183    fn test_coll_query() {
184        let mut store = Store::open("").expect("store connection");
185        store
186            .transact(
187                r#"[
188            [:db/add "s" :db/ident :foo/boolean]
189            [:db/add "s" :db/valueType :db.type/boolean]
190            [:db/add "s" :db/cardinality :db.cardinality/one]
191            [:db/add "t" :db/ident :foo/long]
192            [:db/add "t" :db/valueType :db.type/long]
193            [:db/add "t" :db/cardinality :db.cardinality/one]
194        ]"#,
195            )
196            .expect("successful transaction");
197
198        let report = store
199            .transact(
200                r#"[
201            [:db/add "l" :foo/boolean true]
202            [:db/add "l" :foo/long 25]
203            [:db/add "m" :foo/boolean false]
204            [:db/add "m" :foo/long 26]
205            [:db/add "n" :foo/boolean true]
206            [:db/add "n" :foo/long 27]
207            [:db/add "p" :foo/boolean false]
208            [:db/add "p" :foo/long 24]
209            [:db/add "u" :foo/boolean true]
210            [:db/add "u" :foo/long 23]
211        ]"#,
212            )
213            .expect("successful transaction");
214
215        let u_yes = *report.tempids.get("u").expect("found it");
216        let l_yes = *report.tempids.get("l").expect("found it");
217        let n_yes = *report.tempids.get("n").expect("found it");
218
219        let entids: Vec<i64> = QueryBuilder::new(
220            &mut store,
221            r#"[:find [?x ...]
222                                                                 :in ?v
223                                                                 :where [?x :foo/boolean ?v]]"#,
224        )
225        .bind_value("?v", true)
226        .execute_coll()
227        .expect("CollResult")
228        .into_iter()
229        .map(|v| v.into_entid().expect("val"))
230        .collect();
231
232        assert_eq!(entids, vec![l_yes, n_yes, u_yes]);
233    }
234
235    #[test]
236    fn test_coll_query_by_row() {
237        let mut store = Store::open("").expect("store connection");
238        store
239            .transact(
240                r#"[
241            [:db/add "s" :db/ident :foo/boolean]
242            [:db/add "s" :db/valueType :db.type/boolean]
243            [:db/add "s" :db/cardinality :db.cardinality/one]
244            [:db/add "t" :db/ident :foo/long]
245            [:db/add "t" :db/valueType :db.type/long]
246            [:db/add "t" :db/cardinality :db.cardinality/one]
247        ]"#,
248            )
249            .expect("successful transaction");
250
251        let report = store
252            .transact(
253                r#"[
254            [:db/add "l" :foo/boolean true]
255            [:db/add "l" :foo/long 25]
256            [:db/add "m" :foo/boolean false]
257            [:db/add "m" :foo/long 26]
258            [:db/add "n" :foo/boolean true]
259            [:db/add "n" :foo/long 27]
260            [:db/add "p" :foo/boolean false]
261            [:db/add "p" :foo/long 24]
262            [:db/add "u" :foo/boolean true]
263            [:db/add "u" :foo/long 23]
264        ]"#,
265            )
266            .expect("successful transaction");
267
268        let n_yes = *report.tempids.get("n").expect("found it");
269
270        let results = QueryBuilder::new(
271            &mut store,
272            r#"[:find [?x ...]
273                                                        :in ?v
274                                                        :where [?x :foo/boolean ?v]]"#,
275        )
276        .bind_value("?v", true)
277        .execute_coll()
278        .expect("CollResult");
279        let entid = results
280            .get(1)
281            .and_then(|t| t.to_owned().into_entid())
282            .expect("entid");
283
284        assert_eq!(entid, n_yes);
285    }
286
287    #[test]
288    fn test_tuple_query_result_by_column() {
289        let mut store = Store::open("").expect("store connection");
290        store
291            .transact(
292                r#"[
293            [:db/add "s" :db/ident :foo/boolean]
294            [:db/add "s" :db/valueType :db.type/boolean]
295            [:db/add "s" :db/cardinality :db.cardinality/one]
296            [:db/add "t" :db/ident :foo/long]
297            [:db/add "t" :db/valueType :db.type/long]
298            [:db/add "t" :db/cardinality :db.cardinality/one]
299        ]"#,
300            )
301            .expect("successful transaction");
302
303        let report = store
304            .transact(
305                r#"[
306            [:db/add "l" :foo/boolean true]
307            [:db/add "l" :foo/long 25]
308            [:db/add "m" :foo/boolean false]
309            [:db/add "m" :foo/long 26]
310            [:db/add "n" :foo/boolean true]
311            [:db/add "n" :foo/long 27]
312            [:db/add "p" :foo/boolean false]
313            [:db/add "p" :foo/long 24]
314            [:db/add "u" :foo/boolean true]
315            [:db/add "u" :foo/long 23]
316        ]"#,
317            )
318            .expect("successful transaction");
319
320        let n_yes = *report.tempids.get("n").expect("found it");
321
322        let results = QueryBuilder::new(
323            &mut store,
324            r#"[:find [?x, ?i]
325                                                        :in ?v ?i
326                                                        :where [?x :foo/boolean ?v]
327                                                               [?x :foo/long ?i]]"#,
328        )
329        .bind_value("?v", true)
330        .bind_long("?i", 27)
331        .execute_tuple()
332        .expect("TupleResult")
333        .expect("Vec<TypedValue>");
334        let entid = results
335            .first()
336            .and_then(|t| t.to_owned().into_entid())
337            .expect("entid");
338        let long_val = results
339            .get(1)
340            .and_then(|t| t.to_owned().into_long())
341            .expect("long");
342
343        assert_eq!(entid, n_yes);
344        assert_eq!(long_val, 27);
345    }
346
347    #[test]
348    fn test_tuple_query_result_by_iter() {
349        let mut store = Store::open("").expect("store connection");
350        store
351            .transact(
352                r#"[
353            [:db/add "s" :db/ident :foo/boolean]
354            [:db/add "s" :db/valueType :db.type/boolean]
355            [:db/add "s" :db/cardinality :db.cardinality/one]
356            [:db/add "t" :db/ident :foo/long]
357            [:db/add "t" :db/valueType :db.type/long]
358            [:db/add "t" :db/cardinality :db.cardinality/one]
359        ]"#,
360            )
361            .expect("successful transaction");
362
363        let report = store
364            .transact(
365                r#"[
366            [:db/add "l" :foo/boolean true]
367            [:db/add "l" :foo/long 25]
368            [:db/add "m" :foo/boolean false]
369            [:db/add "m" :foo/long 26]
370            [:db/add "n" :foo/boolean true]
371            [:db/add "n" :foo/long 27]
372            [:db/add "p" :foo/boolean false]
373            [:db/add "p" :foo/long 24]
374            [:db/add "u" :foo/boolean true]
375            [:db/add "u" :foo/long 23]
376        ]"#,
377            )
378            .expect("successful transaction");
379
380        let n_yes = *report.tempids.get("n").expect("found it");
381
382        let results: Vec<_> = QueryBuilder::new(
383            &mut store,
384            r#"[:find [?x, ?i]
385                                                                :in ?v ?i
386                                                                :where [?x :foo/boolean ?v]
387                                                                       [?x :foo/long ?i]]"#,
388        )
389        .bind_value("?v", true)
390        .bind_long("?i", 27)
391        .execute_tuple()
392        .expect("TupleResult")
393        .unwrap_or_default();
394        let entid = TypedValue::Ref(n_yes).into();
395        let long_val = TypedValue::Long(27).into();
396
397        assert_eq!(results, vec![entid, long_val]);
398    }
399
400    #[test]
401    fn test_rel_query_result() {
402        let mut store = Store::open("").expect("store connection");
403        store
404            .transact(
405                r#"[
406            [:db/add "s" :db/ident :foo/boolean]
407            [:db/add "s" :db/valueType :db.type/boolean]
408            [:db/add "s" :db/cardinality :db.cardinality/one]
409            [:db/add "t" :db/ident :foo/long]
410            [:db/add "t" :db/valueType :db.type/long]
411            [:db/add "t" :db/cardinality :db.cardinality/one]
412        ]"#,
413            )
414            .expect("successful transaction");
415
416        let report = store
417            .transact(
418                r#"[
419            [:db/add "l" :foo/boolean true]
420            [:db/add "l" :foo/long 25]
421            [:db/add "m" :foo/boolean false]
422            [:db/add "m" :foo/long 26]
423            [:db/add "n" :foo/boolean true]
424            [:db/add "n" :foo/long 27]
425        ]"#,
426            )
427            .expect("successful transaction");
428
429        let l_yes = *report.tempids.get("l").expect("found it");
430        let m_yes = *report.tempids.get("m").expect("found it");
431        let n_yes = *report.tempids.get("n").expect("found it");
432
433        #[derive(Debug, PartialEq)]
434        struct Res {
435            entid: i64,
436            boolean: bool,
437            long_val: i64,
438        }
439
440        let mut results: Vec<Res> = QueryBuilder::new(
441            &mut store,
442            r#"[:find ?x ?v ?i
443                                                                      :where [?x :foo/boolean ?v]
444                                                                             [?x :foo/long ?i]]"#,
445        )
446        .execute_rel()
447        .expect("RelResult")
448        .into_iter()
449        .map(|row| Res {
450            entid: row
451                .first()
452                .and_then(|t| t.to_owned().into_entid())
453                .expect("entid"),
454            boolean: row
455                .get(1)
456                .and_then(|t| t.to_owned().into_boolean())
457                .expect("boolean"),
458            long_val: row
459                .get(2)
460                .and_then(|t| t.to_owned().into_long())
461                .expect("long"),
462        })
463        .collect();
464
465        let res1 = results.pop().expect("res");
466        assert_eq!(
467            res1,
468            Res {
469                entid: n_yes,
470                boolean: true,
471                long_val: 27
472            }
473        );
474        let res2 = results.pop().expect("res");
475        assert_eq!(
476            res2,
477            Res {
478                entid: m_yes,
479                boolean: false,
480                long_val: 26
481            }
482        );
483        let res3 = results.pop().expect("res");
484        assert_eq!(
485            res3,
486            Res {
487                entid: l_yes,
488                boolean: true,
489                long_val: 25
490            }
491        );
492        assert_eq!(results.pop(), None);
493    }
494
495    #[test]
496    fn test_bind_ref() {
497        let mut store = Store::open("").expect("store connection");
498        store
499            .transact(
500                r#"[
501            [:db/add "s" :db/ident :foo/boolean]
502            [:db/add "s" :db/valueType :db.type/boolean]
503            [:db/add "s" :db/cardinality :db.cardinality/one]
504            [:db/add "t" :db/ident :foo/long]
505            [:db/add "t" :db/valueType :db.type/long]
506            [:db/add "t" :db/cardinality :db.cardinality/one]
507        ]"#,
508            )
509            .expect("successful transaction");
510
511        let report = store
512            .transact(
513                r#"[
514            [:db/add "l" :foo/boolean true]
515            [:db/add "l" :foo/long 25]
516            [:db/add "m" :foo/boolean false]
517            [:db/add "m" :foo/long 26]
518            [:db/add "n" :foo/boolean true]
519            [:db/add "n" :foo/long 27]
520        ]"#,
521            )
522            .expect("successful transaction");
523
524        let l_yes = *report.tempids.get("l").expect("found it");
525
526        let results = QueryBuilder::new(
527            &mut store,
528            r#"[:find [?v ?i]
529                                                        :in ?x
530                                                        :where [?x :foo/boolean ?v]
531                                                               [?x :foo/long ?i]]"#,
532        )
533        .bind_ref("?x", l_yes)
534        .execute_tuple()
535        .expect("TupleResult")
536        .unwrap_or_default();
537        assert!(results
538            .first()
539            .and_then(|t| t.to_owned().into_boolean())
540            .expect("boolean"));
541        assert_eq!(
542            results
543                .get(1)
544                .and_then(|t| t.to_owned().into_long())
545                .expect("long"),
546            25
547        );
548    }
549}