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:
parent
460fdac252
commit
997df0b776
5 changed files with 157 additions and 18 deletions
|
@ -51,6 +51,7 @@ use errors::{
|
||||||
|
|
||||||
use types::{
|
use types::{
|
||||||
ColumnConstraint,
|
ColumnConstraint,
|
||||||
|
ColumnIntersection,
|
||||||
DatomsColumn,
|
DatomsColumn,
|
||||||
DatomsTable,
|
DatomsTable,
|
||||||
EmptyBecause,
|
EmptyBecause,
|
||||||
|
@ -129,7 +130,7 @@ pub struct ConjoiningClauses {
|
||||||
pub from: Vec<SourceAlias>,
|
pub from: Vec<SourceAlias>,
|
||||||
|
|
||||||
/// A list of fragments that can be joined by `AND`.
|
/// 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.
|
/// A map from var to qualified columns. Used to project.
|
||||||
pub column_bindings: BTreeMap<Variable, Vec<QualifiedAlias>>,
|
pub column_bindings: BTreeMap<Variable, Vec<QualifiedAlias>>,
|
||||||
|
@ -218,7 +219,7 @@ impl Default for ConjoiningClauses {
|
||||||
empty_because: None,
|
empty_because: None,
|
||||||
aliaser: default_table_aliaser(),
|
aliaser: default_table_aliaser(),
|
||||||
from: vec![],
|
from: vec![],
|
||||||
wheres: vec![],
|
wheres: ColumnIntersection::default(),
|
||||||
input_variables: BTreeSet::new(),
|
input_variables: BTreeSet::new(),
|
||||||
column_bindings: BTreeMap::new(),
|
column_bindings: BTreeMap::new(),
|
||||||
value_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) {
|
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) {
|
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) {
|
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) {
|
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),
|
QualifiedAlias(table, DatomsColumn::Value),
|
||||||
QueryValue::PrimitiveLong(value)))
|
QueryValue::PrimitiveLong(value)))
|
||||||
}
|
}
|
||||||
|
@ -577,7 +578,7 @@ impl ConjoiningClauses {
|
||||||
// TODO: if both primary and secondary are .v, should we make sure
|
// TODO: if both primary and secondary are .v, should we make sure
|
||||||
// the type tag columns also match?
|
// the type tag columns also match?
|
||||||
// We don't do so in the ClojureScript version.
|
// 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 {
|
} else {
|
||||||
// It must be a keyword.
|
// It must be a keyword.
|
||||||
self.constrain_column_to_constant(col.clone(), DatomsColumn::Value, TypedValue::Keyword(kw.clone()));
|
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) => {
|
PatternValuePlace::Constant(ref c) => {
|
||||||
|
@ -863,7 +864,7 @@ impl ConjoiningClauses {
|
||||||
// Because everything we handle here is unambiguous, we generate a single type
|
// Because everything we handle here is unambiguous, we generate a single type
|
||||||
// restriction from the value type of the typed value.
|
// restriction from the value type of the typed value.
|
||||||
if value_type.is_none() {
|
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,
|
left: left,
|
||||||
right: right,
|
right: right,
|
||||||
};
|
};
|
||||||
self.wheres.push(constraint);
|
self.wheres.also(constraint);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1059,7 +1060,7 @@ mod testing {
|
||||||
assert_eq!(cc.wheres, vec![
|
assert_eq!(cc.wheres, vec![
|
||||||
ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)),
|
ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)),
|
||||||
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))),
|
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))),
|
||||||
]);
|
].into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1097,7 +1098,7 @@ mod testing {
|
||||||
assert_eq!(cc.wheres, vec![
|
assert_eq!(cc.wheres, vec![
|
||||||
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))),
|
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))),
|
||||||
ColumnConstraint::HasType("datoms00".to_string(), ValueType::Boolean),
|
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.
|
/// 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.column_bindings.get(&x).unwrap(), &vec![d0_e.clone()]);
|
||||||
assert_eq!(cc.wheres, vec![
|
assert_eq!(cc.wheres, vec![
|
||||||
ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)),
|
ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)),
|
||||||
]);
|
].into());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries that bind non-entity values to entity places can't return results.
|
/// 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.
|
// ?x is bound to datoms0.e.
|
||||||
assert_eq!(cc.column_bindings.get(&x).unwrap(), &vec![d0_e.clone()]);
|
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.
|
/// 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![
|
assert_eq!(cc.wheres, vec![
|
||||||
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::String("hello".to_string()))),
|
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::String("hello".to_string()))),
|
||||||
ColumnConstraint::HasType("all_datoms00".to_string(), ValueType::String),
|
ColumnConstraint::HasType("all_datoms00".to_string(), ValueType::String),
|
||||||
]);
|
].into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1310,7 +1311,7 @@ mod testing {
|
||||||
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::String("idgoeshere".to_string()))),
|
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::String("idgoeshere".to_string()))),
|
||||||
ColumnConstraint::Equals(d1_a, QueryValue::Entid(99)),
|
ColumnConstraint::Equals(d1_a, QueryValue::Entid(99)),
|
||||||
ColumnConstraint::Equals(d0_e, QueryValue::Column(d1_e)),
|
ColumnConstraint::Equals(d0_e, QueryValue::Column(d1_e)),
|
||||||
]);
|
].into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1346,7 +1347,7 @@ mod testing {
|
||||||
assert_eq!(cc.wheres, vec![
|
assert_eq!(cc.wheres, vec![
|
||||||
ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)),
|
ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)),
|
||||||
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))),
|
ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))),
|
||||||
]);
|
].into());
|
||||||
|
|
||||||
// There is no binding for ?y.
|
// There is no binding for ?y.
|
||||||
assert!(!cc.column_bindings.contains_key(&y));
|
assert!(!cc.column_bindings.contains_key(&y));
|
||||||
|
@ -1466,11 +1467,11 @@ mod testing {
|
||||||
|
|
||||||
let clauses = cc.wheres;
|
let clauses = cc.wheres;
|
||||||
assert_eq!(clauses.len(), 1);
|
assert_eq!(clauses.len(), 1);
|
||||||
assert_eq!(clauses[0], ColumnConstraint::NumericInequality {
|
assert_eq!(clauses.0[0], ColumnConstraint::NumericInequality {
|
||||||
operator: NumericComparison::LessThan,
|
operator: NumericComparison::LessThan,
|
||||||
left: QueryValue::Column(cc.column_bindings.get(&y).unwrap()[0].clone()),
|
left: QueryValue::Column(cc.column_bindings.get(&y).unwrap()[0].clone()),
|
||||||
right: QueryValue::TypedValue(TypedValue::Long(10)),
|
right: QueryValue::TypedValue(TypedValue::Long(10)),
|
||||||
});
|
}.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -93,7 +93,10 @@ pub use cc::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use types::{
|
pub use types::{
|
||||||
|
ColumnAlternation,
|
||||||
ColumnConstraint,
|
ColumnConstraint,
|
||||||
|
ColumnConstraintOrAlternation,
|
||||||
|
ColumnIntersection,
|
||||||
DatomsColumn,
|
DatomsColumn,
|
||||||
DatomsTable,
|
DatomsTable,
|
||||||
QualifiedAlias,
|
QualifiedAlias,
|
||||||
|
|
|
@ -194,6 +194,90 @@ pub enum ColumnConstraint {
|
||||||
HasType(TableAlias, ValueType),
|
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 {
|
impl Debug for ColumnConstraint {
|
||||||
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
||||||
use self::ColumnConstraint::*;
|
use self::ColumnConstraint::*;
|
||||||
|
|
|
@ -81,6 +81,9 @@ pub enum Constraint {
|
||||||
left: ColumnOrExpression,
|
left: ColumnOrExpression,
|
||||||
right: ColumnOrExpression,
|
right: ColumnOrExpression,
|
||||||
},
|
},
|
||||||
|
Or {
|
||||||
|
constraints: Vec<Constraint>,
|
||||||
|
},
|
||||||
And {
|
And {
|
||||||
constraints: Vec<Constraint>,
|
constraints: Vec<Constraint>,
|
||||||
},
|
},
|
||||||
|
@ -260,6 +263,11 @@ impl QueryFragment for Constraint {
|
||||||
},
|
},
|
||||||
|
|
||||||
&And { ref constraints } => {
|
&And { ref constraints } => {
|
||||||
|
// An empty intersection is true.
|
||||||
|
if constraints.is_empty() {
|
||||||
|
out.push_sql("1");
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
out.push_sql("(");
|
out.push_sql("(");
|
||||||
interpose!(constraint, constraints,
|
interpose!(constraint, constraints,
|
||||||
{ constraint.push_sql(out)? },
|
{ constraint.push_sql(out)? },
|
||||||
|
@ -268,6 +276,20 @@ impl QueryFragment for Constraint {
|
||||||
Ok(())
|
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 } => {
|
&In { ref left, ref list } => {
|
||||||
left.push_sql(out)?;
|
left.push_sql(out)?;
|
||||||
out.push_sql(" IN (");
|
out.push_sql(" IN (");
|
||||||
|
|
|
@ -25,7 +25,10 @@ use mentat_query::{
|
||||||
|
|
||||||
use mentat_query_algebrizer::{
|
use mentat_query_algebrizer::{
|
||||||
AlgebraicQuery,
|
AlgebraicQuery,
|
||||||
|
ColumnAlternation,
|
||||||
ColumnConstraint,
|
ColumnConstraint,
|
||||||
|
ColumnConstraintOrAlternation,
|
||||||
|
ColumnIntersection,
|
||||||
ConjoiningClauses,
|
ConjoiningClauses,
|
||||||
DatomsColumn,
|
DatomsColumn,
|
||||||
DatomsTable,
|
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 {
|
impl ToConstraint for ColumnConstraint {
|
||||||
fn to_constraint(self) -> Constraint {
|
fn to_constraint(self) -> Constraint {
|
||||||
use self::ColumnConstraint::*;
|
use self::ColumnConstraint::*;
|
||||||
|
|
Loading…
Reference in a new issue