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,
ColumnIntersection,
ComputedTable,
Column,
DatomsColumn,
DatomsTable,
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?
if let Some(bound_val) = self.bound_value(&var) {
// Great! Use that instead.
// We expect callers to do things like bind keywords here; we need to translate these
// before they hit our constraints.
// TODO: recognize when the valueType might be a ref and also translate entids there.
if column == DatomsColumn::Value {
self.constrain_column_to_constant(table, column, bound_val);
} else {
match bound_val {
TypedValue::Keyword(ref kw) => {
if let Some(entid) = self.entid_for_ident(schema, kw) {
match column {
Column::Variable(_) => {
// We don't need to handle expansion of attributes here. The subquery that
// produces the variable projection will do so.
self.constrain_column_to_constant(table, column, bound_val);
},
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);
} 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);
},
_ => {
// One can't bind an e, a, or tx to something other than an entity.
self.mark_known_empty(EmptyBecause::InvalidBinding(column, bound_val));
},
},
_ => {
// 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
// to get its type, record that we can get it from this table.
let needs_type_extraction =
!late_binding && // Never need to extract for bound vars.
column == DatomsColumn::Value && // Never need to extract types for refs.
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.
!late_binding && // Never need to extract for bound vars.
// Never need to extract types for refs, and var columns are handled elsewhere:
// a subquery will be projecting a type tag.
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);
@ -343,11 +373,13 @@ impl ConjoiningClauses {
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)))
}
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)))
}
@ -357,7 +389,7 @@ impl ConjoiningClauses {
pub fn constrain_value_to_numeric(&mut self, table: TableAlias, value: i64) {
self.wheres.add_intersection(ColumnConstraint::Equals(
QualifiedAlias(table, DatomsColumn::Value),
QualifiedAlias(table, Column::Fixed(DatomsColumn::Value)),
QueryValue::PrimitiveLong(value)))
}
@ -586,7 +618,7 @@ impl ConjoiningClauses {
Some(v) => {
// This pattern cannot match: the caller has bound a non-entity value to an
// 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 vx = Variable::from_valid_name("?x");
let d0 = "datoms00".to_string();
let d0e = QualifiedAlias(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias(d0.clone(), DatomsColumn::Attribute);
let d0v = QualifiedAlias(d0.clone(), DatomsColumn::Value);
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 knows = QueryValue::Entid(66);
let parent = QueryValue::Entid(67);
let john = QueryValue::TypedValue(TypedValue::typed_string("John"));
@ -647,11 +647,11 @@ mod testing {
let vx = Variable::from_valid_name("?x");
let d0 = "datoms00".to_string();
let d1 = "datoms01".to_string();
let d0e = QualifiedAlias(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias(d0.clone(), DatomsColumn::Attribute);
let d1e = QualifiedAlias(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias(d1.clone(), DatomsColumn::Attribute);
let d1v = QualifiedAlias(d1.clone(), DatomsColumn::Value);
let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
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 name = QueryValue::Entid(65);
let knows = QueryValue::Entid(66);
let parent = QueryValue::Entid(67);
@ -697,12 +697,12 @@ mod testing {
let vx = Variable::from_valid_name("?x");
let d0 = "datoms00".to_string();
let d1 = "datoms01".to_string();
let d0e = QualifiedAlias(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias(d0.clone(), DatomsColumn::Attribute);
let d0v = QualifiedAlias(d0.clone(), DatomsColumn::Value);
let d1e = QualifiedAlias(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias(d1.clone(), DatomsColumn::Attribute);
let d1v = QualifiedAlias(d1.clone(), DatomsColumn::Value);
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 d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias::new(d1.clone(), DatomsColumn::Attribute);
let d1v = QualifiedAlias::new(d1.clone(), DatomsColumn::Value);
let knows = QueryValue::Entid(66);
let age = QueryValue::Entid(68);
let john = QueryValue::TypedValue(TypedValue::typed_string("John"));
@ -736,8 +736,9 @@ mod testing {
// 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] [?x :foo/baz ?y]]
// TODO: fixme
/*
#[test]
#[should_panic(expected = "not yet implemented")]
fn test_unit_or_join_doesnt_flatten() {
let schema = prepopulated_schema();
let query = r#"[:find ?x
@ -748,21 +749,20 @@ mod testing {
let vy = Variable::from_valid_name("?y");
let d0 = "datoms00".to_string();
let d1 = "datoms01".to_string();
let d0e = QualifiedAlias(d0.clone(), DatomsColumn::Entity);
let d0a = QualifiedAlias(d0.clone(), DatomsColumn::Attribute);
let d0v = QualifiedAlias(d0.clone(), DatomsColumn::Value);
let d1e = QualifiedAlias(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias(d1.clone(), DatomsColumn::Attribute);
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 d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
let d1a = QualifiedAlias::new(d1.clone(), DatomsColumn::Attribute);
let knows = QueryValue::Entid(66);
let parent = QueryValue::Entid(67);
assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![
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
// 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]));
@ -771,6 +771,7 @@ mod testing {
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0),
SourceAlias(DatomsTable::Datoms, d1)]);
}
*/
// These two are equivalent:
// [: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,
/// but that would be a fair amount of analysis work, I think.
#[test]
#[should_panic(expected = "not yet implemented")]
#[allow(dead_code, unused_variables)]
// TODO: flesh this out.
fn test_alternation_with_and() {
let schema = prepopulated_schema();
let query = r#"

View file

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

View file

@ -106,10 +106,12 @@ pub use clauses::{
};
pub use types::{
Column,
ColumnAlternation,
ColumnConstraint,
ColumnConstraintOrAlternation,
ColumnIntersection,
ColumnName,
ComputedTable,
DatomsColumn,
DatomsTable,
@ -117,5 +119,6 @@ pub use types::{
QueryValue,
SourceAlias,
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.
#[derive(PartialEq, Eq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone)]
pub enum DatomsColumn {
Entity,
Attribute,
@ -71,6 +75,30 @@ pub enum DatomsColumn {
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 {
pub fn as_str(&self) -> &'static str {
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".
pub type TableAlias = String;
@ -99,17 +167,22 @@ impl Debug for SourceAlias {
/// A particular column of a particular aliased table. E.g., "datoms123", Attribute.
#[derive(PartialEq, Eq, Clone)]
pub struct QualifiedAlias(pub TableAlias, pub DatomsColumn);
pub struct QualifiedAlias(pub TableAlias, pub Column);
impl Debug for QualifiedAlias {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(f, "{}.{}", self.0, self.1.as_str())
write!(f, "{}.{:?}", self.0, self.1)
}
}
impl QualifiedAlias {
pub fn new<C: Into<Column>>(table: TableAlias, column: C) -> Self {
QualifiedAlias(table, column.into())
}
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),
InvalidAttributeIdent(NamespacedKeyword),
InvalidAttributeEntid(Entid),
InvalidBinding(DatomsColumn, TypedValue),
InvalidBinding(Column, TypedValue),
ValueTypeMismatch(ValueType, TypedValue),
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.
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));
}
}

View file

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

View file

@ -20,10 +20,14 @@ use mentat_query_algebrizer::{
ColumnConstraint,
ColumnConstraintOrAlternation,
ColumnIntersection,
ComputedTable,
ConjoiningClauses,
DatomsColumn,
DatomsTable,
QualifiedAlias,
QueryValue,
SourceAlias,
TableAlias,
};
use mentat_query_projector::{
@ -37,9 +41,11 @@ use mentat_query_sql::{
Constraint,
FromClause,
Op,
ProjectedColumn,
Projection,
SelectQuery,
TableList,
TableOrSubquery,
};
trait ToConstraint {
@ -136,7 +142,7 @@ impl ToConstraint for ColumnConstraint {
},
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()))
},
}
@ -148,6 +154,46 @@ pub struct ProjectedSelect{
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
/// query that runs SQL. The next level up the call stack can check for known-empty queries if
/// 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() {
FromClause::Nothing
} 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() };