Compare commits

...

3 commits

Author SHA1 Message Date
Emily Toop
7231969ae8 Provide query with db partition map so we can check that tx_ids are in range 2017-06-23 14:44:02 +01:00
Emily Toop
58e43a878a Address review comments r=rnewman 2017-06-23 14:40:09 +01:00
Emily Toop
9c677a5bb1 Support tx places in queries 2017-06-21 11:42:44 +01:00
16 changed files with 473 additions and 204 deletions

View file

@ -10,6 +10,9 @@ error-chain = "0.8.1"
[dependencies.mentat_core]
path = "../core"
[dependencies.mentat_db]
path = "../db"
[dependencies.mentat_query]
path = "../query"

View file

@ -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!(),
}

View file

@ -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())); },

View file

@ -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));
}
}

View file

@ -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));

View file

@ -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"),

View file

@ -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();

View file

@ -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")
},
}
}
}

View file

@ -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());
}

View file

@ -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");

View file

@ -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);

View file

@ -7,6 +7,9 @@ workspace = ".."
[dependencies.mentat_core]
path = "../core"
[dependencies.mentat_db]
path = "../db"
[dependencies.mentat_sql]
path = "../sql"

View file

@ -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` \

View file

@ -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)
}

View file

@ -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.

View file

@ -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.");