// Copyright 2018 Mozilla // // Licensed under the Apache License, Version 2.0 (the "License"); you may not use // this file except in compliance with the License. You may obtain a copy of the // License at http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #![macro_use] use std::collections::BTreeMap; pub use core_traits::{Binding, Entid, TypedValue, ValueType}; use mentat_core::{DateTime, Keyword, Utc}; use super::{HasSchema, QueryInputs, QueryOutput, Queryable, RelResult, Store, Variable}; use public_traits::errors::{MentatError, Result}; pub struct QueryBuilder<'a> { query: String, values: BTreeMap, types: BTreeMap, store: &'a mut Store, } impl<'a> QueryBuilder<'a> { pub fn new(store: &'a mut Store, query: T) -> QueryBuilder<'_> where T: Into, { QueryBuilder { query: query.into(), values: BTreeMap::new(), types: BTreeMap::new(), store, } } pub fn bind_value(&mut self, var: &str, value: T) -> &mut Self where T: Into, { self.values .insert(Variable::from_valid_name(var), value.into()); self } pub fn bind_ref_from_kw(&mut self, var: &str, value: Keyword) -> Result<&mut Self> { let entid = self .store .conn() .current_schema() .get_entid(&value) .ok_or_else(|| MentatError::UnknownAttribute(value.to_string()))?; self.values.insert( Variable::from_valid_name(var), TypedValue::Ref(entid.into()), ); Ok(self) } pub fn bind_ref(&mut self, var: &str, value: T) -> &mut Self where T: Into, { self.values.insert( Variable::from_valid_name(var), TypedValue::Ref(value.into()), ); self } pub fn bind_long(&mut self, var: &str, value: i64) -> &mut Self { self.values .insert(Variable::from_valid_name(var), TypedValue::Long(value)); self } pub fn bind_instant(&mut self, var: &str, value: i64) -> &mut Self { self.values .insert(Variable::from_valid_name(var), TypedValue::instant(value)); self } pub fn bind_date_time(&mut self, var: &str, value: DateTime) -> &mut Self { self.values .insert(Variable::from_valid_name(var), TypedValue::Instant(value)); self } pub fn bind_type(&mut self, var: &str, value_type: ValueType) -> &mut Self { self.types .insert(Variable::from_valid_name(var), value_type); self } pub fn execute(&mut self) -> Result { let values = ::std::mem::take(&mut self.values); let types = ::std::mem::take(&mut self.types); let query_inputs = QueryInputs::new(types, values)?; let read = self.store.begin_read()?; read.q_once(&self.query, query_inputs).map_err(|e| e) } pub fn execute_scalar(&mut self) -> Result> { let results = self.execute()?; results.into_scalar().map_err(|e| e.into()) } pub fn execute_coll(&mut self) -> Result> { let results = self.execute()?; results.into_coll().map_err(|e| e.into()) } pub fn execute_tuple(&mut self) -> Result>> { let results = self.execute()?; results.into_tuple().map_err(|e| e.into()) } pub fn execute_rel(&mut self) -> Result> { let results = self.execute()?; results.into_rel().map_err(|e| e.into()) } } #[cfg(test)] mod test { use super::{QueryBuilder, Store, TypedValue}; #[test] fn test_scalar_query() { let mut store = Store::open("").expect("store connection"); store .transact( r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] ]"#, ) .expect("successful transaction"); let report = store .transact( r#"[ [:db/add "u" :foo/boolean true] [:db/add "p" :foo/boolean false] ]"#, ) .expect("successful transaction"); let yes = *report.tempids.get("u").expect("found it"); let entid = QueryBuilder::new( &mut store, r#"[:find ?x . :in ?v :where [?x :foo/boolean ?v]]"#, ) .bind_value("?v", true) .execute_scalar() .expect("ScalarResult") .and_then(|t| t.into_entid()); assert_eq!(entid, Some(yes)); } #[test] fn test_coll_query() { let mut store = Store::open("").expect("store connection"); store .transact( r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] ]"#, ) .expect("successful transaction"); let report = store .transact( r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] [:db/add "m" :foo/long 26] [:db/add "n" :foo/boolean true] [:db/add "n" :foo/long 27] [:db/add "p" :foo/boolean false] [:db/add "p" :foo/long 24] [:db/add "u" :foo/boolean true] [:db/add "u" :foo/long 23] ]"#, ) .expect("successful transaction"); let u_yes = *report.tempids.get("u").expect("found it"); let l_yes = *report.tempids.get("l").expect("found it"); let n_yes = *report.tempids.get("n").expect("found it"); let entids: Vec = QueryBuilder::new( &mut store, r#"[:find [?x ...] :in ?v :where [?x :foo/boolean ?v]]"#, ) .bind_value("?v", true) .execute_coll() .expect("CollResult") .into_iter() .map(|v| v.into_entid().expect("val")) .collect(); assert_eq!(entids, vec![l_yes, n_yes, u_yes]); } #[test] fn test_coll_query_by_row() { let mut store = Store::open("").expect("store connection"); store .transact( r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] ]"#, ) .expect("successful transaction"); let report = store .transact( r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] [:db/add "m" :foo/long 26] [:db/add "n" :foo/boolean true] [:db/add "n" :foo/long 27] [:db/add "p" :foo/boolean false] [:db/add "p" :foo/long 24] [:db/add "u" :foo/boolean true] [:db/add "u" :foo/long 23] ]"#, ) .expect("successful transaction"); let n_yes = *report.tempids.get("n").expect("found it"); let results = QueryBuilder::new( &mut store, r#"[:find [?x ...] :in ?v :where [?x :foo/boolean ?v]]"#, ) .bind_value("?v", true) .execute_coll() .expect("CollResult"); let entid = results .get(1) .and_then(|t| t.to_owned().into_entid()) .expect("entid"); assert_eq!(entid, n_yes); } #[test] fn test_tuple_query_result_by_column() { let mut store = Store::open("").expect("store connection"); store .transact( r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] ]"#, ) .expect("successful transaction"); let report = store .transact( r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] [:db/add "m" :foo/long 26] [:db/add "n" :foo/boolean true] [:db/add "n" :foo/long 27] [:db/add "p" :foo/boolean false] [:db/add "p" :foo/long 24] [:db/add "u" :foo/boolean true] [:db/add "u" :foo/long 23] ]"#, ) .expect("successful transaction"); let n_yes = *report.tempids.get("n").expect("found it"); let results = QueryBuilder::new( &mut store, r#"[:find [?x, ?i] :in ?v ?i :where [?x :foo/boolean ?v] [?x :foo/long ?i]]"#, ) .bind_value("?v", true) .bind_long("?i", 27) .execute_tuple() .expect("TupleResult") .expect("Vec"); let entid = results .get(0) .and_then(|t| t.to_owned().into_entid()) .expect("entid"); let long_val = results .get(1) .and_then(|t| t.to_owned().into_long()) .expect("long"); assert_eq!(entid, n_yes); assert_eq!(long_val, 27); } #[test] fn test_tuple_query_result_by_iter() { let mut store = Store::open("").expect("store connection"); store .transact( r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] ]"#, ) .expect("successful transaction"); let report = store .transact( r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] [:db/add "m" :foo/long 26] [:db/add "n" :foo/boolean true] [:db/add "n" :foo/long 27] [:db/add "p" :foo/boolean false] [:db/add "p" :foo/long 24] [:db/add "u" :foo/boolean true] [:db/add "u" :foo/long 23] ]"#, ) .expect("successful transaction"); let n_yes = *report.tempids.get("n").expect("found it"); let results: Vec<_> = QueryBuilder::new( &mut store, r#"[:find [?x, ?i] :in ?v ?i :where [?x :foo/boolean ?v] [?x :foo/long ?i]]"#, ) .bind_value("?v", true) .bind_long("?i", 27) .execute_tuple() .expect("TupleResult") .unwrap_or_default(); let entid = TypedValue::Ref(n_yes).into(); let long_val = TypedValue::Long(27).into(); assert_eq!(results, vec![entid, long_val]); } #[test] fn test_rel_query_result() { let mut store = Store::open("").expect("store connection"); store .transact( r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] ]"#, ) .expect("successful transaction"); let report = store .transact( r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] [:db/add "m" :foo/long 26] [:db/add "n" :foo/boolean true] [:db/add "n" :foo/long 27] ]"#, ) .expect("successful transaction"); let l_yes = *report.tempids.get("l").expect("found it"); let m_yes = *report.tempids.get("m").expect("found it"); let n_yes = *report.tempids.get("n").expect("found it"); #[derive(Debug, PartialEq)] struct Res { entid: i64, boolean: bool, long_val: i64, }; let mut results: Vec = QueryBuilder::new( &mut store, r#"[:find ?x ?v ?i :where [?x :foo/boolean ?v] [?x :foo/long ?i]]"#, ) .execute_rel() .expect("RelResult") .into_iter() .map(|row| Res { entid: row .get(0) .and_then(|t| t.to_owned().into_entid()) .expect("entid"), boolean: row .get(1) .and_then(|t| t.to_owned().into_boolean()) .expect("boolean"), long_val: row .get(2) .and_then(|t| t.to_owned().into_long()) .expect("long"), }) .collect(); let res1 = results.pop().expect("res"); assert_eq!( res1, Res { entid: n_yes, boolean: true, long_val: 27 } ); let res2 = results.pop().expect("res"); assert_eq!( res2, Res { entid: m_yes, boolean: false, long_val: 26 } ); let res3 = results.pop().expect("res"); assert_eq!( res3, Res { entid: l_yes, boolean: true, long_val: 25 } ); assert_eq!(results.pop(), None); } #[test] fn test_bind_ref() { let mut store = Store::open("").expect("store connection"); store .transact( r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] ]"#, ) .expect("successful transaction"); let report = store .transact( r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] [:db/add "m" :foo/long 26] [:db/add "n" :foo/boolean true] [:db/add "n" :foo/long 27] ]"#, ) .expect("successful transaction"); let l_yes = *report.tempids.get("l").expect("found it"); let results = QueryBuilder::new( &mut store, r#"[:find [?v ?i] :in ?x :where [?x :foo/boolean ?v] [?x :foo/long ?i]]"#, ) .bind_ref("?x", l_yes) .execute_tuple() .expect("TupleResult") .unwrap_or_default(); assert_eq!( results .get(0) .and_then(|t| t.to_owned().into_boolean()) .expect("boolean"), true ); assert_eq!( results .get(1) .and_then(|t| t.to_owned().into_long()) .expect("long"), 25 ); } }