bd389d2f0d
* 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
545 lines
25 KiB
Rust
545 lines
25 KiB
Rust
// 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),
|
|
}
|
|
}
|
|
}
|