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]
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}