1#![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 #[test]
135 fn test_scalar_query() {
136 let mut store = Store::open("").expect("store connection");
137 store
138 .transact(
139 r#"[
140 [:db/add "s" :db/ident :foo/boolean]
141 [:db/add "s" :db/valueType :db.type/boolean]
142 [:db/add "s" :db/cardinality :db.cardinality/one]
143 ]"#,
144 )
145 .expect("successful transaction");
146
147 let report = store
148 .transact(
149 r#"[
150 [:db/add "u" :foo/boolean true]
151 [:db/add "p" :foo/boolean false]
152 ]"#,
153 )
154 .expect("successful transaction");
155
156 let yes = *report.tempids.get("u").expect("found it");
157
158 let entid = QueryBuilder::new(
159 &mut store,
160 r#"[:find ?x .
161 :in ?v
162 :where [?x :foo/boolean ?v]]"#,
163 )
164 .bind_value("?v", true)
165 .execute_scalar()
166 .expect("ScalarResult")
167 .and_then(|t| t.into_entid());
168 assert_eq!(entid, Some(yes));
169 }
170
171 #[test]
172 fn test_coll_query() {
173 let mut store = Store::open("").expect("store connection");
174 store
175 .transact(
176 r#"[
177 [:db/add "s" :db/ident :foo/boolean]
178 [:db/add "s" :db/valueType :db.type/boolean]
179 [:db/add "s" :db/cardinality :db.cardinality/one]
180 [:db/add "t" :db/ident :foo/long]
181 [:db/add "t" :db/valueType :db.type/long]
182 [:db/add "t" :db/cardinality :db.cardinality/one]
183 ]"#,
184 )
185 .expect("successful transaction");
186
187 let report = store
188 .transact(
189 r#"[
190 [:db/add "l" :foo/boolean true]
191 [:db/add "l" :foo/long 25]
192 [:db/add "m" :foo/boolean false]
193 [:db/add "m" :foo/long 26]
194 [:db/add "n" :foo/boolean true]
195 [:db/add "n" :foo/long 27]
196 [:db/add "p" :foo/boolean false]
197 [:db/add "p" :foo/long 24]
198 [:db/add "u" :foo/boolean true]
199 [:db/add "u" :foo/long 23]
200 ]"#,
201 )
202 .expect("successful transaction");
203
204 let u_yes = *report.tempids.get("u").expect("found it");
205 let l_yes = *report.tempids.get("l").expect("found it");
206 let n_yes = *report.tempids.get("n").expect("found it");
207
208 let entids: Vec<i64> = QueryBuilder::new(
209 &mut store,
210 r#"[:find [?x ...]
211 :in ?v
212 :where [?x :foo/boolean ?v]]"#,
213 )
214 .bind_value("?v", true)
215 .execute_coll()
216 .expect("CollResult")
217 .into_iter()
218 .map(|v| v.into_entid().expect("val"))
219 .collect();
220
221 assert_eq!(entids, vec![l_yes, n_yes, u_yes]);
222 }
223
224 #[test]
225 fn test_coll_query_by_row() {
226 let mut store = Store::open("").expect("store connection");
227 store
228 .transact(
229 r#"[
230 [:db/add "s" :db/ident :foo/boolean]
231 [:db/add "s" :db/valueType :db.type/boolean]
232 [:db/add "s" :db/cardinality :db.cardinality/one]
233 [:db/add "t" :db/ident :foo/long]
234 [:db/add "t" :db/valueType :db.type/long]
235 [:db/add "t" :db/cardinality :db.cardinality/one]
236 ]"#,
237 )
238 .expect("successful transaction");
239
240 let report = store
241 .transact(
242 r#"[
243 [:db/add "l" :foo/boolean true]
244 [:db/add "l" :foo/long 25]
245 [:db/add "m" :foo/boolean false]
246 [:db/add "m" :foo/long 26]
247 [:db/add "n" :foo/boolean true]
248 [:db/add "n" :foo/long 27]
249 [:db/add "p" :foo/boolean false]
250 [:db/add "p" :foo/long 24]
251 [:db/add "u" :foo/boolean true]
252 [:db/add "u" :foo/long 23]
253 ]"#,
254 )
255 .expect("successful transaction");
256
257 let n_yes = *report.tempids.get("n").expect("found it");
258
259 let results = QueryBuilder::new(
260 &mut store,
261 r#"[:find [?x ...]
262 :in ?v
263 :where [?x :foo/boolean ?v]]"#,
264 )
265 .bind_value("?v", true)
266 .execute_coll()
267 .expect("CollResult");
268 let entid = results
269 .get(1)
270 .and_then(|t| t.to_owned().into_entid())
271 .expect("entid");
272
273 assert_eq!(entid, n_yes);
274 }
275
276 #[test]
277 fn test_tuple_query_result_by_column() {
278 let mut store = Store::open("").expect("store connection");
279 store
280 .transact(
281 r#"[
282 [:db/add "s" :db/ident :foo/boolean]
283 [:db/add "s" :db/valueType :db.type/boolean]
284 [:db/add "s" :db/cardinality :db.cardinality/one]
285 [:db/add "t" :db/ident :foo/long]
286 [:db/add "t" :db/valueType :db.type/long]
287 [:db/add "t" :db/cardinality :db.cardinality/one]
288 ]"#,
289 )
290 .expect("successful transaction");
291
292 let report = store
293 .transact(
294 r#"[
295 [:db/add "l" :foo/boolean true]
296 [:db/add "l" :foo/long 25]
297 [:db/add "m" :foo/boolean false]
298 [:db/add "m" :foo/long 26]
299 [:db/add "n" :foo/boolean true]
300 [:db/add "n" :foo/long 27]
301 [:db/add "p" :foo/boolean false]
302 [:db/add "p" :foo/long 24]
303 [:db/add "u" :foo/boolean true]
304 [:db/add "u" :foo/long 23]
305 ]"#,
306 )
307 .expect("successful transaction");
308
309 let n_yes = *report.tempids.get("n").expect("found it");
310
311 let results = QueryBuilder::new(
312 &mut store,
313 r#"[:find [?x, ?i]
314 :in ?v ?i
315 :where [?x :foo/boolean ?v]
316 [?x :foo/long ?i]]"#,
317 )
318 .bind_value("?v", true)
319 .bind_long("?i", 27)
320 .execute_tuple()
321 .expect("TupleResult")
322 .expect("Vec<TypedValue>");
323 let entid = results
324 .first()
325 .and_then(|t| t.to_owned().into_entid())
326 .expect("entid");
327 let long_val = results
328 .get(1)
329 .and_then(|t| t.to_owned().into_long())
330 .expect("long");
331
332 assert_eq!(entid, n_yes);
333 assert_eq!(long_val, 27);
334 }
335
336 #[test]
337 fn test_tuple_query_result_by_iter() {
338 let mut store = Store::open("").expect("store connection");
339 store
340 .transact(
341 r#"[
342 [:db/add "s" :db/ident :foo/boolean]
343 [:db/add "s" :db/valueType :db.type/boolean]
344 [:db/add "s" :db/cardinality :db.cardinality/one]
345 [:db/add "t" :db/ident :foo/long]
346 [:db/add "t" :db/valueType :db.type/long]
347 [:db/add "t" :db/cardinality :db.cardinality/one]
348 ]"#,
349 )
350 .expect("successful transaction");
351
352 let report = store
353 .transact(
354 r#"[
355 [:db/add "l" :foo/boolean true]
356 [:db/add "l" :foo/long 25]
357 [:db/add "m" :foo/boolean false]
358 [:db/add "m" :foo/long 26]
359 [:db/add "n" :foo/boolean true]
360 [:db/add "n" :foo/long 27]
361 [:db/add "p" :foo/boolean false]
362 [:db/add "p" :foo/long 24]
363 [:db/add "u" :foo/boolean true]
364 [:db/add "u" :foo/long 23]
365 ]"#,
366 )
367 .expect("successful transaction");
368
369 let n_yes = *report.tempids.get("n").expect("found it");
370
371 let results: Vec<_> = QueryBuilder::new(
372 &mut store,
373 r#"[:find [?x, ?i]
374 :in ?v ?i
375 :where [?x :foo/boolean ?v]
376 [?x :foo/long ?i]]"#,
377 )
378 .bind_value("?v", true)
379 .bind_long("?i", 27)
380 .execute_tuple()
381 .expect("TupleResult")
382 .unwrap_or_default();
383 let entid = TypedValue::Ref(n_yes).into();
384 let long_val = TypedValue::Long(27).into();
385
386 assert_eq!(results, vec![entid, long_val]);
387 }
388
389 #[test]
390 fn test_rel_query_result() {
391 let mut store = Store::open("").expect("store connection");
392 store
393 .transact(
394 r#"[
395 [:db/add "s" :db/ident :foo/boolean]
396 [:db/add "s" :db/valueType :db.type/boolean]
397 [:db/add "s" :db/cardinality :db.cardinality/one]
398 [:db/add "t" :db/ident :foo/long]
399 [:db/add "t" :db/valueType :db.type/long]
400 [:db/add "t" :db/cardinality :db.cardinality/one]
401 ]"#,
402 )
403 .expect("successful transaction");
404
405 let report = store
406 .transact(
407 r#"[
408 [:db/add "l" :foo/boolean true]
409 [:db/add "l" :foo/long 25]
410 [:db/add "m" :foo/boolean false]
411 [:db/add "m" :foo/long 26]
412 [:db/add "n" :foo/boolean true]
413 [:db/add "n" :foo/long 27]
414 ]"#,
415 )
416 .expect("successful transaction");
417
418 let l_yes = *report.tempids.get("l").expect("found it");
419 let m_yes = *report.tempids.get("m").expect("found it");
420 let n_yes = *report.tempids.get("n").expect("found it");
421
422 #[derive(Debug, PartialEq)]
423 struct Res {
424 entid: i64,
425 boolean: bool,
426 long_val: i64,
427 }
428
429 let mut results: Vec<Res> = QueryBuilder::new(
430 &mut store,
431 r#"[:find ?x ?v ?i
432 :where [?x :foo/boolean ?v]
433 [?x :foo/long ?i]]"#,
434 )
435 .execute_rel()
436 .expect("RelResult")
437 .into_iter()
438 .map(|row| Res {
439 entid: row
440 .first()
441 .and_then(|t| t.to_owned().into_entid())
442 .expect("entid"),
443 boolean: row
444 .get(1)
445 .and_then(|t| t.to_owned().into_boolean())
446 .expect("boolean"),
447 long_val: row
448 .get(2)
449 .and_then(|t| t.to_owned().into_long())
450 .expect("long"),
451 })
452 .collect();
453
454 let res1 = results.pop().expect("res");
455 assert_eq!(
456 res1,
457 Res {
458 entid: n_yes,
459 boolean: true,
460 long_val: 27
461 }
462 );
463 let res2 = results.pop().expect("res");
464 assert_eq!(
465 res2,
466 Res {
467 entid: m_yes,
468 boolean: false,
469 long_val: 26
470 }
471 );
472 let res3 = results.pop().expect("res");
473 assert_eq!(
474 res3,
475 Res {
476 entid: l_yes,
477 boolean: true,
478 long_val: 25
479 }
480 );
481 assert_eq!(results.pop(), None);
482 }
483
484 #[test]
485 fn test_bind_ref() {
486 let mut store = Store::open("").expect("store connection");
487 store
488 .transact(
489 r#"[
490 [:db/add "s" :db/ident :foo/boolean]
491 [:db/add "s" :db/valueType :db.type/boolean]
492 [:db/add "s" :db/cardinality :db.cardinality/one]
493 [:db/add "t" :db/ident :foo/long]
494 [:db/add "t" :db/valueType :db.type/long]
495 [:db/add "t" :db/cardinality :db.cardinality/one]
496 ]"#,
497 )
498 .expect("successful transaction");
499
500 let report = store
501 .transact(
502 r#"[
503 [:db/add "l" :foo/boolean true]
504 [:db/add "l" :foo/long 25]
505 [:db/add "m" :foo/boolean false]
506 [:db/add "m" :foo/long 26]
507 [:db/add "n" :foo/boolean true]
508 [:db/add "n" :foo/long 27]
509 ]"#,
510 )
511 .expect("successful transaction");
512
513 let l_yes = *report.tempids.get("l").expect("found it");
514
515 let results = QueryBuilder::new(
516 &mut store,
517 r#"[:find [?v ?i]
518 :in ?x
519 :where [?x :foo/boolean ?v]
520 [?x :foo/long ?i]]"#,
521 )
522 .bind_ref("?x", l_yes)
523 .execute_tuple()
524 .expect("TupleResult")
525 .unwrap_or_default();
526 assert!(results
527 .first()
528 .and_then(|t| t.to_owned().into_boolean())
529 .expect("boolean"));
530 assert_eq!(
531 results
532 .get(1)
533 .and_then(|t| t.to_owned().into_long())
534 .expect("long"),
535 25
536 );
537 }
538}