mentat/query-algebrizer/src/clauses/not.rs

546 lines
25 KiB
Rust
Raw Normal View History

Parse and Algebrize `not` & `not-join`. (#302) (Closes #303, #389, #422 ) r=rnewman * Part 1 - Parse `not` and `not-join` * Part 2 - Validate `not` and `not-join` pre-algebrization * Address review comments rnewman. * Remove `WhereNotClause` and populate `NotJoin` with `WhereClause`. * Fix validation for `not` and `not-join`, removing tests that were invalid. * Address rustification comments. * Rebase against `rust` branch. * Part 3 - Add required types for NotJoin. * Implement `PartialEq` for `ConjoiningClauses` so `ComputedTable` can be included inside `ColumnConstraint::NotExists` * Part 4 - Implement `apply_not_join` * Part 5 - Call `apply_not_join` from inside `apply_clause` * Part 6 - Translate `not-join` into `NOT EXISTS` SQL * Address review comments. * Rename `projected` to `unified` to better describe the fact that we are not projecting any variables. * Check for presence of each unified var in either `column_bindings` or `input_bindings` and bail if not there. * Copy over `input_bindings` for each var in `unified`. * Only copy over the first `column_binding` for each variable in `unified` rather than the whole list. * Update tests. * Address review comments. * Make output from Debug for NotExists more useful * Clear up misunderstanding. Any single failing clause in the not will cause the entire not to be considered empty * Address review comments. * Remove Limit requirement from cc_to_exists. * Use Entry.or_insert instead of matching on the entry to add to column_bindings. * Move addition of value_bindings to before apply_clauses on template. * Tidy up tests with some variable reuse. * Addressed nits, * Address review comments. * Move addition of column_bindings to above apply_clause. * Update tests. * Add test to ensure that unbound vars fail * Improve test for unbound variable to check for correct variable and error * address nits
2017-04-28 09:44:11 +00:00
// Copyright 2016 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use mentat_core::Schema;
use mentat_query::{
ContainsVariables,
NotJoin,
UnifyVars,
};
use clauses::ConjoiningClauses;
use errors::{
ErrorKind,
Result,
};
use types::{
ColumnConstraint,
ComputedTable,
};
impl ConjoiningClauses {
pub fn apply_not_join(&mut self, schema: &Schema, not_join: NotJoin) -> Result<()> {
let unified = match not_join.unify_vars {
UnifyVars::Implicit => not_join.collect_mentioned_variables(),
UnifyVars::Explicit(vs) => vs.into_iter().collect(),
};
let mut template = self.use_as_template(&unified);
for v in unified.iter() {
if self.value_bindings.contains_key(&v) {
let val = self.value_bindings.get(&v).unwrap().clone();
template.value_bindings.insert(v.clone(), val);
} else if self.column_bindings.contains_key(&v) {
let col = self.column_bindings.get(&v).unwrap()[0].clone();
template.column_bindings.insert(v.clone(), vec![col]);
} else {
bail!(ErrorKind::UnboundVariable(v.name()));
}
}
for clause in not_join.clauses.into_iter() {
template.apply_clause(&schema, clause)?;
}
if template.is_known_empty() {
return Ok(());
}
// We are only expanding column bindings here and not pruning extracted types as we are not projecting values.
template.expand_column_bindings();
let subquery = ComputedTable::Subquery(template);
self.wheres.add_intersection(ColumnConstraint::NotExists(subquery));
Ok(())
}
}
#[cfg(test)]
mod testing {
extern crate mentat_query_parser;
use std::rc::Rc;
use std::collections::BTreeSet;
use super::*;
use mentat_core::{
Attribute,
TypedValue,
ValueType,
};
use mentat_query::{
NamespacedKeyword,
PlainSymbol,
Variable
};
use self::mentat_query_parser::parse_find_string;
use clauses::{
QueryInputs,
add_attribute,
associate_ident,
};
use errors::{
Error,
ErrorKind,
};
use types::{
ColumnAlternation,
ColumnConstraint,
ColumnConstraintOrAlternation,
ColumnIntersection,
DatomsColumn,
DatomsTable,
NumericComparison,
QualifiedAlias,
QueryValue,
SourceAlias,
ValueTypeSet,
};
use {
algebrize,
algebrize_with_inputs,
};
fn alg(schema: &Schema, input: &str) -> ConjoiningClauses {
let parsed = parse_find_string(input).expect("parse failed");
algebrize(schema.into(), parsed).expect("algebrize failed").cc
}
fn alg_with_inputs(schema: &Schema, 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
}
fn prepopulated_schema() -> Schema {
let mut schema = Schema::default();
associate_ident(&mut schema, NamespacedKeyword::new("foo", "name"), 65);
associate_ident(&mut schema, NamespacedKeyword::new("foo", "knows"), 66);
associate_ident(&mut schema, NamespacedKeyword::new("foo", "parent"), 67);
associate_ident(&mut schema, NamespacedKeyword::new("foo", "age"), 68);
associate_ident(&mut schema, NamespacedKeyword::new("foo", "height"), 69);
add_attribute(&mut schema,
65,
Attribute {
value_type: ValueType::String,
multival: false,
..Default::default()
});
add_attribute(&mut schema,
66,
Attribute {
value_type: ValueType::String,
multival: true,
..Default::default()
});
add_attribute(&mut schema,
67,
Attribute {
value_type: ValueType::String,
multival: true,
..Default::default()
});
add_attribute(&mut schema,
68,
Attribute {
value_type: ValueType::Long,
multival: false,
..Default::default()
});
add_attribute(&mut schema,
69,
Attribute {
value_type: ValueType::Long,
multival: false,
..Default::default()
});
schema
}
fn compare_ccs(left: ConjoiningClauses, right: ConjoiningClauses) {
assert_eq!(left.wheres, right.wheres);
assert_eq!(left.from, right.from);
}
// not.
#[test]
fn test_successful_not() {
let schema = prepopulated_schema();
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 vx = Variable::from_valid_name("?x");
let d0 = "datoms00".to_string();
let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
let d0v = QualifiedAlias::new(d0.clone(), DatomsColumn::Value);
let d1 = "datoms01".to_string();
let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias::new(d1.clone(), DatomsColumn::Attribute);
let d1v = QualifiedAlias::new(d1.clone(), DatomsColumn::Value);
let d2 = "datoms02".to_string();
let d2e = QualifiedAlias::new(d2.clone(), DatomsColumn::Entity);
let d2a = QualifiedAlias::new(d2.clone(), DatomsColumn::Attribute);
let d2v = QualifiedAlias::new(d2.clone(), DatomsColumn::Value);
let knows = QueryValue::Entid(66);
let parent = QueryValue::Entid(67);
let john = QueryValue::TypedValue(TypedValue::typed_string("John"));
let ambar = QueryValue::TypedValue(TypedValue::typed_string("Ámbar"));
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
let mut subquery = ConjoiningClauses::default();
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1),
SourceAlias(DatomsTable::Datoms, d2)];
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), parent)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), ambar)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), knows.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), daphne)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone())))]);
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), john)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))),
]));
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e]));
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]);
}
// not-join.
#[test]
fn test_successful_not_join() {
let schema = prepopulated_schema();
let query = r#"
[:find ?x
:where [?x :foo/knows ?y]
[?x :foo/age 11]
[?x :foo/name "John"]
(not-join [?x ?y]
[?x :foo/parent ?y])]"#;
let cc = alg(&schema, query);
let vx = Variable::from_valid_name("?x");
let vy = Variable::from_valid_name("?y");
let d0 = "datoms00".to_string();
let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
let d0v = QualifiedAlias::new(d0.clone(), DatomsColumn::Value);
let d1 = "datoms01".to_string();
let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias::new(d1.clone(), DatomsColumn::Attribute);
let d1v = QualifiedAlias::new(d1.clone(), DatomsColumn::Value);
let d2 = "datoms02".to_string();
let d2e = QualifiedAlias::new(d2.clone(), DatomsColumn::Entity);
let d2a = QualifiedAlias::new(d2.clone(), DatomsColumn::Attribute);
let d2v = QualifiedAlias::new(d2.clone(), DatomsColumn::Value);
let d3 = "datoms03".to_string();
let d3e = QualifiedAlias::new(d3.clone(), DatomsColumn::Entity);
let d3a = QualifiedAlias::new(d3.clone(), DatomsColumn::Attribute);
let d3v = QualifiedAlias::new(d3.clone(), DatomsColumn::Value);
let name = QueryValue::Entid(65);
let knows = QueryValue::Entid(66);
let parent = QueryValue::Entid(67);
let age = QueryValue::Entid(68);
let john = QueryValue::TypedValue(TypedValue::typed_string("John"));
let eleven = QueryValue::PrimitiveLong(11);
let mut subquery = ConjoiningClauses::default();
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d3)];
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d3e.clone()]);
subquery.column_bindings.insert(vy.clone(), vec![d0v.clone(), d3v.clone()]);
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d3a.clone(), parent)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d3e.clone()))),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), QueryValue::Column(d3v.clone())))]);
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
subquery.known_types.insert(vy.clone(), ValueTypeSet::of_one(ValueType::String));
assert!(!cc.is_known_empty());
let expected_wheres = ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), age.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), eleven)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), name.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), john)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone()))),
]);
assert_eq!(cc.wheres, expected_wheres);
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e, d2e]));
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0),
SourceAlias(DatomsTable::Datoms, d1),
SourceAlias(DatomsTable::Datoms, d2)]);
}
// Not with a pattern and a predicate.
#[test]
fn test_not_with_pattern_and_predicate() {
let schema = prepopulated_schema();
let query = r#"
[:find ?x ?age
:where
[?x :foo/age ?age]
[[< ?age 30]]
(not [?x :foo/knows "John"]
[?x :foo/knows "Daphne"])]"#;
let cc = alg(&schema, query);
let vx = Variable::from_valid_name("?x");
let d0 = "datoms00".to_string();
let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
let d0v = QualifiedAlias::new(d0.clone(), DatomsColumn::Value);
let d1 = "datoms01".to_string();
let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias::new(d1.clone(), DatomsColumn::Attribute);
let d1v = QualifiedAlias::new(d1.clone(), DatomsColumn::Value);
let d2 = "datoms02".to_string();
let d2e = QualifiedAlias::new(d2.clone(), DatomsColumn::Entity);
let d2a = QualifiedAlias::new(d2.clone(), DatomsColumn::Attribute);
let d2v = QualifiedAlias::new(d2.clone(), DatomsColumn::Value);
let knows = QueryValue::Entid(66);
let age = QueryValue::Entid(68);
let john = QueryValue::TypedValue(TypedValue::typed_string("John"));
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
let mut subquery = ConjoiningClauses::default();
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1),
SourceAlias(DatomsTable::Datoms, d2)];
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), knows.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), daphne.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone())))]);
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), age.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NumericInequality {
operator: NumericComparison::LessThan,
left: QueryValue::Column(d0v.clone()),
right: QueryValue::TypedValue(TypedValue::Long(30)),
}),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))),
]));
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e]));
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]);
}
// not with an or
#[test]
fn test_not_with_or() {
let schema = prepopulated_schema();
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 d0 = "datoms00".to_string();
let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
let d0v = QualifiedAlias::new(d0.clone(), DatomsColumn::Value);
let d1 = "datoms01".to_string();
let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias::new(d1.clone(), DatomsColumn::Attribute);
let d1v = QualifiedAlias::new(d1.clone(), DatomsColumn::Value);
let d2 = "datoms02".to_string();
let d2e = QualifiedAlias::new(d2.clone(), DatomsColumn::Entity);
let d2a = QualifiedAlias::new(d2.clone(), DatomsColumn::Attribute);
let d2v = QualifiedAlias::new(d2.clone(), DatomsColumn::Value);
let vx = Variable::from_valid_name("?x");
let knows = QueryValue::Entid(66);
let parent = QueryValue::Entid(67);
let bill = QueryValue::TypedValue(TypedValue::typed_string("Bill"));
let john = QueryValue::TypedValue(TypedValue::typed_string("John"));
let ambar = QueryValue::TypedValue(TypedValue::typed_string("Ámbar"));
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
let mut subquery = ConjoiningClauses::default();
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1),
SourceAlias(DatomsTable::Datoms, d2)];
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![
ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john))]),
ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), ambar))]),
])),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), parent)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), daphne)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone())))]);
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), bill)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))),
]));
}
// not-join with an input variable
#[test]
fn test_not_with_in() {
let schema = prepopulated_schema();
let query = r#"
[:find ?x
:in ?y
:where [?x :foo/knows "Bill"]
(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 vx = Variable::from_valid_name("?x");
let vy = Variable::from_valid_name("?y");
let knows = QueryValue::Entid(66);
let bill = QueryValue::TypedValue(TypedValue::typed_string("Bill"));
let john = QueryValue::TypedValue(TypedValue::typed_string("John"));
let d0 = "datoms00".to_string();
let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
let d0v = QualifiedAlias::new(d0.clone(), DatomsColumn::Value);
let d1 = "datoms01".to_string();
let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias::new(d1.clone(), DatomsColumn::Attribute);
let d1v = QualifiedAlias::new(d1.clone(), DatomsColumn::Value);
let mut subquery = ConjoiningClauses::default();
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1)];
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone()]);
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone())))]);
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
subquery.known_types.insert(vy.clone(), ValueTypeSet::of_one(ValueType::String));
let mut input_vars: BTreeSet<Variable> = BTreeSet::default();
input_vars.insert(vy.clone());
subquery.input_variables = input_vars;
subquery.value_bindings.insert(vy.clone(), TypedValue::typed_string("John"));
assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), bill)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))),
]));
}
// Test that if any single clause in the `not` fails to resolve the whole clause is considered empty
#[test]
fn test_fails_if_any_clause_invalid() {
let schema = prepopulated_schema();
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);
assert!(!cc.is_known_empty());
compare_ccs(cc,
alg(&schema,
r#"[:find ?x :where [?x :foo/knows "Bill"]]"#));
}
/// Test that if all the attributes in an `not` fail to resolve, the `cc` isn't considered empty.
#[test]
fn test_no_clauses_succeed() {
let schema = prepopulated_schema();
let query = r#"
[:find ?x
:where [?x :foo/knows "John"]
(not [?x :foo/nope "Ámbar"]
[?x :foo/nope "Daphne"])]"#;
let cc = alg(&schema, query);
assert!(!cc.is_known_empty());
compare_ccs(cc,
alg(&schema, r#"[:find ?x :where [?x :foo/knows "John"]]"#));
}
#[test]
fn test_unbound_var_fails() {
let schema = prepopulated_schema();
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();
assert!(err.is_some());
match err.unwrap() {
Error(ErrorKind::UnboundVariable(var), _) => { assert_eq!(var, PlainSymbol("?x".to_string())); },
x => panic!("expected Unbound Variable error, got {:?}", x),
}
}
}