Compare commits
3 commits
master
...
fluffyemil
Author | SHA1 | Date | |
---|---|---|---|
|
7231969ae8 | ||
|
58e43a878a | ||
|
9c677a5bb1 |
16 changed files with 473 additions and 204 deletions
|
@ -10,6 +10,9 @@ error-chain = "0.8.1"
|
|||
[dependencies.mentat_core]
|
||||
path = "../core"
|
||||
|
||||
[dependencies.mentat_db]
|
||||
path = "../db"
|
||||
|
||||
[dependencies.mentat_query]
|
||||
path = "../query"
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@ use mentat_core::{
|
|||
|
||||
use mentat_core::counter::RcCounter;
|
||||
|
||||
use mentat_db::PartitionMap;
|
||||
|
||||
use mentat_query::{
|
||||
NamespacedKeyword,
|
||||
NonIntegerConstant,
|
||||
|
@ -611,13 +613,8 @@ impl ConjoiningClauses {
|
|||
}
|
||||
|
||||
/// Ensure that the given place has the correct types to be a tx-id.
|
||||
/// Right now this is mostly unimplemented: we fail hard if anything but a placeholder is
|
||||
/// present.
|
||||
fn constrain_to_tx(&mut self, tx: &PatternNonValuePlace) {
|
||||
match *tx {
|
||||
PatternNonValuePlace::Placeholder => (),
|
||||
_ => unimplemented!(), // TODO: #440.
|
||||
}
|
||||
self.constrain_to_ref(tx);
|
||||
}
|
||||
|
||||
/// Ensure that the given place can be an entity, and is congruent with existing types.
|
||||
|
@ -648,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<DatomsTable, EmptyBecause> {
|
||||
if attribute.fulltext {
|
||||
match value {
|
||||
|
@ -867,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) => {
|
||||
|
@ -881,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!(),
|
||||
}
|
||||
|
|
|
@ -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())); },
|
||||
|
|
|
@ -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<Pattern>,
|
||||
mentioned_vars: BTreeSet<Variable>)
|
||||
-> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -81,7 +83,7 @@ impl ConjoiningClauses {
|
|||
// Sorry for the duplication; Rust makes it a pain to abstract this.
|
||||
|
||||
// The transaction part of a pattern must be an entid, variable, or placeholder.
|
||||
self.constrain_to_tx(&pattern.tx); // See #440.
|
||||
self.constrain_to_tx(&pattern.tx);
|
||||
self.constrain_to_ref(&pattern.entity);
|
||||
self.constrain_to_ref(&pattern.attribute);
|
||||
|
||||
|
@ -238,12 +240,42 @@ impl ConjoiningClauses {
|
|||
if value_type.is_none() {
|
||||
self.wheres.add_intersection(ColumnConstraint::HasType(col.clone(), typed_value_type));
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
match pattern.tx {
|
||||
PatternNonValuePlace::Placeholder => (),
|
||||
PatternNonValuePlace::Variable(ref v) => {
|
||||
self.bind_column_to_var(schema, col.clone(), DatomsColumn::Tx, v.clone());
|
||||
},
|
||||
PatternNonValuePlace::Entid(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()) {
|
||||
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()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 => (),
|
||||
|
@ -251,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
|
||||
|
@ -307,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"),
|
||||
|
@ -332,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"),
|
||||
|
@ -350,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 {
|
||||
|
@ -358,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"),
|
||||
|
@ -396,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,
|
||||
|
@ -435,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,
|
||||
|
@ -447,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()),
|
||||
|
@ -482,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");
|
||||
|
@ -490,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()),
|
||||
|
@ -508,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()),
|
||||
|
@ -540,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,
|
||||
|
@ -578,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);
|
||||
|
@ -592,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"),
|
||||
|
@ -650,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 {
|
||||
|
@ -666,7 +708,7 @@ mod testing {
|
|||
let variables: BTreeSet<Variable> = 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"),
|
||||
|
@ -697,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 {
|
||||
|
@ -713,7 +756,7 @@ mod testing {
|
|||
let variables: BTreeSet<Variable> = 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"),
|
||||
|
@ -730,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 {
|
||||
|
@ -747,7 +791,7 @@ mod testing {
|
|||
let variables: BTreeSet<Variable> = 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"),
|
||||
|
@ -766,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);
|
||||
|
@ -781,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"),
|
||||
|
@ -816,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]
|
||||
|
@ -823,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()),
|
||||
|
@ -854,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,
|
||||
|
@ -861,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));
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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<AlgebraicQuery> {
|
||||
algebrize_with_inputs(schema, parsed, counter, QueryInputs::default())
|
||||
pub fn algebrize_with_counter(schema: &Schema, partition_map: &PartitionMap, parsed: FindQuery, counter: usize) -> Result<AlgebraicQuery> {
|
||||
algebrize_with_inputs(schema, partition_map, parsed, counter, QueryInputs::default())
|
||||
}
|
||||
|
||||
pub fn algebrize(schema: &Schema, parsed: FindQuery) -> Result<AlgebraicQuery> {
|
||||
algebrize_with_inputs(schema, parsed, 0, QueryInputs::default())
|
||||
pub fn algebrize(schema: &Schema, partition_map: &PartitionMap, parsed: FindQuery) -> Result<AlgebraicQuery> {
|
||||
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<AlgebraicQuery> {
|
|||
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<AlgebraicQuery> {
|
||||
|
@ -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();
|
||||
|
|
|
@ -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")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -7,6 +7,9 @@ workspace = ".."
|
|||
[dependencies.mentat_core]
|
||||
path = "../core"
|
||||
|
||||
[dependencies.mentat_db]
|
||||
path = "../db"
|
||||
|
||||
[dependencies.mentat_sql]
|
||||
path = "../sql"
|
||||
|
||||
|
|
|
@ -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<mentat_sql::
|
|||
#[test]
|
||||
fn test_scalar() {
|
||||
let schema = prepopulated_schema();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
let query = r#"[:find ?x . :where [?x :foo/bar "yyy"]]"#;
|
||||
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 = $v0 LIMIT 1");
|
||||
assert_eq!(args, vec![make_arg("$v0", "yyy")]);
|
||||
}
|
||||
|
@ -102,9 +106,10 @@ fn test_scalar() {
|
|||
#[test]
|
||||
fn test_tuple() {
|
||||
let schema = prepopulated_schema();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
let query = r#"[:find [?x] :where [?x :foo/bar "yyy"]]"#;
|
||||
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 = $v0 LIMIT 1");
|
||||
assert_eq!(args, vec![make_arg("$v0", "yyy")]);
|
||||
}
|
||||
|
@ -112,9 +117,10 @@ fn test_tuple() {
|
|||
#[test]
|
||||
fn test_coll() {
|
||||
let schema = prepopulated_schema();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
let query = r#"[:find [?x ...] :where [?x :foo/bar "yyy"]]"#;
|
||||
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 = $v0");
|
||||
assert_eq!(args, vec![make_arg("$v0", "yyy")]);
|
||||
}
|
||||
|
@ -122,9 +128,10 @@ fn test_coll() {
|
|||
#[test]
|
||||
fn test_rel() {
|
||||
let schema = prepopulated_schema();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
let query = r#"[:find ?x :where [?x :foo/bar "yyy"]]"#;
|
||||
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 = $v0");
|
||||
assert_eq!(args, vec![make_arg("$v0", "yyy")]);
|
||||
}
|
||||
|
@ -132,9 +139,10 @@ fn test_rel() {
|
|||
#[test]
|
||||
fn test_limit() {
|
||||
let schema = prepopulated_schema();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
let query = r#"[:find ?x :where [?x :foo/bar "yyy"] :limit 5]"#;
|
||||
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 = $v0 LIMIT 5");
|
||||
assert_eq!(args, vec![make_arg("$v0", "yyy")]);
|
||||
}
|
||||
|
@ -142,11 +150,12 @@ fn test_limit() {
|
|||
#[test]
|
||||
fn test_unbound_variable_limit() {
|
||||
let schema = prepopulated_schema();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
// We don't know the value of the limit var, so we produce an escaped SQL variable to handle
|
||||
// later input.
|
||||
let query = r#"[:find ?x :in ?limit-is-9-great :where [?x :foo/bar "yyy"] :limit ?limit-is-9-great]"#;
|
||||
let SQLQuery { sql, args } = translate_with_inputs(&schema, query, QueryInputs::default());
|
||||
let SQLQuery { sql, args } = translate_with_inputs(&schema, &partition_map, query, QueryInputs::default());
|
||||
assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \
|
||||
FROM `datoms` AS `datoms00` \
|
||||
WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 \
|
||||
|
@ -157,11 +166,12 @@ fn test_unbound_variable_limit() {
|
|||
#[test]
|
||||
fn test_bound_variable_limit() {
|
||||
let schema = prepopulated_schema();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
// We know the value of `?limit` at algebrizing time, so we substitute directly.
|
||||
let query = r#"[:find ?x :in ?limit :where [?x :foo/bar "yyy"] :limit ?limit]"#;
|
||||
let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?limit"), TypedValue::Long(92))]);
|
||||
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 `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 92");
|
||||
assert_eq!(args, vec![make_arg("$v0", "yyy")]);
|
||||
}
|
||||
|
@ -169,12 +179,13 @@ fn test_bound_variable_limit() {
|
|||
#[test]
|
||||
fn test_bound_variable_limit_affects_distinct() {
|
||||
let schema = prepopulated_schema();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
// We know the value of `?limit` at algebrizing time, so we substitute directly.
|
||||
// As it's `1`, we know we don't need `DISTINCT`!
|
||||
let query = r#"[:find ?x :in ?limit :where [?x :foo/bar "yyy"] :limit ?limit]"#;
|
||||
let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?limit"), TypedValue::Long(1))]);
|
||||
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 `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 1");
|
||||
assert_eq!(args, vec![make_arg("$v0", "yyy")]);
|
||||
}
|
||||
|
@ -182,10 +193,11 @@ fn test_bound_variable_limit_affects_distinct() {
|
|||
#[test]
|
||||
fn test_bound_variable_limit_affects_types() {
|
||||
let schema = prepopulated_schema();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
let query = r#"[:find ?x ?limit :in ?limit :where [?x _ ?limit] :limit ?limit]"#;
|
||||
let parsed = parse_find_string(query).expect("parse failed");
|
||||
let algebrized = algebrize(&schema, parsed).expect("algebrize failed");
|
||||
let algebrized = algebrize(&schema, &partition_map, parsed).expect("algebrize failed");
|
||||
|
||||
// The type is known.
|
||||
assert_eq!(Some(ValueType::Long),
|
||||
|
@ -204,9 +216,10 @@ fn test_bound_variable_limit_affects_types() {
|
|||
#[test]
|
||||
fn test_unknown_attribute_keyword_value() {
|
||||
let schema = Schema::default();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
let query = r#"[:find ?x :where [?x _ :ab/yyy]]"#;
|
||||
let SQLQuery { sql, args } = translate(&schema, query);
|
||||
let SQLQuery { sql, args } = translate(&schema, &partition_map, query);
|
||||
|
||||
// Only match keywords, not strings: tag = 13.
|
||||
assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.v = $v0 AND `datoms00`.value_type_tag = 13");
|
||||
|
@ -216,9 +229,10 @@ fn test_unknown_attribute_keyword_value() {
|
|||
#[test]
|
||||
fn test_unknown_attribute_string_value() {
|
||||
let schema = Schema::default();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
let query = r#"[:find ?x :where [?x _ "horses"]]"#;
|
||||
let SQLQuery { sql, args } = translate(&schema, query);
|
||||
let SQLQuery { sql, args } = translate(&schema, &partition_map, query);
|
||||
|
||||
// We expect all_datoms because we're querying for a string. Magic, that.
|
||||
// We don't want keywords etc., so tag = 10.
|
||||
|
@ -229,9 +243,10 @@ fn test_unknown_attribute_string_value() {
|
|||
#[test]
|
||||
fn test_unknown_attribute_double_value() {
|
||||
let schema = Schema::default();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
let query = r#"[:find ?x :where [?x _ 9.95]]"#;
|
||||
let SQLQuery { sql, args } = translate(&schema, query);
|
||||
let SQLQuery { sql, args } = translate(&schema, &partition_map, query);
|
||||
|
||||
// In general, doubles _could_ be 1.0, which might match a boolean or a ref. Set tag = 5 to
|
||||
// make sure we only match numbers.
|
||||
|
@ -242,6 +257,7 @@ fn test_unknown_attribute_double_value() {
|
|||
#[test]
|
||||
fn test_unknown_attribute_integer_value() {
|
||||
let schema = Schema::default();
|
||||
let partition_map = PartitionMap::default();
|
||||
|
||||
let negative = r#"[:find ?x :where [?x _ -1]]"#;
|
||||
let zero = r#"[:find ?x :where [?x _ 0]]"#;
|
||||
|
@ -249,22 +265,22 @@ fn test_unknown_attribute_integer_value() {
|
|||
let two = r#"[:find ?x :where [?x _ 2]]"#;
|
||||
|
||||
// Can't match boolean; no need to filter it out.
|
||||
let SQLQuery { sql, args } = translate(&schema, negative);
|
||||
let SQLQuery { sql, args } = translate(&schema, &partition_map, negative);
|
||||
assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.v = -1");
|
||||
assert_eq!(args, vec![]);
|
||||
|
||||
// Excludes booleans.
|
||||
let SQLQuery { sql, args } = translate(&schema, zero);
|
||||
let SQLQuery { sql, args } = translate(&schema, &partition_map, zero);
|
||||
assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE (`datoms00`.v = 0 AND `datoms00`.value_type_tag <> 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` \
|
||||
|
|
|
@ -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<T>(&self,
|
||||
sqlite: &rusqlite::Connection,
|
||||
|
@ -116,9 +121,9 @@ impl Conn {
|
|||
inputs: T) -> Result<QueryResults>
|
||||
where T: Into<Option<QueryInputs>>
|
||||
{
|
||||
|
||||
q_once(sqlite,
|
||||
&*self.current_schema(),
|
||||
&self.current_partition_map(),
|
||||
query,
|
||||
inputs)
|
||||
}
|
||||
|
|
|
@ -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<QueryResults>;
|
|||
/// 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<Option<QueryInputs>>
|
||||
{
|
||||
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.
|
||||
|
|
117
tests/query.rs
117
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 {
|
||||
|
@ -232,11 +232,9 @@ fn test_instants_and_uuids() {
|
|||
conn.transact(&mut c, r#"[
|
||||
[:db/add "u" :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4"]
|
||||
]"#).unwrap();
|
||||
|
||||
// We don't yet support getting the tx from a pattern (#440), so run wild.
|
||||
let r = conn.q_once(&mut c,
|
||||
r#"[:find [?x ?u ?when]
|
||||
:where [?x :foo/uuid ?u]
|
||||
:where [?x :foo/uuid ?u ?tx]
|
||||
[?tx :db/txInstant ?when]]"#, None);
|
||||
match r {
|
||||
Result::Ok(QueryResults::Tuple(Some(vals))) => {
|
||||
|
@ -257,6 +255,105 @@ fn test_instants_and_uuids() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tx() {
|
||||
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");
|
||||
|
||||
let t = conn.transact(&mut c, r#"[
|
||||
[:db/add "u" :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4"]
|
||||
]"#).expect("successful transaction");
|
||||
|
||||
conn.transact(&mut c, r#"[
|
||||
[:db/add "u" :foo/uuid #uuid "550e8400-e29b-41d4-a716-446655440000"]
|
||||
]"#).expect("successful transaction");
|
||||
|
||||
let r = conn.q_once(&mut c,
|
||||
r#"[:find ?tx
|
||||
:where [?x :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4" ?tx]]"#, None);
|
||||
match r {
|
||||
Result::Ok(QueryResults::Rel(ref v)) => {
|
||||
assert_eq!(*v, vec![
|
||||
vec![TypedValue::Ref(t.tx_id),]
|
||||
]);
|
||||
},
|
||||
_ => panic!("Expected query to work."),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tx_as_input() {
|
||||
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");
|
||||
conn.transact(&mut c, r#"[
|
||||
[:db/add "u" :foo/uuid #uuid "267bab92-ee39-4ca2-b7f0-1163a85af1fb"]
|
||||
]"#).expect("successful transaction");
|
||||
|
||||
let tx = (Variable::from_valid_name("?tx"), TypedValue::Ref(t.tx_id));
|
||||
let inputs = QueryInputs::with_value_sequence(vec![tx]);
|
||||
let r = conn.q_once(&mut c,
|
||||
r#"[:find ?uuid
|
||||
:in ?tx
|
||||
:where [?x :foo/uuid ?uuid ?tx]]"#, inputs);
|
||||
match r {
|
||||
Result::Ok(QueryResults::Rel(ref v)) => {
|
||||
assert_eq!(*v, vec![
|
||||
vec![TypedValue::Uuid(Uuid::from_str("cf62d552-6569-4d1b-b667-04703041dfc4").expect("Valid UUID")),]
|
||||
]);
|
||||
},
|
||||
_ => panic!("Expected query to work."),
|
||||
}
|
||||
}
|
||||
|
||||
#[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.");
|
||||
|
|
Loading…
Reference in a new issue