Support input bindings in ConjoiningClauses. r=nalexander
This commit is contained in:
parent
914902cf9e
commit
6109a63249
2 changed files with 218 additions and 47 deletions
|
@ -18,7 +18,11 @@ use std::fmt::{
|
||||||
Formatter,
|
Formatter,
|
||||||
Result,
|
Result,
|
||||||
};
|
};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{
|
||||||
|
BTreeMap,
|
||||||
|
BTreeSet,
|
||||||
|
};
|
||||||
|
|
||||||
use std::collections::btree_map::Entry;
|
use std::collections::btree_map::Entry;
|
||||||
|
|
||||||
use self::mentat_core::{
|
use self::mentat_core::{
|
||||||
|
@ -198,6 +202,7 @@ impl Debug for ColumnConstraint {
|
||||||
pub struct ConjoiningClauses {
|
pub struct ConjoiningClauses {
|
||||||
/// `true` if this set of clauses cannot yield results in the context of the current schema.
|
/// `true` if this set of clauses cannot yield results in the context of the current schema.
|
||||||
pub is_known_empty: bool,
|
pub is_known_empty: bool,
|
||||||
|
pub empty_because: Option<EmptyBecause>,
|
||||||
|
|
||||||
/// A function used to generate an alias for a table -- e.g., from "datoms" to "datoms123".
|
/// A function used to generate an alias for a table -- e.g., from "datoms" to "datoms123".
|
||||||
aliaser: TableAliaser,
|
aliaser: TableAliaser,
|
||||||
|
@ -209,25 +214,82 @@ pub struct ConjoiningClauses {
|
||||||
pub wheres: Vec<ColumnConstraint>,
|
pub wheres: Vec<ColumnConstraint>,
|
||||||
|
|
||||||
/// A map from var to qualified columns. Used to project.
|
/// A map from var to qualified columns. Used to project.
|
||||||
pub bindings: BTreeMap<Variable, Vec<QualifiedAlias>>,
|
pub column_bindings: BTreeMap<Variable, Vec<QualifiedAlias>>,
|
||||||
|
|
||||||
|
/// A list of variables mentioned in the enclosing query's :in clause. These must all be bound
|
||||||
|
/// before the query can be executed. TODO: clarify what this means for nested CCs.
|
||||||
|
pub input_variables: BTreeSet<Variable>,
|
||||||
|
|
||||||
|
/// In some situations -- e.g., when a query is being run only once -- we know in advance the
|
||||||
|
/// values bound to some or all variables. These can be substituted directly when the query is
|
||||||
|
/// algebrized.
|
||||||
|
///
|
||||||
|
/// Value bindings must agree with `known_types`. If you write a query like
|
||||||
|
/// ```edn
|
||||||
|
/// [:find ?x :in $ ?val :where [?x :foo/int ?val]]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// and for `?val` provide `TypedValue::String("foo".to_string())`, the query will be known at
|
||||||
|
/// algebrizing time to be empty.
|
||||||
|
value_bindings: BTreeMap<Variable, TypedValue>,
|
||||||
|
|
||||||
/// A map from var to type. Whenever a var maps unambiguously to two different types, it cannot
|
/// A map from var to type. Whenever a var maps unambiguously to two different types, it cannot
|
||||||
/// yield results, so we don't represent that case here. If a var isn't present in the map, it
|
/// yield results, so we don't represent that case here. If a var isn't present in the map, it
|
||||||
/// means that its type is not known in advance.
|
/// means that its type is not known in advance.
|
||||||
pub known_types: BTreeMap<Variable, ValueType>,
|
pub known_types: BTreeMap<Variable, ValueType>,
|
||||||
|
|
||||||
/// A mapping, similar to `bindings`, but used to pull type tags out of the store at runtime.
|
/// A mapping, similar to `column_bindings`, but used to pull type tags out of the store at runtime.
|
||||||
/// If a var isn't present in `known_types`, it should be present here.
|
/// If a var isn't present in `known_types`, it should be present here.
|
||||||
extracted_types: BTreeMap<Variable, QualifiedAlias>,
|
extracted_types: BTreeMap<Variable, QualifiedAlias>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum EmptyBecause {
|
||||||
|
// Var, existing, desired.
|
||||||
|
TypeMismatch(Variable, ValueType, ValueType),
|
||||||
|
UnresolvedIdent(NamespacedKeyword),
|
||||||
|
InvalidAttributeIdent(NamespacedKeyword),
|
||||||
|
InvalidAttributeEntid(Entid),
|
||||||
|
ValueTypeMismatch(ValueType, TypedValue),
|
||||||
|
AttributeLookupFailed, // Catch-all, because the table lookup code is lazy. TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for EmptyBecause {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
|
use self::EmptyBecause::*;
|
||||||
|
match self {
|
||||||
|
&TypeMismatch(ref var, ref existing, ref desired) => {
|
||||||
|
write!(f, "Type mismatch: {:?} can't be {:?}, because it's already {:?}",
|
||||||
|
var, desired, existing)
|
||||||
|
},
|
||||||
|
&UnresolvedIdent(ref kw) => {
|
||||||
|
write!(f, "Couldn't resolve keyword {}", kw)
|
||||||
|
},
|
||||||
|
&InvalidAttributeIdent(ref kw) => {
|
||||||
|
write!(f, "{} does not name an attribute", kw)
|
||||||
|
},
|
||||||
|
&InvalidAttributeEntid(entid) => {
|
||||||
|
write!(f, "{} is not an attribute", entid)
|
||||||
|
},
|
||||||
|
&ValueTypeMismatch(value_type, ref typed_value) => {
|
||||||
|
write!(f, "Type mismatch: {:?} doesn't match attribute type {:?}",
|
||||||
|
typed_value, value_type)
|
||||||
|
},
|
||||||
|
&AttributeLookupFailed => {
|
||||||
|
write!(f, "Attribute lookup failed")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Debug for ConjoiningClauses {
|
impl Debug for ConjoiningClauses {
|
||||||
fn fmt(&self, fmt: &mut Formatter) -> Result {
|
fn fmt(&self, fmt: &mut Formatter) -> Result {
|
||||||
fmt.debug_struct("ConjoiningClauses")
|
fmt.debug_struct("ConjoiningClauses")
|
||||||
.field("is_known_empty", &self.is_known_empty)
|
.field("is_known_empty", &self.is_known_empty)
|
||||||
.field("from", &self.from)
|
.field("from", &self.from)
|
||||||
.field("wheres", &self.wheres)
|
.field("wheres", &self.wheres)
|
||||||
.field("bindings", &self.bindings)
|
.field("column_bindings", &self.column_bindings)
|
||||||
|
.field("input_variables", &self.input_variables)
|
||||||
|
.field("value_bindings", &self.value_bindings)
|
||||||
.field("known_types", &self.known_types)
|
.field("known_types", &self.known_types)
|
||||||
.field("extracted_types", &self.extracted_types)
|
.field("extracted_types", &self.extracted_types)
|
||||||
.finish()
|
.finish()
|
||||||
|
@ -239,10 +301,13 @@ impl Default for ConjoiningClauses {
|
||||||
fn default() -> ConjoiningClauses {
|
fn default() -> ConjoiningClauses {
|
||||||
ConjoiningClauses {
|
ConjoiningClauses {
|
||||||
is_known_empty: false,
|
is_known_empty: false,
|
||||||
|
empty_because: None,
|
||||||
aliaser: default_table_aliaser(),
|
aliaser: default_table_aliaser(),
|
||||||
from: vec![],
|
from: vec![],
|
||||||
wheres: vec![],
|
wheres: vec![],
|
||||||
bindings: BTreeMap::new(),
|
input_variables: BTreeSet::new(),
|
||||||
|
column_bindings: BTreeMap::new(),
|
||||||
|
value_bindings: BTreeMap::new(),
|
||||||
known_types: BTreeMap::new(),
|
known_types: BTreeMap::new(),
|
||||||
extracted_types: BTreeMap::new(),
|
extracted_types: BTreeMap::new(),
|
||||||
}
|
}
|
||||||
|
@ -250,22 +315,53 @@ impl Default for ConjoiningClauses {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
|
fn with_value_bindings(bindings: BTreeMap<Variable, TypedValue>) -> ConjoiningClauses {
|
||||||
|
let mut cc = ConjoiningClauses {
|
||||||
|
value_bindings: bindings,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pre-fill our type mappings with the types of the input bindings.
|
||||||
|
cc.known_types
|
||||||
|
.extend(cc.value_bindings.iter()
|
||||||
|
.map(|(k, v)| (k.clone(), v.value_type())));
|
||||||
|
cc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConjoiningClauses {
|
||||||
|
fn bound_value(&self, var: &Variable) -> Option<TypedValue> {
|
||||||
|
self.value_bindings.get(var).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bind_column_to_var(&mut self, table: TableAlias, column: DatomsColumn, var: Variable) {
|
pub fn bind_column_to_var(&mut self, table: TableAlias, column: DatomsColumn, var: Variable) {
|
||||||
let alias = QualifiedAlias(table, column);
|
// Do we have an external binding for this?
|
||||||
|
if let Some(bound_val) = self.bound_value(&var) {
|
||||||
|
// Great! Use that instead.
|
||||||
|
self.constrain_column_to_constant(table, column, bound_val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will we have an external binding for this?
|
||||||
|
// If so, we don't need to extract its type. We'll know it later.
|
||||||
|
let late_binding = self.input_variables.contains(&var);
|
||||||
|
|
||||||
// If this is a value, and we don't already know its type or where
|
// If this is a value, and we don't already know its type or where
|
||||||
// to get its type, record that we can get it from this table.
|
// to get its type, record that we can get it from this table.
|
||||||
let needs_type_extraction =
|
let needs_type_extraction =
|
||||||
alias.is_value() &&
|
!late_binding && // Never need to extract for bound vars.
|
||||||
!self.known_types.contains_key(&var) &&
|
column == DatomsColumn::Value && // Never need to extract types for refs.
|
||||||
!self.extracted_types.contains_key(&var);
|
!self.known_types.contains_key(&var) && // We know the type!
|
||||||
|
!self.extracted_types.contains_key(&var); // We're already extracting the type.
|
||||||
|
|
||||||
|
let alias = QualifiedAlias(table, column);
|
||||||
|
|
||||||
// If we subsequently find out its type, we'll remove this later -- see
|
// If we subsequently find out its type, we'll remove this later -- see
|
||||||
// the removal in `constrain_var_to_type`.
|
// the removal in `constrain_var_to_type`.
|
||||||
if needs_type_extraction {
|
if needs_type_extraction {
|
||||||
self.extracted_types.insert(var.clone(), alias.for_type_tag());
|
self.extracted_types.insert(var.clone(), alias.for_type_tag());
|
||||||
}
|
}
|
||||||
self.bindings.entry(var).or_insert(vec![]).push(alias);
|
self.column_bindings.entry(var).or_insert(vec![]).push(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn constrain_column_to_constant(&mut self, table: TableAlias, column: DatomsColumn, constant: TypedValue) {
|
pub fn constrain_column_to_constant(&mut self, table: TableAlias, column: DatomsColumn, constant: TypedValue) {
|
||||||
|
@ -285,9 +381,9 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constrains the var if there's no existing type.
|
/// Constrains the var if there's no existing type.
|
||||||
/// Returns `false` if it's impossible for this type to apply (because there's a conflicting
|
/// Marks as known-empty if it's impossible for this type to apply because there's a conflicting
|
||||||
/// type already known).
|
/// type already known.
|
||||||
fn constrain_var_to_type(&mut self, variable: Variable, this_type: ValueType) -> bool {
|
fn constrain_var_to_type(&mut self, variable: Variable, this_type: ValueType) {
|
||||||
// If this variable now has a known attribute, we can unhook extracted types for
|
// If this variable now has a known attribute, we can unhook extracted types for
|
||||||
// any other instances of that variable.
|
// any other instances of that variable.
|
||||||
// For example, given
|
// For example, given
|
||||||
|
@ -300,17 +396,18 @@ impl ConjoiningClauses {
|
||||||
// the second pattern we can avoid that.
|
// the second pattern we can avoid that.
|
||||||
self.extracted_types.remove(&variable);
|
self.extracted_types.remove(&variable);
|
||||||
|
|
||||||
// Is there an existing binding for this variable?
|
// Is there an existing mapping for this variable?
|
||||||
let types_entry = self.known_types.entry(variable);
|
// Any known inputs have already been added to known_types, and so if they conflict we'll
|
||||||
match types_entry {
|
// spot it here.
|
||||||
|
if let Some(existing) = self.known_types.get(&variable).cloned() {
|
||||||
// If so, the types must match.
|
// If so, the types must match.
|
||||||
Entry::Occupied(entry) =>
|
if existing != this_type {
|
||||||
*entry.get() == this_type,
|
self.mark_known_empty(EmptyBecause::TypeMismatch(variable, existing, this_type));
|
||||||
// If not, record the one we just determined.
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
entry.insert(this_type);
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If not, record the one we just determined.
|
||||||
|
self.known_types.insert(variable, this_type);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,15 +428,14 @@ impl ConjoiningClauses {
|
||||||
// Ident or attribute resolution errors (the only other check we need to do) will be done
|
// Ident or attribute resolution errors (the only other check we need to do) will be done
|
||||||
// by the caller.
|
// by the caller.
|
||||||
if let &PatternNonValuePlace::Variable(ref v) = value {
|
if let &PatternNonValuePlace::Variable(ref v) = value {
|
||||||
if !self.constrain_var_to_type(v.clone(), ValueType::Ref) {
|
self.constrain_var_to_type(v.clone(), ValueType::Ref)
|
||||||
self.mark_known_empty("Couldn't constrain var to Ref.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mark_known_empty(&mut self, why: &str) {
|
fn mark_known_empty(&mut self, why: EmptyBecause) {
|
||||||
self.is_known_empty = true;
|
self.is_known_empty = true;
|
||||||
println!("{}", why); // TODO: proper logging.
|
println!("CC known empty: {:?}.", &why); // TODO: proper logging.
|
||||||
|
self.empty_because = Some(why);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entid_for_ident<'s, 'a>(&self, schema: &'s Schema, ident: &'a NamespacedKeyword) -> Option<Entid> {
|
fn entid_for_ident<'s, 'a>(&self, schema: &'s Schema, ident: &'a NamespacedKeyword) -> Option<Entid> {
|
||||||
|
@ -433,7 +529,7 @@ impl ConjoiningClauses {
|
||||||
/// Expansions.
|
/// Expansions.
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
|
|
||||||
/// Take the contents of `bindings` and generate inter-constraints for the appropriate
|
/// Take the contents of `column_bindings` and generate inter-constraints for the appropriate
|
||||||
/// columns into `wheres`.
|
/// columns into `wheres`.
|
||||||
///
|
///
|
||||||
/// For example, a bindings map associating a var to three places in the query, like
|
/// For example, a bindings map associating a var to three places in the query, like
|
||||||
|
@ -448,8 +544,8 @@ impl ConjoiningClauses {
|
||||||
/// datoms12.e = datoms13.v
|
/// datoms12.e = datoms13.v
|
||||||
/// datoms12.e = datoms14.e
|
/// datoms12.e = datoms14.e
|
||||||
/// ```
|
/// ```
|
||||||
pub fn expand_bindings(&mut self) {
|
pub fn expand_column_bindings(&mut self) {
|
||||||
for cols in self.bindings.values() {
|
for cols in self.column_bindings.values() {
|
||||||
if cols.len() > 1 {
|
if cols.len() > 1 {
|
||||||
let ref primary = cols[0];
|
let ref primary = cols[0];
|
||||||
let secondaries = cols.iter().skip(1);
|
let secondaries = cols.iter().skip(1);
|
||||||
|
@ -541,7 +637,7 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
match pattern.entity {
|
match pattern.entity {
|
||||||
PatternNonValuePlace::Placeholder =>
|
PatternNonValuePlace::Placeholder =>
|
||||||
// Placeholders don't contribute any bindings, nor do
|
// Placeholders don't contribute any column bindings, nor do
|
||||||
// they constrain the query -- there's no need to produce
|
// they constrain the query -- there's no need to produce
|
||||||
// IS NOT NULL, because we don't store nulls in our schema.
|
// IS NOT NULL, because we don't store nulls in our schema.
|
||||||
(),
|
(),
|
||||||
|
@ -554,7 +650,7 @@ impl ConjoiningClauses {
|
||||||
self.constrain_column_to_entity(col.clone(), DatomsColumn::Entity, entid)
|
self.constrain_column_to_entity(col.clone(), DatomsColumn::Entity, entid)
|
||||||
} else {
|
} else {
|
||||||
// A resolution failure means we're done here.
|
// A resolution failure means we're done here.
|
||||||
self.mark_known_empty("Entity ident didn't resolve.");
|
self.mark_known_empty(EmptyBecause::UnresolvedIdent(ident.clone()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -569,7 +665,7 @@ impl ConjoiningClauses {
|
||||||
if !schema.is_attribute(entid) {
|
if !schema.is_attribute(entid) {
|
||||||
// Furthermore, that entid must resolve to an attribute. If it doesn't, this
|
// Furthermore, that entid must resolve to an attribute. If it doesn't, this
|
||||||
// query is meaningless.
|
// query is meaningless.
|
||||||
self.mark_known_empty("Attribute entid isn't an attribute.");
|
self.mark_known_empty(EmptyBecause::InvalidAttributeEntid(entid));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.constrain_attribute(col.clone(), entid)
|
self.constrain_attribute(col.clone(), entid)
|
||||||
|
@ -579,12 +675,12 @@ impl ConjoiningClauses {
|
||||||
self.constrain_attribute(col.clone(), entid);
|
self.constrain_attribute(col.clone(), entid);
|
||||||
|
|
||||||
if !schema.is_attribute(entid) {
|
if !schema.is_attribute(entid) {
|
||||||
self.mark_known_empty("Attribute ident isn't an attribute.");
|
self.mark_known_empty(EmptyBecause::InvalidAttributeIdent(ident.clone()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// A resolution failure means we're done here.
|
// A resolution failure means we're done here.
|
||||||
self.mark_known_empty("Attribute ident didn't resolve.");
|
self.mark_known_empty(EmptyBecause::UnresolvedIdent(ident.clone()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -605,9 +701,8 @@ impl ConjoiningClauses {
|
||||||
if let Some(this_type) = value_type {
|
if let Some(this_type) = value_type {
|
||||||
// Wouldn't it be nice if we didn't need to clone in the found case?
|
// Wouldn't it be nice if we didn't need to clone in the found case?
|
||||||
// It doesn't matter too much: collisons won't be too frequent.
|
// It doesn't matter too much: collisons won't be too frequent.
|
||||||
if !self.constrain_var_to_type(v.clone(), this_type) {
|
self.constrain_var_to_type(v.clone(), this_type);
|
||||||
// The types don't match. This pattern cannot succeed.
|
if self.is_known_empty {
|
||||||
self.mark_known_empty("Value types don't match.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -647,7 +742,7 @@ impl ConjoiningClauses {
|
||||||
} else {
|
} else {
|
||||||
// A resolution failure means we're done here: this attribute must have an
|
// A resolution failure means we're done here: this attribute must have an
|
||||||
// entity value.
|
// entity value.
|
||||||
self.mark_known_empty("Value ident didn't resolve.");
|
self.mark_known_empty(EmptyBecause::UnresolvedIdent(kw.clone()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -661,7 +756,11 @@ impl ConjoiningClauses {
|
||||||
let typed_value = c.clone().into_typed_value();
|
let typed_value = c.clone().into_typed_value();
|
||||||
if !typed_value.is_congruent_with(value_type) {
|
if !typed_value.is_congruent_with(value_type) {
|
||||||
// If the attribute and its value don't match, the pattern must fail.
|
// If the attribute and its value don't match, the pattern must fail.
|
||||||
self.mark_known_empty("Value constant not congruent with attribute type.");
|
// We can never have a congruence failure if `value_type` is `None`, so we
|
||||||
|
// forcibly unwrap here.
|
||||||
|
let value_type = value_type.expect("Congruence failure but couldn't unwrap");
|
||||||
|
let why = EmptyBecause::ValueTypeMismatch(value_type, typed_value);
|
||||||
|
self.mark_known_empty(why);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -707,7 +806,8 @@ impl ConjoiningClauses {
|
||||||
// We didn't determine a table, likely because there was a mismatch
|
// We didn't determine a table, likely because there was a mismatch
|
||||||
// between an attribute and a value.
|
// between an attribute and a value.
|
||||||
// We know we cannot return a result, so we short-circuit here.
|
// We know we cannot return a result, so we short-circuit here.
|
||||||
self.mark_known_empty("Table aliaser couldn't determine a table.");
|
self.mark_known_empty(EmptyBecause::AttributeLookupFailed);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -793,7 +893,7 @@ mod testing {
|
||||||
assert_eq!(cc.known_types.get(&x).unwrap(), &ValueType::Ref);
|
assert_eq!(cc.known_types.get(&x).unwrap(), &ValueType::Ref);
|
||||||
|
|
||||||
// ?x is bound to datoms0.e.
|
// ?x is bound to datoms0.e.
|
||||||
assert_eq!(cc.bindings.get(&x).unwrap(), &vec![d0_e.clone()]);
|
assert_eq!(cc.column_bindings.get(&x).unwrap(), &vec![d0_e.clone()]);
|
||||||
|
|
||||||
// Our 'where' clauses are two:
|
// Our 'where' clauses are two:
|
||||||
// - datoms0.a = 99
|
// - datoms0.a = 99
|
||||||
|
@ -831,7 +931,7 @@ mod testing {
|
||||||
assert_eq!(cc.known_types.get(&x).unwrap(), &ValueType::Ref);
|
assert_eq!(cc.known_types.get(&x).unwrap(), &ValueType::Ref);
|
||||||
|
|
||||||
// ?x is bound to datoms0.e.
|
// ?x is bound to datoms0.e.
|
||||||
assert_eq!(cc.bindings.get(&x).unwrap(), &vec![d0_e.clone()]);
|
assert_eq!(cc.column_bindings.get(&x).unwrap(), &vec![d0_e.clone()]);
|
||||||
|
|
||||||
// Our 'where' clauses are two:
|
// Our 'where' clauses are two:
|
||||||
// - datoms0.v = true
|
// - datoms0.v = true
|
||||||
|
@ -877,8 +977,8 @@ mod testing {
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Finally, expand bindings to get the overlaps for ?x.
|
// Finally, expand column bindings to get the overlaps for ?x.
|
||||||
cc.expand_bindings();
|
cc.expand_column_bindings();
|
||||||
|
|
||||||
println!("{:#?}", cc);
|
println!("{:#?}", cc);
|
||||||
|
|
||||||
|
@ -898,7 +998,7 @@ mod testing {
|
||||||
assert_eq!(cc.known_types.get(&x).unwrap(), &ValueType::Ref);
|
assert_eq!(cc.known_types.get(&x).unwrap(), &ValueType::Ref);
|
||||||
|
|
||||||
// ?x is bound to datoms0.e and datoms1.e.
|
// ?x is bound to datoms0.e and datoms1.e.
|
||||||
assert_eq!(cc.bindings.get(&x).unwrap(),
|
assert_eq!(cc.column_bindings.get(&x).unwrap(),
|
||||||
&vec![
|
&vec![
|
||||||
d0_e.clone(),
|
d0_e.clone(),
|
||||||
d1_e.clone(),
|
d1_e.clone(),
|
||||||
|
@ -917,4 +1017,75 @@ mod testing {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_value_bindings() {
|
||||||
|
let mut schema = Schema::default();
|
||||||
|
|
||||||
|
associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99);
|
||||||
|
add_attribute(&mut schema, 99, Attribute {
|
||||||
|
value_type: ValueType::Boolean,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let x = Variable(PlainSymbol::new("?x"));
|
||||||
|
let y = Variable(PlainSymbol::new("?y"));
|
||||||
|
|
||||||
|
let b: BTreeMap<Variable, TypedValue> =
|
||||||
|
vec![(y.clone(), TypedValue::Boolean(true))].into_iter().collect();
|
||||||
|
let mut cc = ConjoiningClauses::with_value_bindings(b);
|
||||||
|
|
||||||
|
cc.apply_pattern(&schema, &Pattern {
|
||||||
|
source: None,
|
||||||
|
entity: PatternNonValuePlace::Variable(x.clone()),
|
||||||
|
attribute: PatternNonValuePlace::Ident(NamespacedKeyword::new("foo", "bar")),
|
||||||
|
value: PatternValuePlace::Variable(y.clone()),
|
||||||
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
|
});
|
||||||
|
|
||||||
|
let d0_e = QualifiedAlias("datoms00".to_string(), DatomsColumn::Entity);
|
||||||
|
let d0_a = QualifiedAlias("datoms00".to_string(), DatomsColumn::Attribute);
|
||||||
|
let d0_v = QualifiedAlias("datoms00".to_string(), DatomsColumn::Value);
|
||||||
|
|
||||||
|
// ?y has been expanded into `true`.
|
||||||
|
assert_eq!(cc.wheres, vec![
|
||||||
|
ColumnConstraint::EqualsEntity(d0_a, 99),
|
||||||
|
ColumnConstraint::EqualsValue(d0_v, TypedValue::Boolean(true)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// There is no binding for ?y.
|
||||||
|
assert!(!cc.column_bindings.contains_key(&y));
|
||||||
|
|
||||||
|
// ?x is bound to the entity.
|
||||||
|
assert_eq!(cc.column_bindings.get(&x).unwrap(),
|
||||||
|
&vec![d0_e.clone()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_value_bindings_type_disagreement() {
|
||||||
|
let mut schema = Schema::default();
|
||||||
|
|
||||||
|
associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99);
|
||||||
|
add_attribute(&mut schema, 99, Attribute {
|
||||||
|
value_type: ValueType::Boolean,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let x = Variable(PlainSymbol::new("?x"));
|
||||||
|
let y = Variable(PlainSymbol::new("?y"));
|
||||||
|
|
||||||
|
let b: BTreeMap<Variable, TypedValue> =
|
||||||
|
vec![(y.clone(), TypedValue::Long(42))].into_iter().collect();
|
||||||
|
let mut cc = ConjoiningClauses::with_value_bindings(b);
|
||||||
|
|
||||||
|
cc.apply_pattern(&schema, &Pattern {
|
||||||
|
source: None,
|
||||||
|
entity: PatternNonValuePlace::Variable(x.clone()),
|
||||||
|
attribute: PatternNonValuePlace::Ident(NamespacedKeyword::new("foo", "bar")),
|
||||||
|
value: PatternValuePlace::Variable(y.clone()),
|
||||||
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
|
});
|
||||||
|
|
||||||
|
// The type of the provided binding doesn't match the type of the attribute.
|
||||||
|
assert!(cc.is_known_empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,7 +212,7 @@ fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
|
||||||
// one column in the query. If that constraint is violated it's a
|
// one column in the query. If that constraint is violated it's a
|
||||||
// bug in our code, so it's appropriate to panic here.
|
// bug in our code, so it's appropriate to panic here.
|
||||||
let columns = query.cc
|
let columns = query.cc
|
||||||
.bindings
|
.column_bindings
|
||||||
.get(var)
|
.get(var)
|
||||||
.expect("Every variable has a binding");
|
.expect("Every variable has a binding");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue