diff --git a/query-algebrizer/Cargo.toml b/query-algebrizer/Cargo.toml index b1dcf062..d3312702 100644 --- a/query-algebrizer/Cargo.toml +++ b/query-algebrizer/Cargo.toml @@ -10,6 +10,9 @@ error-chain = "0.8.1" [dependencies.mentat_core] path = "../core" +[dependencies.mentat_db] +path = "../db" + [dependencies.mentat_query] path = "../query" diff --git a/query-algebrizer/src/clauses/mod.rs b/query-algebrizer/src/clauses/mod.rs index 7ee93032..6b30e188 100644 --- a/query-algebrizer/src/clauses/mod.rs +++ b/query-algebrizer/src/clauses/mod.rs @@ -32,6 +32,8 @@ use mentat_core::{ use mentat_core::counter::RcCounter; +use mentat_db::PartitionMap; + use mentat_query::{ NamespacedKeyword, NonIntegerConstant, @@ -643,6 +645,11 @@ impl ConjoiningClauses { schema.get_entid(&ident) } + fn valid_tx_entid<'p>(&self, partition_map: &'p PartitionMap, entid: Entid) -> bool { + let partition = partition_map.get(":db.part/tx").expect("transaction id range"); + partition.contains_entid(entid) + } + fn table_for_attribute_and_value<'s, 'a>(&self, attribute: &'s Attribute, value: &'a PatternValuePlace) -> ::std::result::Result { if attribute.fulltext { match value { @@ -862,10 +869,10 @@ impl ConjoiningClauses { impl ConjoiningClauses { // This is here, rather than in `lib.rs`, because it's recursive: `or` can contain `or`, // and so on. - pub fn apply_clause(&mut self, schema: &Schema, where_clause: WhereClause) -> Result<()> { + pub fn apply_clause(&mut self, schema: &Schema, partition_map: &PartitionMap, where_clause: WhereClause) -> Result<()> { match where_clause { WhereClause::Pattern(p) => { - self.apply_pattern(schema, p); + self.apply_pattern(schema, partition_map, p); Ok(()) }, WhereClause::Pred(p) => { @@ -876,11 +883,11 @@ impl ConjoiningClauses { }, WhereClause::OrJoin(o) => { validate_or_join(&o)?; - self.apply_or_join(schema, o) + self.apply_or_join(schema, partition_map, o) }, WhereClause::NotJoin(n) => { validate_not_join(&n)?; - self.apply_not_join(schema, n) + self.apply_not_join(schema, partition_map, n) }, _ => unimplemented!(), } diff --git a/query-algebrizer/src/clauses/not.rs b/query-algebrizer/src/clauses/not.rs index 308cf28a..2b9770c8 100644 --- a/query-algebrizer/src/clauses/not.rs +++ b/query-algebrizer/src/clauses/not.rs @@ -9,6 +9,7 @@ // specific language governing permissions and limitations under the License. use mentat_core::Schema; +use mentat_db::PartitionMap; use mentat_query::{ ContainsVariables, @@ -29,7 +30,7 @@ use types::{ }; impl ConjoiningClauses { - pub fn apply_not_join(&mut self, schema: &Schema, not_join: NotJoin) -> Result<()> { + pub fn apply_not_join(&mut self, schema: &Schema, partition_map: &PartitionMap, not_join: NotJoin) -> Result<()> { let unified = match not_join.unify_vars { UnifyVars::Implicit => not_join.collect_mentioned_variables(), UnifyVars::Explicit(vs) => vs, @@ -50,7 +51,7 @@ impl ConjoiningClauses { } for clause in not_join.clauses.into_iter() { - template.apply_clause(&schema, clause)?; + template.apply_clause(&schema, &partition_map, clause)?; } if template.is_known_empty() { @@ -121,14 +122,14 @@ mod testing { algebrize_with_inputs, }; - fn alg(schema: &Schema, input: &str) -> ConjoiningClauses { + fn alg(schema: &Schema, partition_map: &PartitionMap, input: &str) -> ConjoiningClauses { let parsed = parse_find_string(input).expect("parse failed"); - algebrize(schema.into(), parsed).expect("algebrize failed").cc + algebrize(schema.into(), partition_map, parsed).expect("algebrize failed").cc } - fn alg_with_inputs(schema: &Schema, input: &str, inputs: QueryInputs) -> ConjoiningClauses { + fn alg_with_inputs(schema: &Schema, partition_map: &PartitionMap, input: &str, inputs: QueryInputs) -> ConjoiningClauses { let parsed = parse_find_string(input).expect("parse failed"); - algebrize_with_inputs(schema.into(), parsed, 0, inputs).expect("algebrize failed").cc + algebrize_with_inputs(schema.into(), partition_map, parsed, 0, inputs).expect("algebrize failed").cc } fn prepopulated_schema() -> Schema { @@ -185,12 +186,13 @@ mod testing { #[test] fn test_successful_not() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x :where [?x :foo/knows "John"] (not [?x :foo/parent "Ámbar"] [?x :foo/knows "Daphne"])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); let vx = Variable::from_valid_name("?x"); @@ -243,6 +245,7 @@ mod testing { #[test] fn test_successful_not_join() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x :where [?x :foo/knows ?y] @@ -250,7 +253,7 @@ mod testing { [?x :foo/name "John"] (not-join [?x ?y] [?x :foo/parent ?y])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); let vx = Variable::from_valid_name("?x"); let vy = Variable::from_valid_name("?y"); @@ -316,6 +319,7 @@ mod testing { #[test] fn test_not_with_pattern_and_predicate() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x ?age :where @@ -323,7 +327,7 @@ mod testing { [[< ?age 30]] (not [?x :foo/knows "John"] [?x :foo/knows "Daphne"])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); let vx = Variable::from_valid_name("?x"); @@ -379,13 +383,14 @@ mod testing { #[test] fn test_not_with_or() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x :where [?x :foo/knows "Bill"] (not (or [?x :foo/knows "John"] [?x :foo/knows "Ámbar"]) [?x :foo/parent "Daphne"])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); let d0 = "datoms00".to_string(); let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity); @@ -444,6 +449,7 @@ mod testing { #[test] fn test_not_with_in() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x :in ?y @@ -451,7 +457,7 @@ mod testing { (not [?x :foo/knows ?y])]"#; let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?y"),TypedValue::String(Rc::new("John".to_string())))]); - let cc = alg_with_inputs(&schema, query, inputs); + let cc = alg_with_inputs(&schema, &partition_map, query, inputs); let vx = Variable::from_valid_name("?x"); let vy = Variable::from_valid_name("?y"); @@ -498,16 +504,18 @@ mod testing { #[test] fn test_fails_if_any_clause_invalid() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x :where [?x :foo/knows "Bill"] (not [?x :foo/nope "John"] [?x :foo/parent "Ámbar"] [?x :foo/nope "Daphne"])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); assert!(!cc.is_known_empty()); compare_ccs(cc, alg(&schema, + &partition_map, r#"[:find ?x :where [?x :foo/knows "Bill"]]"#)); } @@ -515,27 +523,29 @@ mod testing { #[test] fn test_no_clauses_succeed() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x :where [?x :foo/knows "John"] (not [?x :foo/nope "Ámbar"] [?x :foo/nope "Daphne"])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); assert!(!cc.is_known_empty()); compare_ccs(cc, - alg(&schema, r#"[:find ?x :where [?x :foo/knows "John"]]"#)); + alg(&schema, &partition_map, r#"[:find ?x :where [?x :foo/knows "John"]]"#)); } #[test] fn test_unbound_var_fails() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x :in ?y :where (not [?x :foo/knows ?y])]"#; let parsed = parse_find_string(query).expect("parse failed"); - let err = algebrize(&schema, parsed).err(); + let err = algebrize(&schema, &partition_map, parsed).err(); assert!(err.is_some()); match err.unwrap() { Error(ErrorKind::UnboundVariable(var), _) => { assert_eq!(var, PlainSymbol("?x".to_string())); }, diff --git a/query-algebrizer/src/clauses/or.rs b/query-algebrizer/src/clauses/or.rs index ed1cc239..7bdde9d7 100644 --- a/query-algebrizer/src/clauses/or.rs +++ b/query-algebrizer/src/clauses/or.rs @@ -18,6 +18,8 @@ use mentat_core::{ Schema, }; +use mentat_db::PartitionMap; + use mentat_query::{ OrJoin, OrWhereClause, @@ -88,23 +90,23 @@ pub enum DeconstructedOrJoin { /// Application of `or`. Note that this is recursive! impl ConjoiningClauses { - fn apply_or_where_clause(&mut self, schema: &Schema, clause: OrWhereClause) -> Result<()> { + fn apply_or_where_clause(&mut self, schema: &Schema, partition_map: &PartitionMap, clause: OrWhereClause) -> Result<()> { match clause { - OrWhereClause::Clause(clause) => self.apply_clause(schema, clause), + OrWhereClause::Clause(clause) => self.apply_clause(schema, partition_map, clause), // A query might be: // [:find ?x :where (or (and [?x _ 5] [?x :foo/bar 7]))] // which is equivalent to dropping the `or` _and_ the `and`! OrWhereClause::And(clauses) => { for clause in clauses { - self.apply_clause(schema, clause)?; + self.apply_clause(schema, partition_map, clause)?; } Ok(()) }, } } - pub fn apply_or_join(&mut self, schema: &Schema, mut or_join: OrJoin) -> Result<()> { + pub fn apply_or_join(&mut self, schema: &Schema, partition_map: &PartitionMap, mut or_join: OrJoin) -> Result<()> { // Simple optimization. Empty `or` clauses disappear. Unit `or` clauses // are equivalent to just the inner clause. @@ -115,7 +117,7 @@ impl ConjoiningClauses { 0 => Ok(()), 1 if or_join.is_fully_unified() => { let clause = or_join.clauses.pop().expect("there's a clause"); - self.apply_or_where_clause(schema, clause) + self.apply_or_where_clause(schema, partition_map, clause) }, // Either there's only one clause pattern, and it's not fully unified, or we // have multiple clauses. @@ -124,7 +126,7 @@ impl ConjoiningClauses { // Notably, this clause might be an `and`, making this a complex pattern, so we can't // necessarily rewrite it in place. // In the latter case, we still need to do a bit more work. - _ => self.apply_non_trivial_or_join(schema, or_join), + _ => self.apply_non_trivial_or_join(schema, partition_map, or_join), } } @@ -292,7 +294,7 @@ impl ConjoiningClauses { } } - fn apply_non_trivial_or_join(&mut self, schema: &Schema, or_join: OrJoin) -> Result<()> { + fn apply_non_trivial_or_join(&mut self, schema: &Schema, partition_map: &PartitionMap, or_join: OrJoin) -> Result<()> { match self.deconstruct_or_join(schema, or_join) { DeconstructedOrJoin::KnownSuccess => { // The pattern came to us empty -- `(or)`. Do nothing. @@ -306,22 +308,22 @@ impl ConjoiningClauses { }, DeconstructedOrJoin::Unit(clause) => { // There was only one clause. We're unifying all variables, so we can just apply here. - self.apply_or_where_clause(schema, clause) + self.apply_or_where_clause(schema, partition_map, clause) }, DeconstructedOrJoin::UnitPattern(pattern) => { // Same, but simpler. - self.apply_pattern(schema, pattern); + self.apply_pattern(schema, partition_map, pattern); Ok(()) }, DeconstructedOrJoin::Simple(patterns, mentioned_vars) => { // Hooray! Fully unified and plain ol' patterns that all use the same table. // Go right ahead and produce a set of constraint alternations that we can collect, // using a single table alias. - self.apply_simple_or_join(schema, patterns, mentioned_vars) + self.apply_simple_or_join(schema, partition_map, patterns, mentioned_vars) }, DeconstructedOrJoin::Complex(or_join) => { // Do this the hard way. - self.apply_complex_or_join(schema, or_join) + self.apply_complex_or_join(schema, partition_map, or_join) }, } } @@ -356,6 +358,7 @@ impl ConjoiningClauses { /// fn apply_simple_or_join(&mut self, schema: &Schema, + partition_map: &PartitionMap, patterns: Vec, mentioned_vars: BTreeSet) -> Result<()> { @@ -407,7 +410,7 @@ impl ConjoiningClauses { .map(|pattern| { let mut receptacle = template.make_receptacle(); println!("Applying pattern with attribute {:?}", pattern.attribute); - receptacle.apply_pattern_clause_for_alias(schema, &pattern, &source_alias); + receptacle.apply_pattern_clause_for_alias(schema, partition_map, &pattern, &source_alias); receptacle }) .peekable(); @@ -545,7 +548,7 @@ impl ConjoiningClauses { /// /// Note that a top-level standalone `or` doesn't really need to be aliased, but /// it shouldn't do any harm. - fn apply_complex_or_join(&mut self, schema: &Schema, or_join: OrJoin) -> Result<()> { + fn apply_complex_or_join(&mut self, schema: &Schema, partition_map: &PartitionMap, or_join: OrJoin) -> Result<()> { // N.B., a solitary pattern here *cannot* be simply applied to the enclosing CC. We don't // want to join all the vars, and indeed if it were safe to do so, we wouldn't have ended up // in this function! @@ -565,11 +568,11 @@ impl ConjoiningClauses { match clause { OrWhereClause::And(clauses) => { for clause in clauses { - receptacle.apply_clause(&schema, clause)?; + receptacle.apply_clause(&schema, &partition_map, clause)?; } }, OrWhereClause::Clause(clause) => { - receptacle.apply_clause(&schema, clause)?; + receptacle.apply_clause(&schema, &partition_map, clause)?; }, } if receptacle.is_known_empty() { @@ -762,16 +765,16 @@ mod testing { algebrize_with_counter, }; - fn alg(schema: &Schema, input: &str) -> ConjoiningClauses { + fn alg(schema: &Schema, partition_map: &PartitionMap, input: &str) -> ConjoiningClauses { let parsed = parse_find_string(input).expect("parse failed"); - algebrize(schema.into(), parsed).expect("algebrize failed").cc + algebrize(schema.into(), partition_map, parsed).expect("algebrize failed").cc } /// Algebrize with a starting counter, so we can compare inner queries by algebrizing a /// simpler version. - fn alg_c(schema: &Schema, counter: usize, input: &str) -> ConjoiningClauses { + fn alg_c(schema: &Schema, partition_map: &PartitionMap, counter: usize, input: &str) -> ConjoiningClauses { let parsed = parse_find_string(input).expect("parse failed"); - algebrize_with_counter(schema.into(), parsed, counter).expect("algebrize failed").cc + algebrize_with_counter(schema.into(), partition_map, parsed, counter).expect("algebrize failed").cc } fn compare_ccs(left: ConjoiningClauses, right: ConjoiningClauses) { @@ -818,12 +821,13 @@ mod testing { #[test] fn test_schema_based_failure() { let schema = Schema::default(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x :where (or [?x :foo/nope1 "John"] [?x :foo/nope2 "Ámbar"] [?x :foo/nope3 "Daphne"])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); assert!(cc.is_known_empty()); assert_eq!(cc.empty_because, Some(EmptyBecause::InvalidAttributeIdent(NamespacedKeyword::new("foo", "nope3")))); } @@ -832,26 +836,28 @@ mod testing { #[test] fn test_only_one_arm_succeeds() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x :where (or [?x :foo/nope "John"] [?x :foo/parent "Ámbar"] [?x :foo/nope "Daphne"])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); assert!(!cc.is_known_empty()); - compare_ccs(cc, alg(&schema, r#"[:find ?x :where [?x :foo/parent "Ámbar"]]"#)); + compare_ccs(cc, alg(&schema, &partition_map, r#"[:find ?x :where [?x :foo/parent "Ámbar"]]"#)); } // Simple alternation. #[test] fn test_simple_alternation() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x :where (or [?x :foo/knows "John"] [?x :foo/parent "Ámbar"] [?x :foo/knows "Daphne"])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); let vx = Variable::from_valid_name("?x"); let d0 = "datoms00".to_string(); let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity); @@ -885,6 +891,7 @@ mod testing { #[test] fn test_alternation_with_pattern() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find [?x ?name] :where @@ -892,7 +899,7 @@ mod testing { (or [?x :foo/knows "John"] [?x :foo/parent "Ámbar"] [?x :foo/knows "Daphne"])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); let vx = Variable::from_valid_name("?x"); let d0 = "datoms00".to_string(); let d1 = "datoms01".to_string(); @@ -935,6 +942,7 @@ mod testing { #[test] fn test_alternation_with_pattern_and_predicate() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x ?age :where @@ -942,7 +950,7 @@ mod testing { [[< ?age 30]] (or [?x :foo/knows "John"] [?x :foo/knows "Daphne"])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); let vx = Variable::from_valid_name("?x"); let d0 = "datoms00".to_string(); let d1 = "datoms01".to_string(); @@ -988,10 +996,11 @@ mod testing { #[test] fn test_unit_or_join_doesnt_flatten() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#"[:find ?x :where [?x :foo/knows ?y] (or-join [?x] [?x :foo/parent ?y])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); let vx = Variable::from_valid_name("?x"); let vy = Variable::from_valid_name("?y"); let d0 = "datoms00".to_string(); @@ -1023,28 +1032,30 @@ mod testing { #[test] fn test_unit_or_does_flatten() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let or_query = r#"[:find ?x :where [?x :foo/knows ?y] (or [?x :foo/parent ?y])]"#; let flat_query = r#"[:find ?x :where [?x :foo/knows ?y] [?x :foo/parent ?y]]"#; - compare_ccs(alg(&schema, or_query), - alg(&schema, flat_query)); + compare_ccs(alg(&schema, &partition_map, or_query), + alg(&schema, &partition_map, flat_query)); } // Elision of `and`. #[test] fn test_unit_or_and_does_flatten() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let or_query = r#"[:find ?x :where (or (and [?x :foo/parent ?y] [?x :foo/age 7]))]"#; let flat_query = r#"[:find ?x :where [?x :foo/parent ?y] [?x :foo/age 7]]"#; - compare_ccs(alg(&schema, or_query), - alg(&schema, flat_query)); + compare_ccs(alg(&schema, &partition_map, or_query), + alg(&schema, &partition_map, flat_query)); } // Alternation with `and`. @@ -1057,12 +1068,13 @@ mod testing { #[test] fn test_alternation_with_and() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let query = r#" [:find ?x :where (or (and [?x :foo/knows "John"] [?x :foo/parent "Ámbar"]) [?x :foo/knows "Daphne"])]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); let mut tables = cc.computed_tables.into_iter(); match (tables.next(), tables.next()) { (Some(ComputedTable::Union { projection, type_extraction, arms }), None) => { @@ -1073,11 +1085,13 @@ mod testing { match (arms.next(), arms.next(), arms.next()) { (Some(and), Some(pattern), None) => { let expected_and = alg_c(&schema, + &partition_map, 0, // The first pattern to be processed. r#"[:find ?x :where [?x :foo/knows "John"] [?x :foo/parent "Ámbar"]]"#); compare_ccs(and, expected_and); let expected_pattern = alg_c(&schema, + &partition_map, 2, // Two aliases taken by the other arm. r#"[:find ?x :where [?x :foo/knows "Daphne"]]"#); compare_ccs(pattern, expected_pattern); @@ -1096,6 +1110,7 @@ mod testing { #[test] fn test_type_based_or_pruning() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); // This simplifies to: // [:find ?x // :where [?a :some/int ?x] @@ -1109,6 +1124,6 @@ mod testing { [:find ?x :where [?a :foo/age ?x] [_ :foo/height ?x]]"#; - compare_ccs(alg(&schema, query), alg(&schema, simple)); + compare_ccs(alg(&schema, &partition_map, query), alg(&schema, &partition_map, simple)); } } diff --git a/query-algebrizer/src/clauses/pattern.rs b/query-algebrizer/src/clauses/pattern.rs index 41d09024..77c97f3f 100644 --- a/query-algebrizer/src/clauses/pattern.rs +++ b/query-algebrizer/src/clauses/pattern.rs @@ -14,6 +14,8 @@ use mentat_core::{ ValueType, }; +use mentat_db::PartitionMap; + use mentat_query::{ Pattern, PatternValuePlace, @@ -70,7 +72,7 @@ impl ConjoiningClauses { /// existence subquery instead of a join. /// /// This method is only public for use from `or.rs`. - pub fn apply_pattern_clause_for_alias<'s>(&mut self, schema: &'s Schema, pattern: &Pattern, alias: &SourceAlias) { + pub fn apply_pattern_clause_for_alias<'s, 'p>(&mut self, schema: &'s Schema, partition_map: &'p PartitionMap, pattern: &Pattern, alias: &SourceAlias) { if self.is_known_empty() { return; } @@ -247,14 +249,23 @@ impl ConjoiningClauses { self.bind_column_to_var(schema, col.clone(), DatomsColumn::Tx, v.clone()); }, PatternNonValuePlace::Entid(entid) => { - // TODO: we want to check whether the tx-id is within range for the database's tx partition. - // (That applies after ident lookup, too.) - // Possible solution: https://github.com/mozilla/mentat/tree/fluffyemily/tx-id-check - self.constrain_column_to_entity(col.clone(), DatomsColumn::Tx, entid); + if self.valid_tx_entid(partition_map, entid) { + self.constrain_column_to_entity(col.clone(), DatomsColumn::Tx, entid); + } else { + // A resolution failure means we're done here. + self.mark_known_empty(EmptyBecause::TxIdOutOfRange); + return; + } }, PatternNonValuePlace::Ident(ref ident) => { if let Some(entid) = self.entid_for_ident(schema, ident.as_ref()) { - self.constrain_column_to_entity(col.clone(), DatomsColumn::Tx, entid) + if self.valid_tx_entid(partition_map, entid) { + self.constrain_column_to_entity(col.clone(), DatomsColumn::Tx, entid) + } else { + // A resolution failure means we're done here. + self.mark_known_empty(EmptyBecause::TxIdOutOfRange); + return; + } } else { // A resolution failure means we're done here. self.mark_known_empty(EmptyBecause::UnresolvedIdent(ident.cloned())); @@ -264,7 +275,7 @@ impl ConjoiningClauses { } } - pub fn apply_pattern<'s, 'p>(&mut self, schema: &'s Schema, pattern: Pattern) { + pub fn apply_pattern<'s, 'p>(&mut self, schema: &'s Schema, partition_map: &'p PartitionMap, pattern: Pattern) { // For now we only support the default source. match pattern.source { Some(SrcVar::DefaultSrc) | None => (), @@ -272,7 +283,7 @@ impl ConjoiningClauses { }; if let Some(alias) = self.alias_table(schema, &pattern) { - self.apply_pattern_clause_for_alias(schema, &pattern, &alias); + self.apply_pattern_clause_for_alias(schema, partition_map, &pattern, &alias); self.from.push(alias); } else { // We didn't determine a table, likely because there was a mismatch @@ -328,17 +339,18 @@ mod testing { use algebrize; - fn alg(schema: &Schema, input: &str) -> ConjoiningClauses { + fn alg(schema: &Schema, partition_map: &PartitionMap, input: &str) -> ConjoiningClauses { let parsed = parse_find_string(input).expect("parse failed"); - algebrize(schema.into(), parsed).expect("algebrize failed").cc + algebrize(schema.into(), partition_map, parsed).expect("algebrize failed").cc } #[test] fn test_unknown_ident() { let mut cc = ConjoiningClauses::default(); let schema = Schema::default(); + let partition_map = PartitionMap::default(); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), attribute: ident("foo", "bar"), @@ -353,10 +365,11 @@ mod testing { fn test_unknown_attribute() { let mut cc = ConjoiningClauses::default(); let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), attribute: ident("foo", "bar"), @@ -371,6 +384,7 @@ mod testing { fn test_apply_simple_pattern() { let mut cc = ConjoiningClauses::default(); let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99); add_attribute(&mut schema, 99, Attribute { @@ -379,7 +393,7 @@ mod testing { }); let x = Variable::from_valid_name("?x"); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: ident("foo", "bar"), @@ -417,9 +431,10 @@ mod testing { fn test_apply_unattributed_pattern() { let mut cc = ConjoiningClauses::default(); let schema = Schema::default(); + let partition_map = PartitionMap::default(); let x = Variable::from_valid_name("?x"); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: PatternNonValuePlace::Placeholder, @@ -456,6 +471,7 @@ mod testing { fn test_apply_unattributed_but_bound_pattern_with_returned() { let mut cc = ConjoiningClauses::default(); let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99); add_attribute(&mut schema, 99, Attribute { value_type: ValueType::Boolean, @@ -468,7 +484,7 @@ mod testing { cc.input_variables.insert(a.clone()); cc.value_bindings.insert(a.clone(), TypedValue::Keyword(Rc::new(NamespacedKeyword::new("foo", "bar")))); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: PatternNonValuePlace::Variable(a.clone()), @@ -503,6 +519,7 @@ mod testing { fn test_bind_the_wrong_thing() { let mut cc = ConjoiningClauses::default(); let schema = Schema::default(); + let partition_map = PartitionMap::default(); let x = Variable::from_valid_name("?x"); let a = Variable::from_valid_name("?a"); @@ -511,7 +528,7 @@ mod testing { cc.input_variables.insert(a.clone()); cc.value_bindings.insert(a.clone(), hello.clone()); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: PatternNonValuePlace::Variable(a.clone()), @@ -529,11 +546,12 @@ mod testing { fn test_apply_unattributed_pattern_with_returned() { let mut cc = ConjoiningClauses::default(); let schema = Schema::default(); + let partition_map = PartitionMap::default(); let x = Variable::from_valid_name("?x"); let a = Variable::from_valid_name("?a"); let v = Variable::from_valid_name("?v"); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: PatternNonValuePlace::Variable(a.clone()), @@ -561,9 +579,10 @@ mod testing { fn test_apply_unattributed_pattern_with_string_value() { let mut cc = ConjoiningClauses::default(); let schema = Schema::default(); + let partition_map = PartitionMap::default(); let x = Variable::from_valid_name("?x"); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: PatternNonValuePlace::Placeholder, @@ -599,6 +618,7 @@ mod testing { fn test_apply_two_patterns() { let mut cc = ConjoiningClauses::default(); let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99); associate_ident(&mut schema, NamespacedKeyword::new("foo", "roz"), 98); @@ -613,14 +633,14 @@ mod testing { let x = Variable::from_valid_name("?x"); let y = Variable::from_valid_name("?y"); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: ident("foo", "roz"), value: PatternValuePlace::Constant(NonIntegerConstant::Text(Rc::new("idgoeshere".to_string()))), tx: PatternNonValuePlace::Placeholder, }); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: ident("foo", "bar"), @@ -671,6 +691,7 @@ mod testing { #[test] fn test_value_bindings() { let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99); add_attribute(&mut schema, 99, Attribute { @@ -687,7 +708,7 @@ mod testing { let variables: BTreeSet = vec![Variable::from_valid_name("?y")].into_iter().collect(); let mut cc = ConjoiningClauses::with_inputs(variables, inputs); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: ident("foo", "bar"), @@ -718,6 +739,7 @@ mod testing { /// the variable inferred from known attributes. fn test_value_bindings_type_disagreement() { let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99); add_attribute(&mut schema, 99, Attribute { @@ -734,7 +756,7 @@ mod testing { let variables: BTreeSet = vec![Variable::from_valid_name("?y")].into_iter().collect(); let mut cc = ConjoiningClauses::with_inputs(variables, inputs); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: ident("foo", "bar"), @@ -751,6 +773,7 @@ mod testing { /// of a fulltext-valued attribute. fn test_fulltext_type_disagreement() { let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99); add_attribute(&mut schema, 99, Attribute { @@ -768,7 +791,7 @@ mod testing { let variables: BTreeSet = vec![Variable::from_valid_name("?y")].into_iter().collect(); let mut cc = ConjoiningClauses::with_inputs(variables, inputs); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: ident("foo", "bar"), @@ -787,6 +810,7 @@ mod testing { fn test_apply_two_conflicting_known_patterns() { let mut cc = ConjoiningClauses::default(); let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99); associate_ident(&mut schema, NamespacedKeyword::new("foo", "roz"), 98); @@ -802,14 +826,14 @@ mod testing { let x = Variable::from_valid_name("?x"); let y = Variable::from_valid_name("?y"); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: ident("foo", "roz"), value: PatternValuePlace::Variable(y.clone()), tx: PatternNonValuePlace::Placeholder, }); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: ident("foo", "bar"), @@ -837,6 +861,7 @@ mod testing { fn test_apply_two_implicitly_conflicting_patterns() { let mut cc = ConjoiningClauses::default(); let schema = Schema::default(); + let partition_map = PartitionMap::default(); // [:find ?x :where // [?x ?y true] @@ -844,14 +869,14 @@ mod testing { let x = Variable::from_valid_name("?x"); let y = Variable::from_valid_name("?y"); let z = Variable::from_valid_name("?z"); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: PatternNonValuePlace::Variable(y.clone()), value: PatternValuePlace::Constant(NonIntegerConstant::Boolean(true)), tx: PatternNonValuePlace::Placeholder, }); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(z.clone()), attribute: PatternNonValuePlace::Variable(y.clone()), @@ -875,6 +900,7 @@ mod testing { fn ensure_extracted_types_is_cleared() { let query = r#"[:find ?e ?v :where [_ _ ?v] [?e :foo/bar ?v]]"#; let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99); add_attribute(&mut schema, 99, Attribute { value_type: ValueType::Boolean, @@ -882,7 +908,7 @@ mod testing { }); let e = Variable::from_valid_name("?e"); let v = Variable::from_valid_name("?v"); - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); assert_eq!(cc.known_types.get(&e), Some(&ValueTypeSet::of_one(ValueType::Ref))); assert_eq!(cc.known_types.get(&v), Some(&ValueTypeSet::of_one(ValueType::Boolean))); assert!(!cc.extracted_types.contains_key(&e)); diff --git a/query-algebrizer/src/clauses/predicate.rs b/query-algebrizer/src/clauses/predicate.rs index 0a77ac21..9ade7313 100644 --- a/query-algebrizer/src/clauses/predicate.rs +++ b/query-algebrizer/src/clauses/predicate.rs @@ -157,6 +157,8 @@ mod testing { ValueType, }; + use mentat_db::PartitionMap; + use mentat_query::{ FnArg, NamespacedKeyword, @@ -187,6 +189,7 @@ mod testing { fn test_apply_inequality() { let mut cc = ConjoiningClauses::default(); let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99); add_attribute(&mut schema, 99, Attribute { @@ -196,7 +199,7 @@ mod testing { let x = Variable::from_valid_name("?x"); let y = Variable::from_valid_name("?y"); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: PatternNonValuePlace::Placeholder, @@ -241,6 +244,7 @@ mod testing { fn test_apply_conflict_with_numeric_range() { let mut cc = ConjoiningClauses::default(); let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99); associate_ident(&mut schema, NamespacedKeyword::new("foo", "roz"), 98); @@ -256,7 +260,7 @@ mod testing { let x = Variable::from_valid_name("?x"); let y = Variable::from_valid_name("?y"); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: PatternNonValuePlace::Placeholder, @@ -274,7 +278,7 @@ mod testing { ]}).is_ok()); assert!(!cc.is_known_empty()); - cc.apply_pattern(&schema, Pattern { + cc.apply_pattern(&schema, &partition_map, Pattern { source: None, entity: PatternNonValuePlace::Variable(x.clone()), attribute: ident("foo", "roz"), diff --git a/query-algebrizer/src/lib.rs b/query-algebrizer/src/lib.rs index b25b221d..92366c2d 100644 --- a/query-algebrizer/src/lib.rs +++ b/query-algebrizer/src/lib.rs @@ -18,6 +18,7 @@ extern crate error_chain; extern crate maplit; extern crate mentat_core; +extern crate mentat_db; extern crate mentat_query; use std::collections::BTreeSet; @@ -35,6 +36,7 @@ use mentat_core::{ }; use mentat_core::counter::RcCounter; +use mentat_db::PartitionMap; use mentat_query::{ FindQuery, @@ -85,12 +87,12 @@ impl AlgebraicQuery { } } -pub fn algebrize_with_counter(schema: &Schema, parsed: FindQuery, counter: usize) -> Result { - algebrize_with_inputs(schema, parsed, counter, QueryInputs::default()) +pub fn algebrize_with_counter(schema: &Schema, partition_map: &PartitionMap, parsed: FindQuery, counter: usize) -> Result { + algebrize_with_inputs(schema, partition_map, parsed, counter, QueryInputs::default()) } -pub fn algebrize(schema: &Schema, parsed: FindQuery) -> Result { - algebrize_with_inputs(schema, parsed, 0, QueryInputs::default()) +pub fn algebrize(schema: &Schema, partition_map: &PartitionMap, parsed: FindQuery) -> Result { + algebrize_with_inputs(schema, partition_map, parsed, 0, QueryInputs::default()) } /// Take an ordering list. Any variables that aren't fixed by the query are used to produce @@ -168,7 +170,8 @@ fn simplify_limit(mut query: AlgebraicQuery) -> Result { Ok(query) } -pub fn algebrize_with_inputs(schema: &Schema, +pub fn algebrize_with_inputs(schema: &Schema, + partition_map: &PartitionMap, parsed: FindQuery, counter: usize, inputs: QueryInputs) -> Result { @@ -184,7 +187,7 @@ pub fn algebrize_with_inputs(schema: &Schema, // TODO: flesh out the rest of find-into-context. let where_clauses = parsed.where_clauses; for where_clause in where_clauses { - cc.apply_clause(schema, where_clause)?; + cc.apply_clause(schema, partition_map, where_clause)?; } cc.expand_column_bindings(); cc.prune_extracted_types(); diff --git a/query-algebrizer/src/types.rs b/query-algebrizer/src/types.rs index 970404a1..17f64054 100644 --- a/query-algebrizer/src/types.rs +++ b/query-algebrizer/src/types.rs @@ -485,6 +485,7 @@ pub enum EmptyBecause { InvalidBinding(Column, TypedValue), ValueTypeMismatch(ValueType, TypedValue), AttributeLookupFailed, // Catch-all, because the table lookup code is lazy. TODO + TxIdOutOfRange, } impl Debug for EmptyBecause { @@ -537,6 +538,9 @@ impl Debug for EmptyBecause { &AttributeLookupFailed => { write!(f, "Attribute lookup failed") }, + &TxIdOutOfRange => { + write!(f, "Transaction ID is not valid") + }, } } } diff --git a/query-algebrizer/tests/fulltext.rs b/query-algebrizer/tests/fulltext.rs index 5b1cb1d7..b04434dc 100644 --- a/query-algebrizer/tests/fulltext.rs +++ b/query-algebrizer/tests/fulltext.rs @@ -9,6 +9,7 @@ // specific language governing permissions and limitations under the License. extern crate mentat_core; +extern crate mentat_db; extern crate mentat_query; extern crate mentat_query_algebrizer; extern crate mentat_query_parser; @@ -20,6 +21,8 @@ use mentat_core::{ ValueType, }; +use mentat_db::PartitionMap; + use mentat_query_parser::{ parse_find_string, }; @@ -80,23 +83,24 @@ fn prepopulated_schema() -> Schema { schema } -fn alg(schema: &Schema, input: &str) -> ConjoiningClauses { +fn alg(schema: &Schema, partition_map: &PartitionMap, input: &str) -> ConjoiningClauses { let parsed = parse_find_string(input).expect("query input to have parsed"); - algebrize(schema.into(), parsed).expect("algebrizing to have succeeded").cc + algebrize(schema.into(), partition_map, parsed).expect("algebrizing to have succeeded").cc } #[test] fn test_apply_fulltext() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); // If you use a non-FTS attribute, we will short-circuit. let query = r#"[:find ?val :where [(fulltext $ :foo/name "hello") [[?entity ?val _ _]]]]"#; - assert!(alg(&schema, query).is_known_empty()); + assert!(alg(&schema, &partition_map, query).is_known_empty()); // If you get a type mismatch, we will short-circuit. let query = r#"[:find ?val :where [(fulltext $ :foo/description "hello") [[?entity ?val ?tx ?score]]] [?score :foo/bar _]]"#; - assert!(alg(&schema, query).is_known_empty()); + assert!(alg(&schema, &partition_map, query).is_known_empty()); } diff --git a/query-algebrizer/tests/ground.rs b/query-algebrizer/tests/ground.rs index b7f811db..51f04f4c 100644 --- a/query-algebrizer/tests/ground.rs +++ b/query-algebrizer/tests/ground.rs @@ -9,6 +9,7 @@ // specific language governing permissions and limitations under the License. extern crate mentat_core; +extern crate mentat_db; extern crate mentat_query; extern crate mentat_query_algebrizer; extern crate mentat_query_parser; @@ -23,6 +24,8 @@ use mentat_core::{ TypedValue, }; +use mentat_db::PartitionMap; + use mentat_query_parser::{ parse_find_string, }; @@ -91,19 +94,19 @@ fn prepopulated_schema() -> Schema { schema } -fn bails(schema: &Schema, input: &str) -> Error { +fn bails(schema: &Schema, partition_map: &PartitionMap, input: &str) -> Error { let parsed = parse_find_string(input).expect("query input to have parsed"); - algebrize(schema.into(), parsed).expect_err("algebrize to have failed") + algebrize(schema.into(), partition_map, parsed).expect_err("algebrize to have failed") } -fn bails_with_inputs(schema: &Schema, input: &str, inputs: QueryInputs) -> Error { +fn bails_with_inputs(schema: &Schema, partition_map: &PartitionMap, input: &str, inputs: QueryInputs) -> Error { let parsed = parse_find_string(input).expect("query input to have parsed"); - algebrize_with_inputs(schema, parsed, 0, inputs).expect_err("algebrize to have failed") + algebrize_with_inputs(schema, partition_map, parsed, 0, inputs).expect_err("algebrize to have failed") } -fn alg(schema: &Schema, input: &str) -> ConjoiningClauses { +fn alg(schema: &Schema, partition_map: &PartitionMap, input: &str) -> ConjoiningClauses { let parsed = parse_find_string(input).expect("query input to have parsed"); - algebrize(schema.into(), parsed).expect("algebrizing to have succeeded").cc + algebrize(schema.into(), partition_map, parsed).expect("algebrizing to have succeeded").cc } #[test] @@ -112,7 +115,8 @@ fn test_ground_doesnt_bail_for_type_conflicts() { // The query can return no results. let q = r#"[:find ?x :where [?x :foo/knows ?p] [(ground 9.95) ?x]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_some()); } @@ -120,7 +124,8 @@ fn test_ground_doesnt_bail_for_type_conflicts() { fn test_ground_tuple_fails_impossible() { let q = r#"[:find ?x :where [?x :foo/knows ?p] [(ground [5 9.95]) [?x ?p]]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_some()); } @@ -128,7 +133,8 @@ fn test_ground_tuple_fails_impossible() { fn test_ground_scalar_fails_impossible() { let q = r#"[:find ?x :where [?x :foo/knows ?p] [(ground true) ?p]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_some()); } @@ -138,7 +144,8 @@ fn test_ground_coll_skips_impossible() { // The query can return no results. let q = r#"[:find ?x :where [?x :foo/knows ?p] [(ground [5 9.95 11]) [?x ...]]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_none()); assert_eq!(cc.computed_tables[0], ComputedTable::NamedValues { names: vec![Variable::from_valid_name("?x")], @@ -150,7 +157,8 @@ fn test_ground_coll_skips_impossible() { fn test_ground_coll_fails_if_all_impossible() { let q = r#"[:find ?x :where [?x :foo/knows ?p] [(ground [5.1 5.2]) [?p ...]]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_some()); } @@ -158,7 +166,8 @@ fn test_ground_coll_fails_if_all_impossible() { fn test_ground_rel_skips_impossible() { let q = r#"[:find ?x :where [?x :foo/knows ?p] [(ground [[8 "foo"] [5 7] [9.95 9] [11 12]]) [[?x ?p]]]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_none()); assert_eq!(cc.computed_tables[0], ComputedTable::NamedValues { names: vec![Variable::from_valid_name("?x"), Variable::from_valid_name("?p")], @@ -170,7 +179,8 @@ fn test_ground_rel_skips_impossible() { fn test_ground_rel_fails_if_all_impossible() { let q = r#"[:find ?x :where [?x :foo/knows ?p] [(ground [[11 5.1] [12 5.2]]) [[?x ?p]]]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_some()); } @@ -178,21 +188,24 @@ fn test_ground_rel_fails_if_all_impossible() { fn test_ground_tuple_rejects_all_placeholders() { let q = r#"[:find ?x :where [?x :foo/knows ?p] [(ground [8 "foo" 3]) [_ _ _]]]"#; let schema = prepopulated_schema(); - bails(&schema, &q); + let partition_map = PartitionMap::default(); + bails(&schema, &partition_map, &q); } #[test] fn test_ground_rel_rejects_all_placeholders() { let q = r#"[:find ?x :where [?x :foo/knows ?p] [(ground [[8 "foo"]]) [[_ _]]]]"#; let schema = prepopulated_schema(); - bails(&schema, &q); + let partition_map = PartitionMap::default(); + bails(&schema, &partition_map, &q); } #[test] fn test_ground_tuple_placeholders() { let q = r#"[:find ?x :where [?x :foo/knows ?p] [(ground [8 "foo" 3]) [?x _ ?p]]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_none()); assert_eq!(cc.bound_value(&Variable::from_valid_name("?x")), Some(TypedValue::Ref(8))); assert_eq!(cc.bound_value(&Variable::from_valid_name("?p")), Some(TypedValue::Ref(3))); @@ -202,7 +215,8 @@ fn test_ground_tuple_placeholders() { fn test_ground_rel_placeholders() { let q = r#"[:find ?x :where [?x :foo/knows ?p] [(ground [[8 "foo" 3] [5 false 7] [5 9.95 9]]) [[?x _ ?p]]]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_none()); assert_eq!(cc.computed_tables[0], ComputedTable::NamedValues { names: vec![Variable::from_valid_name("?x"), Variable::from_valid_name("?p")], @@ -222,7 +236,8 @@ fn test_ground_rel_placeholders() { fn test_multiple_reference_type_failure() { let q = r#"[:find ?x :where [?x :foo/age ?y] [?x :foo/knows ?y]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_some()); } @@ -230,7 +245,8 @@ fn test_multiple_reference_type_failure() { fn test_ground_tuple_infers_types() { let q = r#"[:find ?x :where [?x :foo/age ?v] [(ground [8 10]) [?x ?v]]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_none()); assert_eq!(cc.bound_value(&Variable::from_valid_name("?x")), Some(TypedValue::Ref(8))); assert_eq!(cc.bound_value(&Variable::from_valid_name("?v")), Some(TypedValue::Long(10))); @@ -240,7 +256,8 @@ fn test_ground_tuple_infers_types() { fn test_ground_rel_infers_types() { let q = r#"[:find ?x :where [?x :foo/age ?v] [(ground [[8 10]]) [[?x ?v]]]]"#; let schema = prepopulated_schema(); - let cc = alg(&schema, &q); + let partition_map = PartitionMap::default(); + let cc = alg(&schema, &partition_map, &q); assert!(cc.empty_because.is_none()); assert_eq!(cc.computed_tables[0], ComputedTable::NamedValues { names: vec![Variable::from_valid_name("?x"), Variable::from_valid_name("?v")], @@ -252,7 +269,8 @@ fn test_ground_rel_infers_types() { fn test_ground_coll_heterogeneous_types() { let q = r#"[:find ?x :where [?x _ ?v] [(ground [false 8.5]) [?v ...]]]"#; let schema = prepopulated_schema(); - let e = bails(&schema, &q); + let partition_map = PartitionMap::default(); + let e = bails(&schema, &partition_map, &q); match e { Error(ErrorKind::InvalidGroundConstant, _) => { }, @@ -266,7 +284,8 @@ fn test_ground_coll_heterogeneous_types() { fn test_ground_rel_heterogeneous_types() { let q = r#"[:find ?x :where [?x _ ?v] [(ground [[false] [5]]) [[?v]]]]"#; let schema = prepopulated_schema(); - let e = bails(&schema, &q); + let partition_map = PartitionMap::default(); + let e = bails(&schema, &partition_map, &q); match e { Error(ErrorKind::InvalidGroundConstant, _) => { }, @@ -280,7 +299,8 @@ fn test_ground_rel_heterogeneous_types() { fn test_ground_tuple_duplicate_vars() { let q = r#"[:find ?x :where [?x :foo/age ?v] [(ground [8 10]) [?x ?x]]]"#; let schema = prepopulated_schema(); - let e = bails(&schema, &q); + let partition_map = PartitionMap::default(); + let e = bails(&schema, &partition_map, &q); match e { Error(ErrorKind::InvalidBinding(v, e), _) => { assert_eq!(v, PlainSymbol::new("ground")); @@ -296,7 +316,8 @@ fn test_ground_tuple_duplicate_vars() { fn test_ground_rel_duplicate_vars() { let q = r#"[:find ?x :where [?x :foo/age ?v] [(ground [[8 10]]) [[?x ?x]]]]"#; let schema = prepopulated_schema(); - let e = bails(&schema, &q); + let partition_map = PartitionMap::default(); + let e = bails(&schema, &partition_map, &q); match e { Error(ErrorKind::InvalidBinding(v, e), _) => { assert_eq!(v, PlainSymbol::new("ground")); @@ -312,7 +333,8 @@ fn test_ground_rel_duplicate_vars() { fn test_ground_nonexistent_variable_invalid() { let q = r#"[:find ?x ?e :where [?e _ ?x] (not [(ground 17) ?v])]"#; let schema = prepopulated_schema(); - let e = bails(&schema, &q); + let partition_map = PartitionMap::default(); + let e = bails(&schema, &partition_map, &q); match e { Error(ErrorKind::UnboundVariable(PlainSymbol(v)), _) => { assert_eq!(v, "?v".to_string()); @@ -326,6 +348,7 @@ fn test_ground_nonexistent_variable_invalid() { #[test] fn test_unbound_input_variable_invalid() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); let q = r#"[:find ?y ?age :in ?x :where [(ground [?x]) [?y ...]] [?y :foo/age ?age]]"#; // This fails even if we know the type: we don't support grounding bindings @@ -335,7 +358,7 @@ fn test_unbound_input_variable_invalid() { let i = QueryInputs::new(types, BTreeMap::default()).expect("valid QueryInputs"); - let e = bails_with_inputs(&schema, &q, i); + let e = bails_with_inputs(&schema, &partition_map, &q, i); match e { Error(ErrorKind::UnboundVariable(v), _) => { assert_eq!(v.0, "?x"); diff --git a/query-algebrizer/tests/predicate.rs b/query-algebrizer/tests/predicate.rs index 766f183c..74644edd 100644 --- a/query-algebrizer/tests/predicate.rs +++ b/query-algebrizer/tests/predicate.rs @@ -9,6 +9,7 @@ // specific language governing permissions and limitations under the License. extern crate mentat_core; +extern crate mentat_db; extern crate mentat_query; extern crate mentat_query_algebrizer; extern crate mentat_query_parser; @@ -20,6 +21,8 @@ use mentat_core::{ ValueType, }; +use mentat_db::PartitionMap; + use mentat_query_parser::{ parse_find_string, }; @@ -70,26 +73,27 @@ fn prepopulated_schema() -> Schema { schema } -fn bails(schema: &Schema, input: &str) -> Error { +fn bails(schema: &Schema, partition_map: &PartitionMap, input: &str) -> Error { let parsed = parse_find_string(input).expect("query input to have parsed"); - algebrize(schema.into(), parsed).expect_err("algebrize to have failed") + algebrize(schema.into(), partition_map, parsed).expect_err("algebrize to have failed") } -fn alg(schema: &Schema, input: &str) -> ConjoiningClauses { +fn alg(schema: &Schema, partition_map: &PartitionMap, input: &str) -> ConjoiningClauses { let parsed = parse_find_string(input).expect("query input to have parsed"); - algebrize(schema.into(), parsed).expect("algebrizing to have succeeded").cc + algebrize(schema.into(), partition_map, parsed).expect("algebrizing to have succeeded").cc } #[test] fn test_instant_predicates_require_instants() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); // You can't use a string for an inequality: this is a straight-up error. let query = r#"[:find ?e :where [?e :foo/date ?t] [(> ?t "2017-06-16T00:56:41.257Z")]]"#; - match bails(&schema, query).0 { + match bails(&schema, &partition_map, query).0 { ErrorKind::InvalidArgument(op, why, idx) => { assert_eq!(op, PlainSymbol::new(">")); assert_eq!(why, "numeric or instant"); @@ -102,7 +106,7 @@ fn test_instant_predicates_require_instants() { :where [?e :foo/date ?t] [(> "2017-06-16T00:56:41.257Z", ?t)]]"#; - match bails(&schema, query).0 { + match bails(&schema, &partition_map, query).0 { ErrorKind::InvalidArgument(op, why, idx) => { assert_eq!(op, PlainSymbol::new(">")); assert_eq!(why, "numeric or instant"); @@ -118,7 +122,7 @@ fn test_instant_predicates_require_instants() { :where [?e :foo/date ?t] [(> ?t 1234512345)]]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); assert!(cc.is_known_empty()); assert_eq!(cc.empty_because.unwrap(), EmptyBecause::TypeMismatch { @@ -132,7 +136,7 @@ fn test_instant_predicates_require_instants() { :where [?e :foo/double ?t] [(< ?t 1234512345)]]"#; - let cc = alg(&schema, query); + let cc = alg(&schema, &partition_map, query); assert!(!cc.is_known_empty()); assert_eq!(cc.known_type(&Variable::from_valid_name("?t")).expect("?t is known"), ValueType::Double); diff --git a/query-translator/Cargo.toml b/query-translator/Cargo.toml index e2a8f141..837d23bb 100644 --- a/query-translator/Cargo.toml +++ b/query-translator/Cargo.toml @@ -7,6 +7,9 @@ workspace = ".." [dependencies.mentat_core] path = "../core" +[dependencies.mentat_db] +path = "../db" + [dependencies.mentat_sql] path = "../sql" diff --git a/query-translator/tests/translate.rs b/query-translator/tests/translate.rs index fb9d0aec..161c1f56 100644 --- a/query-translator/tests/translate.rs +++ b/query-translator/tests/translate.rs @@ -9,6 +9,7 @@ // specific language governing permissions and limitations under the License. extern crate mentat_core; +extern crate mentat_db; extern crate mentat_query; extern crate mentat_query_algebrizer; extern crate mentat_query_parser; @@ -32,6 +33,8 @@ use mentat_core::{ ValueType, }; +use mentat_db::PartitionMap; + use mentat_query_parser::parse_find_string; use mentat_query_algebrizer::{ QueryInputs, @@ -53,15 +56,15 @@ fn add_attribute(schema: &mut Schema, e: Entid, a: Attribute) { schema.schema_map.insert(e, a); } -fn translate_with_inputs(schema: &Schema, query: &'static str, inputs: QueryInputs) -> SQLQuery { +fn translate_with_inputs(schema: &Schema, partition_map: &PartitionMap, query: &'static str, inputs: QueryInputs) -> SQLQuery { let parsed = parse_find_string(query).expect("parse failed"); - let algebrized = algebrize_with_inputs(schema, parsed, 0, inputs).expect("algebrize failed"); + let algebrized = algebrize_with_inputs(schema, partition_map, parsed, 0, inputs).expect("algebrize failed"); let select = query_to_select(algebrized); select.query.to_sql_query().unwrap() } -fn translate(schema: &Schema, query: &'static str) -> SQLQuery { - translate_with_inputs(schema, query, QueryInputs::default()) +fn translate(schema: &Schema, partition_map: &PartitionMap, query: &'static str) -> SQLQuery { + translate_with_inputs(schema, partition_map, query, QueryInputs::default()) } fn prepopulated_typed_schema(foo_type: ValueType) -> Schema { @@ -92,9 +95,10 @@ fn make_arg(name: &'static str, value: &'static str) -> (String, Rc 1)"); assert_eq!(args, vec![]); // Excludes booleans. - let SQLQuery { sql, args } = translate(&schema, one); + let SQLQuery { sql, args } = translate(&schema, &partition_map, one); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE (`datoms00`.v = 1 AND `datoms00`.value_type_tag <> 1)"); assert_eq!(args, vec![]); // Can't match boolean; no need to filter it out. - let SQLQuery { sql, args } = translate(&schema, two); + let SQLQuery { sql, args } = translate(&schema, &partition_map, two); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.v = 2"); assert_eq!(args, vec![]); } @@ -272,10 +288,11 @@ fn test_unknown_attribute_integer_value() { #[test] fn test_unknown_ident() { let schema = Schema::default(); + let partition_map = PartitionMap::default(); let impossible = r#"[:find ?x :where [?x :db/ident :no/exist]]"#; let parsed = parse_find_string(impossible).expect("parse failed"); - let algebrized = algebrize(&schema, parsed).expect("algebrize failed"); + let algebrized = algebrize(&schema, &partition_map, parsed).expect("algebrize failed"); // This query cannot return results: the ident doesn't resolve for a ref-typed attribute. assert!(algebrized.is_known_empty()); @@ -289,9 +306,10 @@ fn test_unknown_ident() { #[test] fn test_numeric_less_than_unknown_attribute() { let schema = Schema::default(); + let partition_map = PartitionMap::default(); let query = r#"[:find ?x :where [?x _ ?y] [(< ?y 10)]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); // Although we infer numericness from numeric predicates, we've already assigned a table to the // first pattern, and so this is _still_ `all_datoms`. @@ -302,8 +320,9 @@ fn test_numeric_less_than_unknown_attribute() { #[test] fn test_numeric_gte_known_attribute() { let schema = prepopulated_typed_schema(ValueType::Double); + let partition_map = PartitionMap::default(); let query = r#"[:find ?x :where [?x :foo/bar ?y] [(>= ?y 12.9)]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v >= 1.29e1"); assert_eq!(args, vec![]); } @@ -311,8 +330,9 @@ fn test_numeric_gte_known_attribute() { #[test] fn test_numeric_not_equals_known_attribute() { let schema = prepopulated_typed_schema(ValueType::Long); + let partition_map = PartitionMap::default(); let query = r#"[:find ?x . :where [?x :foo/bar ?y] [(!= ?y 12)]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v <> 12 LIMIT 1"); assert_eq!(args, vec![]); } @@ -320,12 +340,13 @@ fn test_numeric_not_equals_known_attribute() { #[test] fn test_compare_long_to_double_constants() { let schema = prepopulated_typed_schema(ValueType::Double); + let partition_map = PartitionMap::default(); let query = r#"[:find ?e . :where [?e :foo/bar ?v] [(< 99.0 1234512345)]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \ WHERE `datoms00`.a = 99 \ AND 9.9e1 < 1234512345 \ @@ -336,13 +357,14 @@ fn test_compare_long_to_double_constants() { #[test] fn test_compare_long_to_double() { let schema = prepopulated_typed_schema(ValueType::Double); + let partition_map = PartitionMap::default(); // You can compare longs to doubles. let query = r#"[:find ?e . :where [?e :foo/bar ?t] [(< ?t 1234512345)]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \ WHERE `datoms00`.a = 99 \ AND `datoms00`.v < 1234512345 \ @@ -353,13 +375,14 @@ fn test_compare_long_to_double() { #[test] fn test_compare_double_to_long() { let schema = prepopulated_typed_schema(ValueType::Long); + let partition_map = PartitionMap::default(); // You can compare doubles to longs. let query = r#"[:find ?e . :where [?e :foo/bar ?t] [(< ?t 1234512345.0)]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \ WHERE `datoms00`.a = 99 \ AND `datoms00`.v < 1.234512345e9 \ @@ -370,6 +393,7 @@ fn test_compare_double_to_long() { #[test] fn test_simple_or_join() { let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("page", "url"), 97); associate_ident(&mut schema, NamespacedKeyword::new("page", "title"), 98); associate_ident(&mut schema, NamespacedKeyword::new("page", "description"), 99); @@ -387,7 +411,7 @@ fn test_simple_or_join() { [?page :page/title "Foo"]) [?page :page/url ?url] [?page :page/description ?description]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT `datoms01`.v AS `?url`, `datoms02`.v AS `?description` FROM `datoms` AS `datoms00`, `datoms` AS `datoms01`, `datoms` AS `datoms02` WHERE ((`datoms00`.a = 97 AND `datoms00`.v = $v0) OR (`datoms00`.a = 98 AND `datoms00`.v = $v1)) AND `datoms01`.a = 97 AND `datoms02`.a = 99 AND `datoms00`.e = `datoms01`.e AND `datoms00`.e = `datoms02`.e LIMIT 1"); assert_eq!(args, vec![make_arg("$v0", "http://foo.com/"), make_arg("$v1", "Foo")]); } @@ -395,6 +419,7 @@ fn test_simple_or_join() { #[test] fn test_complex_or_join() { let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("page", "save"), 95); add_attribute(&mut schema, 95, Attribute { value_type: ValueType::Ref, @@ -422,7 +447,7 @@ fn test_complex_or_join() { [?save :save/title "Foo"])) [?page :page/url ?url] [?page :page/description ?description]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT `datoms04`.v AS `?url`, \ `datoms05`.v AS `?description` \ FROM (SELECT `datoms00`.e AS `?page` \ @@ -456,6 +481,7 @@ fn test_complex_or_join() { #[test] fn test_complex_or_join_type_projection() { let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("page", "title"), 98); add_attribute(&mut schema, 98, Attribute { value_type: ValueType::String, @@ -467,7 +493,7 @@ fn test_complex_or_join_type_projection() { (or [6 :page/title ?y] [5 _ ?y])]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT `c00`.`?y` AS `?y`, \ `c00`.`?y_value_type_tag` AS `?y_value_type_tag` \ FROM (SELECT `datoms00`.v AS `?y`, \ @@ -487,6 +513,7 @@ fn test_complex_or_join_type_projection() { #[test] fn test_not() { let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("page", "url"), 97); associate_ident(&mut schema, NamespacedKeyword::new("page", "title"), 98); associate_ident(&mut schema, NamespacedKeyword::new("page", "bookmarked"), 99); @@ -505,7 +532,7 @@ fn test_not() { :where [?page :page/title ?title] (not [?page :page/url "http://foo.com/"] [?page :page/bookmarked true])]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `datoms00`.v AS `?title` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 98 AND NOT EXISTS (SELECT 1 FROM `datoms` AS `datoms01`, `datoms` AS `datoms02` WHERE `datoms01`.a = 97 AND `datoms01`.v = $v0 AND `datoms02`.a = 99 AND `datoms02`.v = 1 AND `datoms00`.e = `datoms01`.e AND `datoms00`.e = `datoms02`.e)"); assert_eq!(args, vec![make_arg("$v0", "http://foo.com/")]); } @@ -513,6 +540,7 @@ fn test_not() { #[test] fn test_not_join() { let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("page", "url"), 97); associate_ident(&mut schema, NamespacedKeyword::new("bookmarks", "page"), 98); associate_ident(&mut schema, NamespacedKeyword::new("bookmarks", "date_created"), 99); @@ -534,7 +562,7 @@ fn test_not_join() { (not-join [?url] [?page :bookmarks/page ?url] [?page :bookmarks/date_created "4/4/2017"])]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?url` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 97 AND NOT EXISTS (SELECT 1 FROM `datoms` AS `datoms01`, `datoms` AS `datoms02` WHERE `datoms01`.a = 98 AND `datoms02`.a = 99 AND `datoms02`.v = $v0 AND `datoms01`.e = `datoms02`.e AND `datoms00`.e = `datoms01`.v)"); assert_eq!(args, vec![make_arg("$v0", "4/4/2017")]); } @@ -542,16 +570,17 @@ fn test_not_join() { #[test] fn test_with_without_aggregate() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); // Known type. let query = r#"[:find ?x :with ?y :where [?x :foo/bar ?y]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x`, `datoms00`.v AS `?y` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99"); assert_eq!(args, vec![]); // Unknown type. let query = r#"[:find ?x :with ?y :where [?x _ ?y]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `all_datoms00`.e AS `?x`, `all_datoms00`.v AS `?y`, `all_datoms00`.value_type_tag AS `?y_value_type_tag` FROM `all_datoms` AS `all_datoms00`"); assert_eq!(args, vec![]); } @@ -559,10 +588,11 @@ fn test_with_without_aggregate() { #[test] fn test_order_by() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); // Known type. let query = r#"[:find ?x :where [?x :foo/bar ?y] :order (desc ?y)]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x`, `datoms00`.v AS `?y` \ FROM `datoms` AS `datoms00` \ WHERE `datoms00`.a = 99 \ @@ -571,7 +601,7 @@ fn test_order_by() { // Unknown type. let query = r#"[:find ?x :with ?y :where [?x _ ?y] :order ?y ?x]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `all_datoms00`.e AS `?x`, `all_datoms00`.v AS `?y`, \ `all_datoms00`.value_type_tag AS `?y_value_type_tag` \ FROM `all_datoms` AS `all_datoms00` \ @@ -582,6 +612,7 @@ fn test_order_by() { #[test] fn test_complex_nested_or_join_type_projection() { let mut schema = Schema::default(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("page", "title"), 98); add_attribute(&mut schema, 98, Attribute { value_type: ValueType::String, @@ -596,7 +627,7 @@ fn test_complex_nested_or_join_type_projection() { (or [_ :page/title ?y]))]"#; - let SQLQuery { sql, args } = translate(&schema, input); + let SQLQuery { sql, args } = translate(&schema, &partition_map, input); assert_eq!(sql, "SELECT `c00`.`?y` AS `?y` \ FROM (SELECT `datoms00`.v AS `?y` \ FROM `datoms` AS `datoms00` \ @@ -613,17 +644,18 @@ fn test_complex_nested_or_join_type_projection() { #[test] fn test_ground_scalar() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); // Verify that we accept inline constants. let query = r#"[:find ?x . :where [(ground "yyy") ?x]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT $v0 AS `?x` LIMIT 1"); assert_eq!(args, vec![make_arg("$v0", "yyy")]); // Verify that we accept bound input constants. let query = r#"[:find ?x . :in ?v :where [(ground ?v) ?x]]"#; let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?v"), TypedValue::String(Rc::new("aaa".into())))]); - let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); + let SQLQuery { sql, args } = translate_with_inputs(&schema, &partition_map, query, inputs); assert_eq!(sql, "SELECT $v0 AS `?x` LIMIT 1"); assert_eq!(args, vec![make_arg("$v0", "aaa"),]); } @@ -631,10 +663,11 @@ fn test_ground_scalar() { #[test] fn test_ground_tuple() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); // Verify that we accept inline constants. let query = r#"[:find ?x ?y :where [(ground [1 "yyy"]) [?x ?y]]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT 1 AS `?x`, $v0 AS `?y`"); assert_eq!(args, vec![make_arg("$v0", "yyy")]); @@ -642,7 +675,7 @@ fn test_ground_tuple() { let query = r#"[:find [?x ?y] :in ?u ?v :where [(ground [?u ?v]) [?x ?y]]]"#; let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?u"), TypedValue::Long(2)), (Variable::from_valid_name("?v"), TypedValue::String(Rc::new("aaa".into()))),]); - let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); + let SQLQuery { sql, args } = translate_with_inputs(&schema, &partition_map, query, inputs); // TODO: treat 2 as an input variable that could be bound late, rather than eagerly binding it. assert_eq!(sql, "SELECT 2 AS `?x`, $v0 AS `?y` LIMIT 1"); assert_eq!(args, vec![make_arg("$v0", "aaa"),]); @@ -651,10 +684,11 @@ fn test_ground_tuple() { #[test] fn test_ground_coll() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); // Verify that we accept inline constants. let query = r#"[:find ?x :where [(ground ["xxx" "yyy"]) [?x ...]]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `c00`.`?x` AS `?x` FROM \ (SELECT 0 AS `?x` WHERE 0 UNION ALL VALUES ($v0), ($v1)) AS `c00`"); assert_eq!(args, vec![make_arg("$v0", "xxx"), @@ -664,7 +698,7 @@ fn test_ground_coll() { let query = r#"[:find ?x :in ?u ?v :where [(ground [?u ?v]) [?x ...]]]"#; let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?u"), TypedValue::Long(2)), (Variable::from_valid_name("?v"), TypedValue::Long(3)),]); - let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); + let SQLQuery { sql, args } = translate_with_inputs(&schema, &partition_map, query, inputs); // TODO: treat 2 and 3 as input variables that could be bound late, rather than eagerly binding. assert_eq!(sql, "SELECT DISTINCT `c00`.`?x` AS `?x` FROM \ (SELECT 0 AS `?x` WHERE 0 UNION ALL VALUES (2), (3)) AS `c00`"); @@ -674,10 +708,11 @@ fn test_ground_coll() { #[test] fn test_ground_rel() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); // Verify that we accept inline constants. let query = r#"[:find ?x ?y :where [(ground [[1 "xxx"] [2 "yyy"]]) [[?x ?y]]]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `c00`.`?x` AS `?x`, `c00`.`?y` AS `?y` FROM \ (SELECT 0 AS `?x`, 0 AS `?y` WHERE 0 UNION ALL VALUES (1, $v0), (2, $v1)) AS `c00`"); assert_eq!(args, vec![make_arg("$v0", "xxx"), @@ -687,7 +722,7 @@ fn test_ground_rel() { let query = r#"[:find ?x ?y :in ?u ?v :where [(ground [[?u 1] [?v 2]]) [[?x ?y]]]]"#; let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?u"), TypedValue::Long(3)), (Variable::from_valid_name("?v"), TypedValue::Long(4)),]); - let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); + let SQLQuery { sql, args } = translate_with_inputs(&schema, &partition_map, query, inputs); // TODO: treat 3 and 4 as input variables that could be bound late, rather than eagerly binding. assert_eq!(sql, "SELECT DISTINCT `c00`.`?x` AS `?x`, `c00`.`?y` AS `?y` FROM \ (SELECT 0 AS `?x`, 0 AS `?y` WHERE 0 UNION ALL VALUES (3, 1), (4, 2)) AS `c00`"); @@ -697,11 +732,12 @@ fn test_ground_rel() { #[test] fn test_compound_with_ground() { let schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); // Verify that we can use the resulting CCs as children in compound CCs. let query = r#"[:find ?x :where (or [(ground "yyy") ?x] [(ground "zzz") ?x])]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); // This is confusing because the computed tables (like `c00`) are numbered sequentially in each // arm of the `or` rather than numbered globally. But SQLite scopes the names correctly, so it @@ -714,7 +750,7 @@ fn test_compound_with_ground() { // Verify that we can use ground to constrain the bindings produced by earlier clauses. let query = r#"[:find ?x . :where [_ :foo/bar ?x] [(ground "yyy") ?x]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT $v0 AS `?x` FROM `datoms` AS `datoms00` \ WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 1"); @@ -722,7 +758,7 @@ fn test_compound_with_ground() { // Verify that we can further constrain the bindings produced by our clause. let query = r#"[:find ?x . :where [(ground "yyy") ?x] [_ :foo/bar ?x]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT $v0 AS `?x` FROM `datoms` AS `datoms00` \ WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 1"); @@ -733,7 +769,8 @@ fn test_compound_with_ground() { fn test_unbound_attribute_with_ground_entity() { let query = r#"[:find ?x ?v :where [?x _ ?v] (not [(ground 17) ?x])]"#; let schema = prepopulated_schema(); - let SQLQuery { sql, .. } = translate(&schema, query); + let partition_map = PartitionMap::default(); + let SQLQuery { sql, .. } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `all_datoms00`.e AS `?x`, \ `all_datoms00`.v AS `?v`, \ `all_datoms00`.value_type_tag AS `?v_value_type_tag` \ @@ -745,7 +782,8 @@ fn test_unbound_attribute_with_ground_entity() { fn test_unbound_attribute_with_ground() { let query = r#"[:find ?x ?v :where [?x _ ?v] (not [(ground 17) ?v])]"#; let schema = prepopulated_schema(); - let SQLQuery { sql, .. } = translate(&schema, query); + let partition_map = PartitionMap::default(); + let SQLQuery { sql, .. } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `all_datoms00`.e AS `?x`, \ `all_datoms00`.v AS `?v`, \ `all_datoms00`.value_type_tag AS `?v_value_type_tag` \ @@ -758,6 +796,7 @@ fn test_unbound_attribute_with_ground() { #[test] fn test_not_with_ground() { let mut schema = prepopulated_schema(); + let partition_map = PartitionMap::default(); associate_ident(&mut schema, NamespacedKeyword::new("db", "valueType"), 7); associate_ident(&mut schema, NamespacedKeyword::new("db.type", "ref"), 23); associate_ident(&mut schema, NamespacedKeyword::new("db.type", "bool"), 28); @@ -771,7 +810,7 @@ fn test_not_with_ground() { // Scalar. // TODO: this kind of simple `not` should be implemented without the subquery. #476. let query = r#"[:find ?x :where [?x :db/valueType ?v] (not [(ground :db.type/instant) ?v])]"#; - let SQLQuery { sql, .. } = translate(&schema, query); + let SQLQuery { sql, .. } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 7 AND NOT \ EXISTS (SELECT 1 WHERE `datoms00`.v = 29)"); @@ -779,7 +818,7 @@ fn test_not_with_ground() { // Coll. // TODO: we can generate better SQL for this, too. #476. let query = r#"[:find ?x :where [?x :db/valueType ?v] (not [(ground [:db.type/bool :db.type/instant]) [?v ...]])]"#; - let SQLQuery { sql, .. } = translate(&schema, query); + let SQLQuery { sql, .. } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` \ WHERE `datoms00`.a = 7 AND NOT EXISTS \ @@ -790,9 +829,10 @@ fn test_not_with_ground() { #[test] fn test_fulltext() { let schema = prepopulated_typed_schema(ValueType::Double); + let partition_map = PartitionMap::default(); let query = r#"[:find ?entity ?value ?tx ?score :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `datoms01`.e AS `?entity`, \ `fulltext_values00`.text AS `?value`, \ `datoms01`.tx AS `?tx`, \ @@ -805,7 +845,7 @@ fn test_fulltext() { assert_eq!(args, vec![make_arg("$v0", "needle"),]); let query = r#"[:find ?entity ?value ?tx :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); // Observe that the computed table isn't dropped, even though `?score` isn't bound in the final conjoining clause. assert_eq!(sql, "SELECT DISTINCT `datoms01`.e AS `?entity`, \ `fulltext_values00`.text AS `?value`, \ @@ -818,7 +858,7 @@ fn test_fulltext() { assert_eq!(args, vec![make_arg("$v0", "needle"),]); let query = r#"[:find ?entity ?value ?tx :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx _]]]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); // Observe that the computed table isn't included at all when `?score` isn't bound. assert_eq!(sql, "SELECT DISTINCT `datoms01`.e AS `?entity`, \ `fulltext_values00`.text AS `?value`, \ @@ -831,7 +871,7 @@ fn test_fulltext() { assert_eq!(args, vec![make_arg("$v0", "needle"),]); let query = r#"[:find ?entity ?value ?tx :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]] [?entity :foo/bar ?score]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `datoms01`.e AS `?entity`, \ `fulltext_values00`.text AS `?value`, \ `datoms01`.tx AS `?tx` \ @@ -847,7 +887,7 @@ fn test_fulltext() { assert_eq!(args, vec![make_arg("$v0", "needle"),]); let query = r#"[:find ?entity ?value ?tx :where [?entity :foo/bar ?score] [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?entity`, \ `fulltext_values01`.text AS `?value`, \ `datoms02`.tx AS `?tx` \ @@ -866,6 +906,7 @@ fn test_fulltext() { #[test] fn test_fulltext_inputs() { let schema = prepopulated_typed_schema(ValueType::String); + let partition_map = PartitionMap::default(); // Bind ?entity. We expect the output to collide. let query = r#"[:find ?val @@ -876,7 +917,7 @@ fn test_fulltext_inputs() { let inputs = QueryInputs::new(types, BTreeMap::default()).expect("valid inputs"); // Without binding the value. q_once will err if you try this! - let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); + let SQLQuery { sql, args } = translate_with_inputs(&schema, &partition_map, query, inputs); assert_eq!(sql, "SELECT DISTINCT `fulltext_values00`.text AS `?val` \ FROM \ `fulltext_values` AS `fulltext_values00`, \ @@ -888,7 +929,7 @@ fn test_fulltext_inputs() { // With the value bound. let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?entity"), TypedValue::Ref(111))]); - let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); + let SQLQuery { sql, args } = translate_with_inputs(&schema, &partition_map, query, inputs); assert_eq!(sql, "SELECT DISTINCT `fulltext_values00`.text AS `?val` \ FROM \ `fulltext_values` AS `fulltext_values00`, \ @@ -904,7 +945,7 @@ fn test_fulltext_inputs() { :in ?entity :where [(fulltext $ :foo/fts "hello") [[?entity _ _]]]]"#; let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?entity"), TypedValue::Ref(111))]); - let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); + let SQLQuery { sql, args } = translate_with_inputs(&schema, &partition_map, query, inputs); assert_eq!(sql, "SELECT 111 AS `?entity` FROM \ `fulltext_values` AS `fulltext_values00`, \ `datoms` AS `datoms01` \ @@ -922,7 +963,7 @@ fn test_fulltext_inputs() { [(fulltext $ :foo/fts "hello") [[?entity ?value]]] [?entity :foo/bar ?friend]]"#; let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?entity"), TypedValue::Ref(121))]); - let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); + let SQLQuery { sql, args } = translate_with_inputs(&schema, &partition_map, query, inputs); assert_eq!(sql, "SELECT DISTINCT 121 AS `?entity`, \ `fulltext_values00`.text AS `?value`, \ `datoms02`.v AS `?friend` \ @@ -942,12 +983,13 @@ fn test_fulltext_inputs() { #[test] fn test_instant_range() { let schema = prepopulated_typed_schema(ValueType::Instant); + let partition_map = PartitionMap::default(); let query = r#"[:find ?e :where [?e :foo/bar ?t] [(> ?t #inst "2017-06-16T00:56:41.257Z")]]"#; - let SQLQuery { sql, args } = translate(&schema, query); + let SQLQuery { sql, args } = translate(&schema, &partition_map, query); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?e` \ FROM \ `datoms` AS `datoms00` \ diff --git a/src/conn.rs b/src/conn.rs index d6820fc0..495a628e 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -109,6 +109,11 @@ impl Conn { self.metadata.lock().unwrap().schema.clone() } + /// Yield the current partition_map instance. + pub fn current_partition_map(&self) -> PartitionMap { + self.metadata.lock().unwrap().partition_map.clone() + } + /// Query the Mentat store, using the given connection and the current metadata. pub fn q_once(&self, sqlite: &rusqlite::Connection, @@ -116,9 +121,9 @@ impl Conn { inputs: T) -> Result where T: Into> { - q_once(sqlite, &*self.current_schema(), + &self.current_partition_map(), query, inputs) } diff --git a/src/query.rs b/src/query.rs index 010f4485..d1764312 100644 --- a/src/query.rs +++ b/src/query.rs @@ -15,6 +15,8 @@ use mentat_core::{ Schema, }; +use mentat_db::PartitionMap; + use mentat_query_algebrizer::{ algebrize_with_inputs, }; @@ -59,15 +61,16 @@ pub type QueryExecutionResult = Result; /// instances. /// The caller is responsible for ensuring that the SQLite connection has an open transaction if /// isolation is required. -pub fn q_once<'sqlite, 'schema, 'query, T> +pub fn q_once<'sqlite, 'schema, 'partition, 'query, T> (sqlite: &'sqlite rusqlite::Connection, schema: &'schema Schema, + partition_map: &'partition PartitionMap, query: &'query str, inputs: T) -> QueryExecutionResult where T: Into> { let parsed = parse_find_string(query)?; - let algebrized = algebrize_with_inputs(schema, parsed, 0, inputs.into().unwrap_or(QueryInputs::default()))?; + let algebrized = algebrize_with_inputs(schema, partition_map, parsed, 0, inputs.into().unwrap_or(QueryInputs::default()))?; if algebrized.is_known_empty() { // We don't need to do any SQL work at all. diff --git a/tests/query.rs b/tests/query.rs index 6bf2853a..703491d1 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -51,7 +51,7 @@ fn test_rel() { // Rel. let start = time::PreciseTime::now(); - let results = q_once(&c, &db.schema, + let results = q_once(&c, &db.schema, &db.partition_map, "[:find ?x ?ident :where [?x :db/ident ?ident]]", None) .expect("Query failed"); let end = time::PreciseTime::now(); @@ -81,7 +81,7 @@ fn test_failing_scalar() { // Scalar that fails. let start = time::PreciseTime::now(); - let results = q_once(&c, &db.schema, + let results = q_once(&c, &db.schema, &db.partition_map, "[:find ?x . :where [?x :db/fulltext true]]", None) .expect("Query failed"); let end = time::PreciseTime::now(); @@ -103,7 +103,7 @@ fn test_scalar() { // Scalar that succeeds. let start = time::PreciseTime::now(); - let results = q_once(&c, &db.schema, + let results = q_once(&c, &db.schema, &db.partition_map, "[:find ?ident . :where [24 :db/ident ?ident]]", None) .expect("Query failed"); let end = time::PreciseTime::now(); @@ -130,7 +130,7 @@ fn test_tuple() { // Tuple. let start = time::PreciseTime::now(); - let results = q_once(&c, &db.schema, + let results = q_once(&c, &db.schema, &db.partition_map, "[:find [?index ?cardinality] :where [:db/txInstant :db/index ?index] [:db/txInstant :db/cardinality ?cardinality]]", @@ -160,7 +160,7 @@ fn test_coll() { // Coll. let start = time::PreciseTime::now(); - let results = q_once(&c, &db.schema, + let results = q_once(&c, &db.schema, &db.partition_map, "[:find [?e ...] :where [?e :db/ident _]]", None) .expect("Query failed"); let end = time::PreciseTime::now(); @@ -185,7 +185,7 @@ fn test_inputs() { // entids::DB_INSTALL_VALUE_TYPE = 5. let ee = (Variable::from_valid_name("?e"), TypedValue::Ref(5)); let inputs = QueryInputs::with_value_sequence(vec![ee]); - let results = q_once(&c, &db.schema, + let results = q_once(&c, &db.schema, &db.partition_map, "[:find ?i . :in ?e :where [?e :db/ident ?i]]", inputs) .expect("query to succeed"); @@ -205,7 +205,7 @@ fn test_unbound_inputs() { // Bind the wrong var by 'mistake'. let xx = (Variable::from_valid_name("?x"), TypedValue::Ref(5)); let inputs = QueryInputs::with_value_sequence(vec![xx]); - let results = q_once(&c, &db.schema, + let results = q_once(&c, &db.schema, &db.partition_map, "[:find ?i . :in ?e :where [?e :db/ident ?i]]", inputs); match results { @@ -321,6 +321,39 @@ fn test_tx_as_input() { } } +#[test] +fn test_tx_as_entid() { + let mut c = new_connection("").expect("Couldn't open conn."); + let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + conn.transact(&mut c, r#"[ + [:db/add "s" :db/ident :foo/uuid] + [:db/add "s" :db/valueType :db.type/uuid] + [:db/add "s" :db/cardinality :db.cardinality/one] + ]"#).expect("successful transaction"); + conn.transact(&mut c, r#"[ + [:db/add "u" :foo/uuid #uuid "550e8400-e29b-41d4-a716-446655440000"] + ]"#).expect("successful transaction"); + let t = conn.transact(&mut c, r#"[ + [:db/add "u" :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4"] + ]"#).expect("successful transaction"); + let temp_id = t.tempids.get("u").cloned().expect("u was mapped"); + conn.transact(&mut c, r#"[ + [:db/add "u" :foo/uuid #uuid "267bab92-ee39-4ca2-b7f0-1163a85af1fb"] + ]"#).expect("successful transaction"); + + let r = conn.q_once(&mut c, + &format!(r#"[:find ?x + :where [?x :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4" {}]]"#, t.tx_id), None); + match r { + Result::Ok(QueryResults::Rel(ref v)) => { + assert_eq!(*v, vec![ + vec![TypedValue::Ref(temp_id),] + ]); + }, + _ => panic!("Expected query to work."), + } +} + #[test] fn test_fulltext() { let mut c = new_connection("").expect("Couldn't open conn.");