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:
parent
7948788936
commit
08d2c613a4
8 changed files with 303 additions and 103 deletions
|
@ -49,6 +49,7 @@ use types::{
|
|||
ColumnConstraint,
|
||||
ColumnIntersection,
|
||||
ComputedTable,
|
||||
Column,
|
||||
DatomsColumn,
|
||||
DatomsTable,
|
||||
EmptyBecause,
|
||||
|
@ -286,16 +287,42 @@ 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 {
|
||||
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);
|
||||
} else {
|
||||
},
|
||||
|
||||
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) {
|
||||
|
@ -317,6 +344,7 @@ impl ConjoiningClauses {
|
|||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -329,7 +357,9 @@ impl ConjoiningClauses {
|
|||
// 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.
|
||||
// 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.
|
||||
|
||||
|
@ -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()))
|
||||
},
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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#"
|
||||
|
|
|
@ -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![
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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() };
|
||||
|
|
Loading…
Reference in a new issue