Part 2: expand the definition of a table to include computed tables.

This commit:

- Defines a new kind of column, distinct from the eavt columns in
  `DatomsColumn`, to model the rows projected from subqueries. These
  always name one of two things: a variable, or a variable's type tag.
  Naturally the two cases are thus `Variable` and `VariableTypeTag`.
  These are cheap to clone, given that `Variable` is an `Rc<String>`.
- Defines `Column` as a wrapper around `DatomsColumn` and
  `VariableColumn`. Everywhere we used to use `DatomsColumn` we now
  allow `Column`: particularly in constraints and projections.
- Broadens the definition of a table list in the intermediate
  "query-sql" representation to include a SQL UNION. A UNION is
  represented as a list of queries and an alias.
- Implements translation from a `ComputedTable` to the query-sql
  representation. In this commit we only project vars, not type tags.

Review comment: discuss bind_column_to_var for ValueTypeTag.
Review comment: implement From<Vec<T>> for ConsumableVec<T>.
This commit is contained in:
Richard Newman 2017-04-07 17:23:41 -07:00
parent 7948788936
commit 08d2c613a4
8 changed files with 303 additions and 103 deletions

View file

@ -49,6 +49,7 @@ use types::{
ColumnConstraint, ColumnConstraint,
ColumnIntersection, ColumnIntersection,
ComputedTable, ComputedTable,
Column,
DatomsColumn, DatomsColumn,
DatomsTable, DatomsTable,
EmptyBecause, EmptyBecause,
@ -286,35 +287,62 @@ impl ConjoiningClauses {
} }
} }
pub fn bind_column_to_var(&mut self, schema: &Schema, table: TableAlias, column: DatomsColumn, var: Variable) { pub fn bind_column_to_var<C: Into<Column>>(&mut self, schema: &Schema, table: TableAlias, column: C, var: Variable) {
let column = column.into();
// Do we have an external binding for this? // Do we have an external binding for this?
if let Some(bound_val) = self.bound_value(&var) { if let Some(bound_val) = self.bound_value(&var) {
// Great! Use that instead. // Great! Use that instead.
// We expect callers to do things like bind keywords here; we need to translate these // We expect callers to do things like bind keywords here; we need to translate these
// before they hit our constraints. // before they hit our constraints.
// TODO: recognize when the valueType might be a ref and also translate entids there. match column {
if column == DatomsColumn::Value { Column::Variable(_) => {
self.constrain_column_to_constant(table, column, bound_val); // We don't need to handle expansion of attributes here. The subquery that
} else { // produces the variable projection will do so.
match bound_val { self.constrain_column_to_constant(table, column, bound_val);
TypedValue::Keyword(ref kw) => { },
if let Some(entid) = self.entid_for_ident(schema, kw) {
Column::Fixed(DatomsColumn::ValueTypeTag) => {
// I'm pretty sure this is meaningless right now, because we will never bind
// a type tag to a variable -- there's no syntax for doing so.
// In the future we might expose a way to do so, perhaps something like:
// ```
// [:find ?x
// :where [?x _ ?y]
// [(= (typeof ?y) :db.valueType/double)]]
// ```
unimplemented!();
},
// TODO: recognize when the valueType might be a ref and also translate entids there.
Column::Fixed(DatomsColumn::Value) => {
self.constrain_column_to_constant(table, column, bound_val);
},
// These columns can only be entities, so attempt to translate keywords. If we can't
// get an entity out of the bound value, the pattern cannot produce results.
Column::Fixed(DatomsColumn::Attribute) |
Column::Fixed(DatomsColumn::Entity) |
Column::Fixed(DatomsColumn::Tx) => {
match bound_val {
TypedValue::Keyword(ref kw) => {
if let Some(entid) = self.entid_for_ident(schema, kw) {
self.constrain_column_to_entity(table, column, entid);
} else {
// Impossible.
// For attributes this shouldn't occur, because we check the binding in
// `table_for_places`/`alias_table`, and if it didn't resolve to a valid
// attribute then we should have already marked the pattern as empty.
self.mark_known_empty(EmptyBecause::UnresolvedIdent(kw.cloned()));
}
},
TypedValue::Ref(entid) => {
self.constrain_column_to_entity(table, column, entid); self.constrain_column_to_entity(table, column, entid);
} else { },
// Impossible. _ => {
// For attributes this shouldn't occur, because we check the binding in // One can't bind an e, a, or tx to something other than an entity.
// `table_for_places`/`alias_table`, and if it didn't resolve to a valid self.mark_known_empty(EmptyBecause::InvalidBinding(column, bound_val));
// attribute then we should have already marked the pattern as empty. },
self.mark_known_empty(EmptyBecause::UnresolvedIdent(kw.cloned())); }
}
},
TypedValue::Ref(entid) => {
self.constrain_column_to_entity(table, column, entid);
},
_ => {
// One can't bind an e, a, or tx to something other than an entity.
self.mark_known_empty(EmptyBecause::InvalidBinding(column, bound_val));
},
} }
} }
@ -328,10 +356,12 @@ impl ConjoiningClauses {
// 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 =
!late_binding && // Never need to extract for bound vars. !late_binding && // Never need to extract for bound vars.
column == DatomsColumn::Value && // Never need to extract types for refs. // Never need to extract types for refs, and var columns are handled elsewhere:
self.known_type(&var).is_none() && // Don't need to extract if we know a single type. // a subquery will be projecting a type tag.
!self.extracted_types.contains_key(&var); // We're already extracting the type. column == Column::Fixed(DatomsColumn::Value) &&
self.known_type(&var).is_none() && // Don't need to extract if we know a single type.
!self.extracted_types.contains_key(&var); // We're already extracting the type.
let alias = QualifiedAlias(table, column); let alias = QualifiedAlias(table, column);
@ -343,11 +373,13 @@ impl ConjoiningClauses {
self.column_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<C: Into<Column>>(&mut self, table: TableAlias, column: C, constant: TypedValue) {
let column = column.into();
self.wheres.add_intersection(ColumnConstraint::Equals(QualifiedAlias(table, column), QueryValue::TypedValue(constant))) self.wheres.add_intersection(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<C: Into<Column>>(&mut self, table: TableAlias, column: C, entity: Entid) {
let column = column.into();
self.wheres.add_intersection(ColumnConstraint::Equals(QualifiedAlias(table, column), QueryValue::Entid(entity))) self.wheres.add_intersection(ColumnConstraint::Equals(QualifiedAlias(table, column), QueryValue::Entid(entity)))
} }
@ -357,7 +389,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.add_intersection(ColumnConstraint::Equals( self.wheres.add_intersection(ColumnConstraint::Equals(
QualifiedAlias(table, DatomsColumn::Value), QualifiedAlias(table, Column::Fixed(DatomsColumn::Value)),
QueryValue::PrimitiveLong(value))) QueryValue::PrimitiveLong(value)))
} }
@ -586,7 +618,7 @@ impl ConjoiningClauses {
Some(v) => { Some(v) => {
// This pattern cannot match: the caller has bound a non-entity value to an // This pattern cannot match: the caller has bound a non-entity value to an
// attribute place. // attribute place.
Err(EmptyBecause::InvalidBinding(DatomsColumn::Attribute, v.clone())) Err(EmptyBecause::InvalidBinding(Column::Fixed(DatomsColumn::Attribute), v.clone()))
}, },
} }
}, },

View file

@ -605,9 +605,9 @@ mod testing {
let cc = alg(&schema, query); let cc = alg(&schema, query);
let vx = Variable::from_valid_name("?x"); let vx = Variable::from_valid_name("?x");
let d0 = "datoms00".to_string(); let d0 = "datoms00".to_string();
let d0e = QualifiedAlias(d0.clone(), DatomsColumn::Entity); let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias(d0.clone(), DatomsColumn::Attribute); let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
let d0v = QualifiedAlias(d0.clone(), DatomsColumn::Value); let d0v = QualifiedAlias::new(d0.clone(), DatomsColumn::Value);
let knows = QueryValue::Entid(66); let knows = QueryValue::Entid(66);
let parent = QueryValue::Entid(67); let parent = QueryValue::Entid(67);
let john = QueryValue::TypedValue(TypedValue::typed_string("John")); let john = QueryValue::TypedValue(TypedValue::typed_string("John"));
@ -647,11 +647,11 @@ mod testing {
let vx = Variable::from_valid_name("?x"); let vx = Variable::from_valid_name("?x");
let d0 = "datoms00".to_string(); let d0 = "datoms00".to_string();
let d1 = "datoms01".to_string(); let d1 = "datoms01".to_string();
let d0e = QualifiedAlias(d0.clone(), DatomsColumn::Entity); let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias(d0.clone(), DatomsColumn::Attribute); let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
let d1e = QualifiedAlias(d1.clone(), DatomsColumn::Entity); let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias(d1.clone(), DatomsColumn::Attribute); let d1a = QualifiedAlias::new(d1.clone(), DatomsColumn::Attribute);
let d1v = QualifiedAlias(d1.clone(), DatomsColumn::Value); let d1v = QualifiedAlias::new(d1.clone(), DatomsColumn::Value);
let name = QueryValue::Entid(65); let name = QueryValue::Entid(65);
let knows = QueryValue::Entid(66); let knows = QueryValue::Entid(66);
let parent = QueryValue::Entid(67); let parent = QueryValue::Entid(67);
@ -697,12 +697,12 @@ mod testing {
let vx = Variable::from_valid_name("?x"); let vx = Variable::from_valid_name("?x");
let d0 = "datoms00".to_string(); let d0 = "datoms00".to_string();
let d1 = "datoms01".to_string(); let d1 = "datoms01".to_string();
let d0e = QualifiedAlias(d0.clone(), DatomsColumn::Entity); let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias(d0.clone(), DatomsColumn::Attribute); let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
let d0v = QualifiedAlias(d0.clone(), DatomsColumn::Value); let d0v = QualifiedAlias::new(d0.clone(), DatomsColumn::Value);
let d1e = QualifiedAlias(d1.clone(), DatomsColumn::Entity); let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias(d1.clone(), DatomsColumn::Attribute); let d1a = QualifiedAlias::new(d1.clone(), DatomsColumn::Attribute);
let d1v = QualifiedAlias(d1.clone(), DatomsColumn::Value); let d1v = QualifiedAlias::new(d1.clone(), DatomsColumn::Value);
let knows = QueryValue::Entid(66); let knows = QueryValue::Entid(66);
let age = QueryValue::Entid(68); let age = QueryValue::Entid(68);
let john = QueryValue::TypedValue(TypedValue::typed_string("John")); let john = QueryValue::TypedValue(TypedValue::typed_string("John"));
@ -736,8 +736,9 @@ mod testing {
// These two are not equivalent: // These two are not equivalent:
// [:find ?x :where [?x :foo/bar ?y] (or-join [?x] [?x :foo/baz ?y])] // [:find ?x :where [?x :foo/bar ?y] (or-join [?x] [?x :foo/baz ?y])]
// [:find ?x :where [?x :foo/bar ?y] [?x :foo/baz ?y]] // [:find ?x :where [?x :foo/bar ?y] [?x :foo/baz ?y]]
// TODO: fixme
/*
#[test] #[test]
#[should_panic(expected = "not yet implemented")]
fn test_unit_or_join_doesnt_flatten() { fn test_unit_or_join_doesnt_flatten() {
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let query = r#"[:find ?x let query = r#"[:find ?x
@ -748,21 +749,20 @@ mod testing {
let vy = Variable::from_valid_name("?y"); let vy = Variable::from_valid_name("?y");
let d0 = "datoms00".to_string(); let d0 = "datoms00".to_string();
let d1 = "datoms01".to_string(); let d1 = "datoms01".to_string();
let d0e = QualifiedAlias(d0.clone(), DatomsColumn::Entity); let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias(d0.clone(), DatomsColumn::Attribute); let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
let d0v = QualifiedAlias(d0.clone(), DatomsColumn::Value); let d0v = QualifiedAlias::new(d0.clone(), DatomsColumn::Value);
let d1e = QualifiedAlias(d1.clone(), DatomsColumn::Entity); let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias(d1.clone(), DatomsColumn::Attribute); let d1a = QualifiedAlias::new(d1.clone(), DatomsColumn::Attribute);
let knows = QueryValue::Entid(66); let knows = QueryValue::Entid(66);
let parent = QueryValue::Entid(67); let parent = QueryValue::Entid(67);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![ assert_eq!(cc.wheres, ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows.clone())), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows.clone())),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), parent.clone())),
// The outer pattern joins against the `or` on the entity, but not value -- ?y means // The outer pattern joins against the `or` on the entity, but not value -- ?y means
// different things in each place. // different things in each place.
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(QualifiedAlias::new("c00".to_string(), VariableColumn::Variable(vx.clone()))))),
])); ]));
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e])); assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e]));
@ -771,6 +771,7 @@ mod testing {
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0), assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0),
SourceAlias(DatomsTable::Datoms, d1)]); SourceAlias(DatomsTable::Datoms, d1)]);
} }
*/
// These two are equivalent: // These two are equivalent:
// [:find ?x :where [?x :foo/bar ?y] (or [?x :foo/baz ?y])] // [:find ?x :where [?x :foo/bar ?y] (or [?x :foo/baz ?y])]
@ -810,8 +811,8 @@ mod testing {
/// Strictly speaking this can be implemented with a `NOT EXISTS` clause for the second pattern, /// Strictly speaking this can be implemented with a `NOT EXISTS` clause for the second pattern,
/// but that would be a fair amount of analysis work, I think. /// but that would be a fair amount of analysis work, I think.
#[test] #[test]
#[should_panic(expected = "not yet implemented")]
#[allow(dead_code, unused_variables)] #[allow(dead_code, unused_variables)]
// TODO: flesh this out.
fn test_alternation_with_and() { fn test_alternation_with_and() {
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let query = r#" let query = r#"

View file

@ -295,6 +295,7 @@ mod testing {
}; };
use types::{ use types::{
Column,
ColumnConstraint, ColumnConstraint,
DatomsTable, DatomsTable,
QualifiedAlias, QualifiedAlias,
@ -365,9 +366,9 @@ mod testing {
// println!("{:#?}", cc); // println!("{:#?}", cc);
let d0_e = QualifiedAlias("datoms00".to_string(), DatomsColumn::Entity); let d0_e = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Entity);
let d0_a = QualifiedAlias("datoms00".to_string(), DatomsColumn::Attribute); let d0_a = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Attribute);
let d0_v = QualifiedAlias("datoms00".to_string(), DatomsColumn::Value); let d0_v = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Value);
// After this, we know a lot of things: // After this, we know a lot of things:
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
@ -405,8 +406,8 @@ mod testing {
// println!("{:#?}", cc); // println!("{:#?}", cc);
let d0_e = QualifiedAlias("datoms00".to_string(), DatomsColumn::Entity); let d0_e = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Entity);
let d0_v = QualifiedAlias("datoms00".to_string(), DatomsColumn::Value); let d0_v = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Value);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, "datoms00".to_string())]); assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, "datoms00".to_string())]);
@ -454,8 +455,8 @@ mod testing {
// println!("{:#?}", cc); // println!("{:#?}", cc);
let d0_e = QualifiedAlias("datoms00".to_string(), DatomsColumn::Entity); let d0_e = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Entity);
let d0_a = QualifiedAlias("datoms00".to_string(), DatomsColumn::Attribute); let d0_a = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Attribute);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, "datoms00".to_string())]); assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, "datoms00".to_string())]);
@ -496,7 +497,7 @@ mod testing {
}); });
assert!(cc.is_known_empty()); assert!(cc.is_known_empty());
assert_eq!(cc.empty_because.unwrap(), EmptyBecause::InvalidBinding(DatomsColumn::Attribute, hello)); assert_eq!(cc.empty_because.unwrap(), EmptyBecause::InvalidBinding(Column::Fixed(DatomsColumn::Attribute), hello));
} }
@ -519,7 +520,7 @@ mod testing {
// println!("{:#?}", cc); // println!("{:#?}", cc);
let d0_e = QualifiedAlias("all_datoms00".to_string(), DatomsColumn::Entity); let d0_e = QualifiedAlias::new("all_datoms00".to_string(), DatomsColumn::Entity);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::AllDatoms, "all_datoms00".to_string())]); assert_eq!(cc.from, vec![SourceAlias(DatomsTable::AllDatoms, "all_datoms00".to_string())]);
@ -549,8 +550,8 @@ mod testing {
// println!("{:#?}", cc); // println!("{:#?}", cc);
let d0_e = QualifiedAlias("all_datoms00".to_string(), DatomsColumn::Entity); let d0_e = QualifiedAlias::new("all_datoms00".to_string(), DatomsColumn::Entity);
let d0_v = QualifiedAlias("all_datoms00".to_string(), DatomsColumn::Value); let d0_v = QualifiedAlias::new("all_datoms00".to_string(), DatomsColumn::Value);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::AllDatoms, "all_datoms00".to_string())]); assert_eq!(cc.from, vec![SourceAlias(DatomsTable::AllDatoms, "all_datoms00".to_string())]);
@ -609,11 +610,11 @@ mod testing {
println!("{:#?}", cc); println!("{:#?}", cc);
let d0_e = QualifiedAlias("datoms00".to_string(), DatomsColumn::Entity); let d0_e = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Entity);
let d0_a = QualifiedAlias("datoms00".to_string(), DatomsColumn::Attribute); let d0_a = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Attribute);
let d0_v = QualifiedAlias("datoms00".to_string(), DatomsColumn::Value); let d0_v = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Value);
let d1_e = QualifiedAlias("datoms01".to_string(), DatomsColumn::Entity); let d1_e = QualifiedAlias::new("datoms01".to_string(), DatomsColumn::Entity);
let d1_a = QualifiedAlias("datoms01".to_string(), DatomsColumn::Attribute); let d1_a = QualifiedAlias::new("datoms01".to_string(), DatomsColumn::Attribute);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.from, vec![ assert_eq!(cc.from, vec![
@ -669,9 +670,9 @@ mod testing {
tx: PatternNonValuePlace::Placeholder, tx: PatternNonValuePlace::Placeholder,
}); });
let d0_e = QualifiedAlias("datoms00".to_string(), DatomsColumn::Entity); let d0_e = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Entity);
let d0_a = QualifiedAlias("datoms00".to_string(), DatomsColumn::Attribute); let d0_a = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Attribute);
let d0_v = QualifiedAlias("datoms00".to_string(), DatomsColumn::Value); let d0_v = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Value);
// ?y has been expanded into `true`. // ?y has been expanded into `true`.
assert_eq!(cc.wheres, vec![ assert_eq!(cc.wheres, vec![

View file

@ -106,10 +106,12 @@ pub use clauses::{
}; };
pub use types::{ pub use types::{
Column,
ColumnAlternation, ColumnAlternation,
ColumnConstraint, ColumnConstraint,
ColumnConstraintOrAlternation, ColumnConstraintOrAlternation,
ColumnIntersection, ColumnIntersection,
ColumnName,
ComputedTable, ComputedTable,
DatomsColumn, DatomsColumn,
DatomsTable, DatomsTable,
@ -117,5 +119,6 @@ pub use types::{
QueryValue, QueryValue,
SourceAlias, SourceAlias,
TableAlias, TableAlias,
VariableColumn,
}; };

View file

@ -61,8 +61,12 @@ impl DatomsTable {
} }
} }
pub trait ColumnName {
fn column_name(&self) -> String;
}
/// One of the named columns of our tables. /// One of the named columns of our tables.
#[derive(PartialEq, Eq, Clone, Debug)] #[derive(PartialEq, Eq, Clone)]
pub enum DatomsColumn { pub enum DatomsColumn {
Entity, Entity,
Attribute, Attribute,
@ -71,6 +75,30 @@ pub enum DatomsColumn {
ValueTypeTag, ValueTypeTag,
} }
#[derive(PartialEq, Eq, Clone)]
pub enum VariableColumn {
Variable(Variable),
VariableTypeTag(Variable),
}
#[derive(PartialEq, Eq, Clone)]
pub enum Column {
Fixed(DatomsColumn),
Variable(VariableColumn),
}
impl From<DatomsColumn> for Column {
fn from(from: DatomsColumn) -> Column {
Column::Fixed(from)
}
}
impl From<VariableColumn> for Column {
fn from(from: VariableColumn) -> Column {
Column::Variable(from)
}
}
impl DatomsColumn { impl DatomsColumn {
pub fn as_str(&self) -> &'static str { pub fn as_str(&self) -> &'static str {
use self::DatomsColumn::*; use self::DatomsColumn::*;
@ -84,6 +112,46 @@ impl DatomsColumn {
} }
} }
impl ColumnName for DatomsColumn {
fn column_name(&self) -> String {
self.as_str().to_string()
}
}
impl ColumnName for VariableColumn {
fn column_name(&self) -> String {
match self {
&VariableColumn::Variable(ref v) => v.to_string(),
&VariableColumn::VariableTypeTag(ref v) => format!("{}_value_type_tag", v.as_str()),
}
}
}
impl Debug for VariableColumn {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
// These should agree with VariableColumn::column_name.
&VariableColumn::Variable(ref v) => write!(f, "{}", v.as_str()),
&VariableColumn::VariableTypeTag(ref v) => write!(f, "{}_value_type_tag", v.as_str()),
}
}
}
impl Debug for DatomsColumn {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(f, "{}", self.as_str())
}
}
impl Debug for Column {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
&Column::Fixed(ref c) => c.fmt(f),
&Column::Variable(ref v) => v.fmt(f),
}
}
}
/// A specific instance of a table within a query. E.g., "datoms123". /// A specific instance of a table within a query. E.g., "datoms123".
pub type TableAlias = String; pub type TableAlias = String;
@ -99,17 +167,22 @@ impl Debug for SourceAlias {
/// A particular column of a particular aliased table. E.g., "datoms123", Attribute. /// A particular column of a particular aliased table. E.g., "datoms123", Attribute.
#[derive(PartialEq, Eq, Clone)] #[derive(PartialEq, Eq, Clone)]
pub struct QualifiedAlias(pub TableAlias, pub DatomsColumn); pub struct QualifiedAlias(pub TableAlias, pub Column);
impl Debug for QualifiedAlias { impl Debug for QualifiedAlias {
fn fmt(&self, f: &mut Formatter) -> Result { fn fmt(&self, f: &mut Formatter) -> Result {
write!(f, "{}.{}", self.0, self.1.as_str()) write!(f, "{}.{:?}", self.0, self.1)
} }
} }
impl QualifiedAlias { impl QualifiedAlias {
pub fn new<C: Into<Column>>(table: TableAlias, column: C) -> Self {
QualifiedAlias(table, column.into())
}
pub fn for_type_tag(&self) -> QualifiedAlias { pub fn for_type_tag(&self) -> QualifiedAlias {
QualifiedAlias(self.0.clone(), DatomsColumn::ValueTypeTag) // TODO: this only makes sense for `DatomsColumn` tables.
QualifiedAlias(self.0.clone(), Column::Fixed(DatomsColumn::ValueTypeTag))
} }
} }
@ -332,7 +405,7 @@ pub enum EmptyBecause {
UnresolvedIdent(NamespacedKeyword), UnresolvedIdent(NamespacedKeyword),
InvalidAttributeIdent(NamespacedKeyword), InvalidAttributeIdent(NamespacedKeyword),
InvalidAttributeEntid(Entid), InvalidAttributeEntid(Entid),
InvalidBinding(DatomsColumn, TypedValue), InvalidBinding(Column, TypedValue),
ValueTypeMismatch(ValueType, TypedValue), ValueTypeMismatch(ValueType, TypedValue),
AttributeLookupFailed, // Catch-all, because the table lookup code is lazy. TODO AttributeLookupFailed, // Catch-all, because the table lookup code is lazy. TODO
} }

