Part 1: introduce ColumnIntersection and ColumnAlternation.

This provides a limited form of OR and AND for column constraints, allowing
simple 'or-join' queries to be expressed on a single table alias.
This commit is contained in:
Richard Newman 2017-03-27 19:35:39 -07:00
parent 460fdac252
commit 997df0b776
5 changed files with 157 additions and 18 deletions

View file

@ -51,6 +51,7 @@ use errors::{
use types::{
ColumnConstraint,
ColumnIntersection,
DatomsColumn,
DatomsTable,
EmptyBecause,
@ -129,7 +130,7 @@ pub struct ConjoiningClauses {
pub from: Vec<SourceAlias>,
/// A list of fragments that can be joined by `AND`.
pub wheres: Vec<ColumnConstraint>,
pub wheres: ColumnIntersection,
/// A map from var to qualified columns. Used to project.
pub column_bindings: BTreeMap<Variable, Vec<QualifiedAlias>>,
@ -218,7 +219,7 @@ impl Default for ConjoiningClauses {
empty_because: None,
aliaser: default_table_aliaser(),
from: vec![],
wheres: vec![],
wheres: ColumnIntersection::default(),
input_variables: BTreeSet::new(),
column_bindings: BTreeMap::new(),
value_bindings: BTreeMap::new(),
@ -318,11 +319,11 @@ impl ConjoiningClauses {
}
pub fn constrain_column_to_constant(&mut self, table: TableAlias, column: DatomsColumn, constant: TypedValue) {
self.wheres.push(ColumnConstraint::Equals(QualifiedAlias(table, column), QueryValue::TypedValue(constant)))
self.wheres.also(ColumnConstraint::Equals(QualifiedAlias(table, column), QueryValue::TypedValue(constant)))
}
pub fn constrain_column_to_entity(&mut self, table: TableAlias, column: DatomsColumn, entity: Entid) {
self.wheres.push(ColumnConstraint::Equals(QualifiedAlias(table, column), QueryValue::Entid(entity)))
self.wheres.also(ColumnConstraint::Equals(QualifiedAlias(table, column), QueryValue::Entid(entity)))
}
pub fn constrain_attribute(&mut self, table: TableAlias, attribute: Entid) {
@ -330,7 +331,7 @@ impl ConjoiningClauses {
}
pub fn constrain_value_to_numeric(&mut self, table: TableAlias, value: i64) {
self.wheres.push(ColumnConstraint::Equals(
self.wheres.also(ColumnConstraint::Equals(
QualifiedAlias(table, DatomsColumn::Value),
QueryValue::PrimitiveLong(value)))
}
@ -577,7 +578,7 @@ impl ConjoiningClauses {
// TODO: if both primary and secondary are .v, should we make sure
// the type tag columns also match?
// We don't do so in the ClojureScript version.
self.wheres.push(ColumnConstraint::Equals(primary.clone(), QueryValue::Column(secondary.clone())));
self.wheres.also(ColumnConstraint::Equals(primary.clone(), QueryValue::Column(secondary.clone())));
}
}
}
@ -827,7 +828,7 @@ impl ConjoiningClauses {
} else {
// It must be a keyword.
self.constrain_column_to_constant(col.clone(), DatomsColumn::Value, TypedValue::Keyword(kw.clone()));
self.wheres.push(ColumnConstraint::HasType(col.clone(), ValueType::Keyword));
self.wheres.also(ColumnConstraint::HasType(col.clone(), ValueType::Keyword));
};
},
PatternValuePlace::Constant(ref c) => {
@ -863,7 +864,7 @@ impl ConjoiningClauses {
// Because everything we handle here is unambiguous, we generate a single type
// restriction from the value type of the typed value.
if value_type.is_none() {
self.wheres.push(ColumnConstraint::HasType(col.clone(), typed_value_type));
self.wheres.also(ColumnConstraint::HasType(col.clone(), typed_value_type));
}
},
@ -941,7 +942,7 @@ impl ConjoiningClauses {
left: left,
right: right,
};
self.wheres.push(constraint);
self.wheres.also(constraint);
Ok(())
}
}
@ -1059,7 +1060,7 @@ mod testing {
assert_eq!(cc.wheres, vec![
ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)),
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))),
]);
].into());
}
#[test]
@ -1097,7 +1098,7 @@ mod testing {
assert_eq!(cc.wheres, vec![
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))),
ColumnConstraint::HasType("datoms00".to_string(), ValueType::Boolean),
]);
].into());
}
/// This test ensures that we do less work if we know the attribute thanks to a var lookup.
@ -1140,7 +1141,7 @@ mod testing {
assert_eq!(cc.column_bindings.get(&x).unwrap(), &vec![d0_e.clone()]);
assert_eq!(cc.wheres, vec![
ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)),
]);
].into());
}
/// Queries that bind non-entity values to entity places can't return results.
@ -1198,7 +1199,7 @@ mod testing {
// ?x is bound to datoms0.e.
assert_eq!(cc.column_bindings.get(&x).unwrap(), &vec![d0_e.clone()]);
assert_eq!(cc.wheres, vec![]);
assert_eq!(cc.wheres, vec![].into());
}
/// This test ensures that we query all_datoms if we're looking for a string.
@ -1237,7 +1238,7 @@ mod testing {
assert_eq!(cc.wheres, vec![
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::String("hello".to_string()))),
ColumnConstraint::HasType("all_datoms00".to_string(), ValueType::String),
]);
].into());
}
#[test]
@ -1310,7 +1311,7 @@ mod testing {
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::String("idgoeshere".to_string()))),
ColumnConstraint::Equals(d1_a, QueryValue::Entid(99)),
ColumnConstraint::Equals(d0_e, QueryValue::Column(d1_e)),
]);
].into());
}
#[test]
@ -1346,7 +1347,7 @@ mod testing {
assert_eq!(cc.wheres, vec![
ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)),
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))),
]);
].into());
// There is no binding for ?y.
assert!(!cc.column_bindings.contains_key(&y));
@ -1466,11 +1467,11 @@ mod testing {
let clauses = cc.wheres;
assert_eq!(clauses.len(), 1);
assert_eq!(clauses[0], ColumnConstraint::NumericInequality {
assert_eq!(clauses.0[0], ColumnConstraint::NumericInequality {
operator: NumericComparison::LessThan,
left: QueryValue::Column(cc.column_bindings.get(&y).unwrap()[0].clone()),
right: QueryValue::TypedValue(TypedValue::Long(10)),
});
}.into());
}
#[test]

View file

@ -93,7 +93,10 @@ pub use cc::{
};
pub use types::{
ColumnAlternation,
ColumnConstraint,
ColumnConstraintOrAlternation,
ColumnIntersection,
DatomsColumn,
DatomsTable,
QualifiedAlias,

View file

@ -194,6 +194,90 @@ pub enum ColumnConstraint {
HasType(TableAlias, ValueType),
}
#[derive(PartialEq, Eq, Debug)]
pub enum ColumnConstraintOrAlternation {
Constraint(ColumnConstraint),
Alternation(ColumnAlternation),
}
impl From<ColumnConstraint> for ColumnConstraintOrAlternation {
fn from(thing: ColumnConstraint) -> Self {
ColumnConstraintOrAlternation::Constraint(thing)
}
}
/// A `ColumnIntersection` constraint is satisfied if all of its inner constraints are satisfied.
/// An empty intersection is always satisfied.
#[derive(PartialEq, Eq)]
pub struct ColumnIntersection(pub Vec<ColumnConstraintOrAlternation>);
impl From<Vec<ColumnConstraint>> for ColumnIntersection {
fn from(thing: Vec<ColumnConstraint>) -> Self {
ColumnIntersection(thing.into_iter().map(|x| x.into()).collect())
}
}
impl Default for ColumnIntersection {
fn default() -> Self {
ColumnIntersection(vec![])
}
}
impl IntoIterator for ColumnIntersection {
type Item = ColumnConstraintOrAlternation;
type IntoIter = ::std::vec::IntoIter<ColumnConstraintOrAlternation>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl ColumnIntersection {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn also(&mut self, constraint: ColumnConstraint) {
self.0.push(ColumnConstraintOrAlternation::Constraint(constraint));
}
}
/// A `ColumnAlternation` constraint is satisfied if at least one of its inner constraints is
/// satisfied. An empty `ColumnAlternation` is never satisfied.
#[derive(PartialEq, Eq, Debug)]
pub struct ColumnAlternation(pub Vec<ColumnIntersection>);
impl Default for ColumnAlternation {
fn default() -> Self {
ColumnAlternation(vec![])
}
}
impl IntoIterator for ColumnAlternation {
type Item = ColumnIntersection;
type IntoIter = ::std::vec::IntoIter<ColumnIntersection>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl ColumnAlternation {
pub fn instead(&mut self, intersection: ColumnIntersection) {
self.0.push(intersection);
}
}
impl Debug for ColumnIntersection {
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl Debug for ColumnConstraint {
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
use self::ColumnConstraint::*;

View file

@ -81,6 +81,9 @@ pub enum Constraint {
left: ColumnOrExpression,
right: ColumnOrExpression,
},
Or {
constraints: Vec<Constraint>,
},
And {
constraints: Vec<Constraint>,
},
@ -260,6 +263,11 @@ impl QueryFragment for Constraint {
},
&And { ref constraints } => {
// An empty intersection is true.
if constraints.is_empty() {
out.push_sql("1");
return Ok(())
}
out.push_sql("(");
interpose!(constraint, constraints,
{ constraint.push_sql(out)? },
@ -268,6 +276,20 @@ impl QueryFragment for Constraint {
Ok(())
},
&Or { ref constraints } => {
// An empty alternation is false.
if constraints.is_empty() {
out.push_sql("0");
return Ok(())
}
out.push_sql("(");
interpose!(constraint, constraints,
{ constraint.push_sql(out)? },
{ out.push_sql(" OR ") });
out.push_sql(")");
Ok(())
}
&In { ref left, ref list } => {
left.push_sql(out)?;
out.push_sql(" IN (");

View file

@ -25,7 +25,10 @@ use mentat_query::{
use mentat_query_algebrizer::{
AlgebraicQuery,
ColumnAlternation,
ColumnConstraint,
ColumnConstraintOrAlternation,
ColumnIntersection,
ConjoiningClauses,
DatomsColumn,
DatomsTable,
@ -66,6 +69,32 @@ impl ToColumn for QualifiedAlias {
}
}
impl ToConstraint for ColumnIntersection {
fn to_constraint(self) -> Constraint {
Constraint::And {
constraints: self.into_iter().map(|x| x.to_constraint()).collect()
}
}
}
impl ToConstraint for ColumnAlternation {
fn to_constraint(self) -> Constraint {
Constraint::Or {
constraints: self.into_iter().map(|x| x.to_constraint()).collect()
}
}
}
impl ToConstraint for ColumnConstraintOrAlternation {
fn to_constraint(self) -> Constraint {
use self::ColumnConstraintOrAlternation::*;
match self {
Alternation(alt) => alt.to_constraint(),
Constraint(c) => c.to_constraint(),
}
}
}
impl ToConstraint for ColumnConstraint {
fn to_constraint(self) -> Constraint {
use self::ColumnConstraint::*;