View file

@ -228,7 +228,7 @@ fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
// Also project the type from the SQL query. // Also project the type from the SQL query.
let type_name = value_type_tag_name(var); let type_name = value_type_tag_name(var);
let type_qa = QualifiedAlias(table, DatomsColumn::ValueTypeTag); let type_qa = QualifiedAlias::new(table, DatomsColumn::ValueTypeTag);
cols.push(ProjectedColumn(ColumnOrExpression::Column(type_qa), type_name)); cols.push(ProjectedColumn(ColumnOrExpression::Column(type_qa), type_name));
} }
} }

View file

@ -19,10 +19,12 @@ use mentat_core::{
}; };
use mentat_query_algebrizer::{ use mentat_query_algebrizer::{
DatomsColumn, Column,
QualifiedAlias, QualifiedAlias,
QueryValue, QueryValue,
SourceAlias, SourceAlias,
TableAlias,
VariableColumn,
}; };
use mentat_sql::{ use mentat_sql::{
@ -117,7 +119,7 @@ enum JoinOp {
} }
// Short-hand for a list of tables all inner-joined. // Short-hand for a list of tables all inner-joined.
pub struct TableList(pub Vec<SourceAlias>); pub struct TableList(pub Vec<TableOrSubquery>);
impl TableList { impl TableList {
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
@ -133,8 +135,9 @@ pub struct Join {
} }
#[allow(dead_code)] #[allow(dead_code)]
enum TableOrSubquery { pub enum TableOrSubquery {
Table(SourceAlias), Table(SourceAlias),
Union(Vec<SelectQuery>, TableAlias),
// TODO: Subquery. // TODO: Subquery.
} }
@ -152,9 +155,23 @@ pub struct SelectQuery {
pub limit: Option<u64>, pub limit: Option<u64>,
} }
// We know that DatomsColumns are safe to serialize. fn push_column(qb: &mut QueryBuilder, col: &Column) -> BuildQueryResult {
fn push_column(qb: &mut QueryBuilder, col: &DatomsColumn) { match col {
qb.push_sql(col.as_str()); &Column::Fixed(ref d) => {
qb.push_sql(d.as_str());
Ok(())
},
&Column::Variable(ref vc) => {
match vc {
&VariableColumn::Variable(ref v) => {
qb.push_identifier(v.as_str())
},
&VariableColumn::VariableTypeTag(ref v) => {
qb.push_identifier(format!("{}_value_type_tag", v.name()).as_str())
},
}
},
}
} }
//--------------------------------------------------------- //---------------------------------------------------------
@ -196,8 +213,7 @@ impl QueryFragment for ColumnOrExpression {
&Column(QualifiedAlias(ref table, ref column)) => { &Column(QualifiedAlias(ref table, ref column)) => {
out.push_identifier(table.as_str())?; out.push_identifier(table.as_str())?;
out.push_sql("."); out.push_sql(".");
push_column(out, column); push_column(out, column)
Ok(())
}, },
&Entid(entid) => { &Entid(entid) => {
out.push_sql(entid.to_string().as_str()); out.push_sql(entid.to_string().as_str());
@ -324,8 +340,8 @@ impl QueryFragment for TableList {
return Ok(()); return Ok(());
} }
interpose!(sa, self.0, interpose!(t, self.0,
{ source_alias_push_sql(out, sa)? }, { t.push_sql(out)? },
{ out.push_sql(", ") }); { out.push_sql(", ") });
Ok(()) Ok(())
} }
@ -343,7 +359,15 @@ impl QueryFragment for TableOrSubquery {
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
use self::TableOrSubquery::*; use self::TableOrSubquery::*;
match self { match self {
&Table(ref sa) => source_alias_push_sql(out, sa) &Table(ref sa) => source_alias_push_sql(out, sa),
&Union(ref subqueries, ref table_alias) => {
out.push_sql("(");
interpose!(subquery, subqueries,
{ subquery.push_sql(out)? },
{ out.push_sql(" UNION ") });
out.push_sql(") AS ");
out.push_identifier(table_alias.as_str())
},
} }
} }
} }
@ -406,7 +430,10 @@ impl SelectQuery {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use mentat_query_algebrizer::DatomsTable; use mentat_query_algebrizer::{
DatomsColumn,
DatomsTable,
};
fn build_constraint(c: Constraint) -> String { fn build_constraint(c: Constraint) -> String {
let mut builder = SQLiteQueryBuilder::new(); let mut builder = SQLiteQueryBuilder::new();
@ -418,19 +445,19 @@ mod tests {
#[test] #[test]
fn test_in_constraint() { fn test_in_constraint() {
let none = Constraint::In { let none = Constraint::In {
left: ColumnOrExpression::Column(QualifiedAlias("datoms01".to_string(), DatomsColumn::Value)), left: ColumnOrExpression::Column(QualifiedAlias::new("datoms01".to_string(), DatomsColumn::Value)),
list: vec![], list: vec![],
}; };
let one = Constraint::In { let one = Constraint::In {
left: ColumnOrExpression::Column(QualifiedAlias("datoms01".to_string(), DatomsColumn::Value)), left: ColumnOrExpression::Column(QualifiedAlias::new("datoms01".to_string(), DatomsColumn::Value)),
list: vec![ list: vec![
ColumnOrExpression::Entid(123), ColumnOrExpression::Entid(123),
], ],
}; };
let three = Constraint::In { let three = Constraint::In {
left: ColumnOrExpression::Column(QualifiedAlias("datoms01".to_string(), DatomsColumn::Value)), left: ColumnOrExpression::Column(QualifiedAlias::new("datoms01".to_string(), DatomsColumn::Value)),
list: vec![ list: vec![
ColumnOrExpression::Entid(123), ColumnOrExpression::Entid(123),
ColumnOrExpression::Entid(456), ColumnOrExpression::Entid(456),
@ -476,8 +503,8 @@ mod tests {
let datoms01 = "datoms01".to_string(); let datoms01 = "datoms01".to_string();
let eq = Op("="); let eq = Op("=");
let source_aliases = vec![ let source_aliases = vec![
SourceAlias(DatomsTable::Datoms, datoms00.clone()), TableOrSubquery::Table(SourceAlias(DatomsTable::Datoms, datoms00.clone())),
SourceAlias(DatomsTable::Datoms, datoms01.clone()), TableOrSubquery::Table(SourceAlias(DatomsTable::Datoms, datoms01.clone())),
]; ];
let mut query = SelectQuery { let mut query = SelectQuery {
@ -485,24 +512,24 @@ mod tests {
projection: Projection::Columns( projection: Projection::Columns(
vec![ vec![
ProjectedColumn( ProjectedColumn(
ColumnOrExpression::Column(QualifiedAlias(datoms00.clone(), DatomsColumn::Entity)), ColumnOrExpression::Column(QualifiedAlias::new(datoms00.clone(), DatomsColumn::Entity)),
"x".to_string()), "x".to_string()),
]), ]),
from: FromClause::TableList(TableList(source_aliases)), from: FromClause::TableList(TableList(source_aliases)),
constraints: vec![ constraints: vec![
Constraint::Infix { Constraint::Infix {
op: eq.clone(), op: eq.clone(),
left: ColumnOrExpression::Column(QualifiedAlias(datoms01.clone(), DatomsColumn::Value)), left: ColumnOrExpression::Column(QualifiedAlias::new(datoms01.clone(), DatomsColumn::Value)),
right: ColumnOrExpression::Column(QualifiedAlias(datoms00.clone(), DatomsColumn::Value)), right: ColumnOrExpression::Column(QualifiedAlias::new(datoms00.clone(), DatomsColumn::Value)),
}, },
Constraint::Infix { Constraint::Infix {
op: eq.clone(), op: eq.clone(),
left: ColumnOrExpression::Column(QualifiedAlias(datoms00.clone(), DatomsColumn::Attribute)), left: ColumnOrExpression::Column(QualifiedAlias::new(datoms00.clone(), DatomsColumn::Attribute)),
right: ColumnOrExpression::Entid(65537), right: ColumnOrExpression::Entid(65537),
}, },
Constraint::Infix { Constraint::Infix {
op: eq.clone(), op: eq.clone(),
left: ColumnOrExpression::Column(QualifiedAlias(datoms01.clone(), DatomsColumn::Attribute)), left: ColumnOrExpression::Column(QualifiedAlias::new(datoms01.clone(), DatomsColumn::Attribute)),
right: ColumnOrExpression::Entid(65536), right: ColumnOrExpression::Entid(65536),
}, },
], ],

View file

@ -20,10 +20,14 @@ use mentat_query_algebrizer::{
ColumnConstraint, ColumnConstraint,
ColumnConstraintOrAlternation, ColumnConstraintOrAlternation,
ColumnIntersection, ColumnIntersection,
ComputedTable,
ConjoiningClauses, ConjoiningClauses,
DatomsColumn, DatomsColumn,
DatomsTable,
QualifiedAlias, QualifiedAlias,
QueryValue, QueryValue,
SourceAlias,
TableAlias,
}; };
use mentat_query_projector::{ use mentat_query_projector::{
@ -37,9 +41,11 @@ use mentat_query_sql::{
Constraint, Constraint,
FromClause, FromClause,
Op, Op,
ProjectedColumn,
Projection, Projection,
SelectQuery, SelectQuery,
TableList, TableList,
TableOrSubquery,
}; };
trait ToConstraint { trait ToConstraint {
@ -136,7 +142,7 @@ impl ToConstraint for ColumnConstraint {
}, },
HasType(table, value_type) => { HasType(table, value_type) => {
let column = QualifiedAlias(table, DatomsColumn::ValueTypeTag).to_column(); let column = QualifiedAlias::new(table, DatomsColumn::ValueTypeTag).to_column();
Constraint::equal(column, ColumnOrExpression::Integer(value_type.value_type_tag())) Constraint::equal(column, ColumnOrExpression::Integer(value_type.value_type_tag()))
}, },
} }
@ -148,6 +154,46 @@ pub struct ProjectedSelect{
pub projector: Box<Projector>, pub projector: Box<Projector>,
} }
// Nasty little hack to let us move out of indexed context.
struct ConsumableVec<T> {
inner: Vec<Option<T>>,
}
impl<T> From<Vec<T>> for ConsumableVec<T> {
fn from(vec: Vec<T>) -> ConsumableVec<T> {
ConsumableVec { inner: vec.into_iter().map(|x| Some(x)).collect() }
}
}
impl<T> ConsumableVec<T> {
fn take_dangerously(&mut self, i: usize) -> T {
::std::mem::replace(&mut self.inner[i], None).expect("each value to only be fetched once")
}
}
fn table_for_computed(computed: ComputedTable, alias: TableAlias) -> TableOrSubquery {
match computed {
ComputedTable::Union {
projection, type_extraction, arms,
} => {
// The projection list for each CC must have the same shape and the same names.
// The values we project might be fixed or they might be columns, and of course
// each arm will have different columns.
// TODO: type extraction.
let queries = arms.into_iter()
.map(|cc| {
let var_columns = projection.iter().map(|var| {
let col = cc.column_bindings.get(&var).unwrap()[0].clone();
ProjectedColumn(ColumnOrExpression::Column(col), var.to_string())
}).collect();
let projection = Projection::Columns(var_columns);
cc_to_select_query(projection, cc, false, None)
}).collect();
TableOrSubquery::Union(queries, alias)
},
}
}
/// Returns a `SelectQuery` that queries for the provided `cc`. Note that this _always_ returns a /// Returns a `SelectQuery` that queries for the provided `cc`. Note that this _always_ returns a
/// query that runs SQL. The next level up the call stack can check for known-empty queries if /// query that runs SQL. The next level up the call stack can check for known-empty queries if
/// needed. /// needed.
@ -155,7 +201,24 @@ fn cc_to_select_query<T: Into<Option<u64>>>(projection: Projection, cc: Conjoini
let from = if cc.from.is_empty() { let from = if cc.from.is_empty() {
FromClause::Nothing FromClause::Nothing
} else { } else {
FromClause::TableList(TableList(cc.from)) // Move these out of the CC.
let from = cc.from;
let mut computed: ConsumableVec<_> = cc.computed_tables.into();
let tables =
from.into_iter().map(|source_alias| {
match source_alias {
SourceAlias(DatomsTable::Computed(i), alias) => {
let comp = computed.take_dangerously(i);
table_for_computed(comp, alias)
},
_ => {
TableOrSubquery::Table(source_alias)
}
}
});
FromClause::TableList(TableList(tables.collect()))
}; };
let limit = if cc.empty_because.is_some() { Some(0) } else { limit.into() }; let limit = if cc.empty_because.is_some() { Some(0) } else { limit.into() };