Implement query-translator. (#301) r=nalexander

This commit is contained in:
Richard Newman 2017-02-21 19:57:00 -08:00
parent 91c75f26c8
commit 5e3cdd1fc2
5 changed files with 243 additions and 42 deletions

View file

@ -4,6 +4,9 @@ version = "0.0.1"
workspace = ".." workspace = ".."
[dependencies] [dependencies]
[dependencies.mentat_core]
path = "../core"
[dependencies.mentat_sql] [dependencies.mentat_sql]
path = "../sql" path = "../sql"
@ -12,3 +15,7 @@ path = "../query"
[dependencies.mentat_query_algebrizer] [dependencies.mentat_query_algebrizer]
path = "../query-algebrizer" path = "../query-algebrizer"
# Only for tests.
[dev-dependencies.mentat_query_parser]
path = "../query-parser"

View file

@ -8,8 +8,15 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate mentat_core;
extern crate mentat_query; extern crate mentat_query;
extern crate mentat_query_algebrizer; extern crate mentat_query_algebrizer;
extern crate mentat_sql; extern crate mentat_sql;
mod translate;
mod types; mod types;
pub use translate::{
cc_to_exists,
cc_to_select,
};

View file

@ -0,0 +1,76 @@
// Copyright 2016 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
#![allow(dead_code, unused_imports)]
use mentat_query_algebrizer::{
AlgebraicQuery,
ColumnConstraint,
ConjoiningClauses,
DatomsColumn,
DatomsTable,
QualifiedAlias,
SourceAlias,
};
use types::{
ColumnOrExpression,
Constraint,
FromClause,
Projection,
SelectQuery,
TableList,
};
trait ToConstraint {
fn to_constraint(self) -> Constraint;
}
trait ToColumn {
fn to_column(self) -> ColumnOrExpression;
}
impl ToColumn for QualifiedAlias {
fn to_column(self) -> ColumnOrExpression {
ColumnOrExpression::Column(self)
}
}
impl ToConstraint for ColumnConstraint {
fn to_constraint(self) -> Constraint {
use self::ColumnConstraint::*;
match self {
EqualsEntity(qa, entid) =>
Constraint::equal(qa.to_column(), ColumnOrExpression::Entid(entid)),
EqualsValue(qa, tv) =>
Constraint::equal(qa.to_column(), ColumnOrExpression::Value(tv)),
EqualsColumn(left, right) =>
Constraint::equal(left.to_column(), right.to_column()),
}
}
}
/// Consume a provided `ConjoiningClauses` to yield a new
/// `SelectQuery`. A projection list must also be provided.
pub fn cc_to_select(projection: Projection, cc: ConjoiningClauses) -> SelectQuery {
SelectQuery {
projection: projection,
from: FromClause::TableList(TableList(cc.from)),
constraints: cc.wheres
.into_iter()
.map(|c| c.to_constraint())
.collect(),
}
}
pub fn cc_to_exists(cc: ConjoiningClauses) -> SelectQuery {
cc_to_select(Projection::One, cc)
}

View file

@ -10,6 +10,11 @@
#![allow(dead_code, unused_imports)] #![allow(dead_code, unused_imports)]
use mentat_core::{
Entid,
TypedValue,
};
use mentat_query_algebrizer::{ use mentat_query_algebrizer::{
AlgebraicQuery, AlgebraicQuery,
ConjoiningClauses, ConjoiningClauses,
@ -25,22 +30,38 @@ use mentat_sql::{
QueryBuilder, QueryBuilder,
QueryFragment, QueryFragment,
SQLiteQueryBuilder, SQLiteQueryBuilder,
SQLQuery,
}; };
//--------------------------------------------------------- //---------------------------------------------------------
// A Mentat-focused representation of a SQL query. // A Mentat-focused representation of a SQL query.
enum ColumnOrExpression { /// One of the things that can appear in a projection or a constraint. Note that we use
/// `TypedValue` here; it's not pure SQL, but it avoids us having to concern ourselves at this
/// point with the translation between a `TypedValue` and the storage-layer representation.
///
/// Eventually we might allow different translations by providing a different `QueryBuilder`
/// implementation for each storage backend. Passing `TypedValue`s here allows for that.
pub enum ColumnOrExpression {
Column(QualifiedAlias), Column(QualifiedAlias),
Integer(i64), // Because it's so common. Entid(Entid), // Because it's so common.
Value(TypedValue),
} }
type Name = String; pub type Name = String;
struct Projection (ColumnOrExpression, Name);
#[derive(Clone)] pub struct ProjectedColumn(pub ColumnOrExpression, pub Name);
struct Op(String); // TODO
enum Constraint { pub enum Projection {
Columns(Vec<ProjectedColumn>),
Star,
One,
}
#[derive(Copy, Clone)]
pub struct Op(&'static str); // TODO: we can do better than this!
pub enum Constraint {
Infix { Infix {
op: Op, op: Op,
left: ColumnOrExpression, left: ColumnOrExpression,
@ -48,14 +69,24 @@ enum Constraint {
} }
} }
impl Constraint {
pub fn equal(left: ColumnOrExpression, right: ColumnOrExpression) -> Constraint {
Constraint::Infix {
op: Op("="),
left: left,
right: right,
}
}
}
enum JoinOp { enum JoinOp {
Inner, Inner,
} }
// Short-hand for a list of tables all inner-joined. // Short-hand for a list of tables all inner-joined.
struct TableList(Vec<SourceAlias>); pub struct TableList(pub Vec<SourceAlias>);
struct Join { pub struct Join {
left: TableOrSubquery, left: TableOrSubquery,
op: JoinOp, op: JoinOp,
right: TableOrSubquery, right: TableOrSubquery,
@ -67,15 +98,20 @@ enum TableOrSubquery {
// TODO: Subquery. // TODO: Subquery.
} }
enum FromClause { pub enum FromClause {
TableList(TableList), // Short-hand for a pile of inner joins. TableList(TableList), // Short-hand for a pile of inner joins.
Join(Join), Join(Join),
} }
struct SelectQuery { pub struct SelectQuery {
projection: Vec<Projection>, pub projection: Projection,
from: FromClause, pub from: FromClause,
constraints: Vec<Constraint>, pub constraints: Vec<Constraint>,
}
// We know that DatomsColumns are safe to serialize.
fn push_column(qb: &mut QueryBuilder, col: &DatomsColumn) {
qb.push_sql(col.as_str());
} }
//--------------------------------------------------------- //---------------------------------------------------------
@ -88,28 +124,48 @@ 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(".");
out.push_identifier(column.as_str()) push_column(out, column);
},
&Integer(i) => {
out.push_sql(i.to_string().as_str());
Ok(()) Ok(())
} },
&Entid(entid) => {
out.push_sql(entid.to_string().as_str());
Ok(())
},
&Value(ref v) => {
out.push_typed_value(v)
},
} }
} }
} }
impl QueryFragment for Projection { impl QueryFragment for Projection {
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
self.0.push_sql(out)?; use self::Projection::*;
match self {
&One => out.push_sql("1"),
&Star => out.push_sql("*"),
&Columns(ref cols) => {
let &ProjectedColumn(ref col, ref alias) = &cols[0];
col.push_sql(out)?;
out.push_sql(" AS "); out.push_sql(" AS ");
out.push_identifier(self.1.as_str()) out.push_identifier(alias.as_str())?;
for &ProjectedColumn(ref col, ref alias) in &cols[1..] {
out.push_sql(", ");
col.push_sql(out)?;
out.push_sql(" AS ");
out.push_identifier(alias.as_str())?;
}
},
};
Ok(())
} }
} }
impl QueryFragment for Op { impl QueryFragment for Op {
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
// No escaping needed. // No escaping needed.
out.push_sql(self.0.as_str()); out.push_sql(self.0);
Ok(()) Ok(())
} }
} }
@ -190,12 +246,7 @@ impl QueryFragment for FromClause {
impl QueryFragment for SelectQuery { impl QueryFragment for SelectQuery {
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
out.push_sql("SELECT "); out.push_sql("SELECT ");
self.projection[0].push_sql(out)?; self.projection.push_sql(out)?;
for projection in self.projection[1..].iter() {
out.push_sql(", ");
projection.push_sql(out)?;
}
out.push_sql(" FROM "); out.push_sql(" FROM ");
self.from.push_sql(out)?; self.from.push_sql(out)?;
@ -217,7 +268,7 @@ impl QueryFragment for SelectQuery {
} }
impl SelectQuery { impl SelectQuery {
fn to_sql_string(&self) -> Result<String, BuildQueryError> { pub fn to_sql_query(&self) -> Result<SQLQuery, BuildQueryError> {
let mut builder = SQLiteQueryBuilder::new(); let mut builder = SQLiteQueryBuilder::new();
self.push_sql(&mut builder).map(|_| builder.finish()) self.push_sql(&mut builder).map(|_| builder.finish())
} }
@ -233,21 +284,20 @@ mod tests {
// [:find ?x :where [?x 65537 ?v] [?x 65536 ?v]] // [:find ?x :where [?x 65537 ?v] [?x 65536 ?v]]
let datoms00 = "datoms00".to_string(); let datoms00 = "datoms00".to_string();
let datoms01 = "datoms01".to_string(); let datoms01 = "datoms01".to_string();
let eq = Op("=".to_string()); let eq = Op("=");
let source_aliases = vec![ let source_aliases = vec![
SourceAlias(DatomsTable::Datoms, datoms00.clone()), SourceAlias(DatomsTable::Datoms, datoms00.clone()),
SourceAlias(DatomsTable::Datoms, datoms01.clone()), SourceAlias(DatomsTable::Datoms, datoms01.clone()),
]; ];
let query = SelectQuery { let query = SelectQuery {
projection: vec![ projection: Projection::Columns(
Projection( vec![
ProjectedColumn(
ColumnOrExpression::Column(QualifiedAlias(datoms00.clone(), DatomsColumn::Entity)), ColumnOrExpression::Column(QualifiedAlias(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![
//ColumnOrExpression::Expression(TypedValue::Integer(15)),
Constraint::Infix { Constraint::Infix {
op: eq.clone(), op: eq.clone(),
left: ColumnOrExpression::Column(QualifiedAlias(datoms01.clone(), DatomsColumn::Value)), left: ColumnOrExpression::Column(QualifiedAlias(datoms01.clone(), DatomsColumn::Value)),
@ -256,18 +306,19 @@ mod tests {
Constraint::Infix { Constraint::Infix {
op: eq.clone(), op: eq.clone(),
left: ColumnOrExpression::Column(QualifiedAlias(datoms00.clone(), DatomsColumn::Attribute)), left: ColumnOrExpression::Column(QualifiedAlias(datoms00.clone(), DatomsColumn::Attribute)),
right: ColumnOrExpression::Integer(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(datoms01.clone(), DatomsColumn::Attribute)),
right: ColumnOrExpression::Integer(65536), right: ColumnOrExpression::Entid(65536),
}, },
], ],
}; };
let sql = query.to_sql_string().unwrap(); let SQLQuery { sql, args } = query.to_sql_query().unwrap();
println!("{}", sql); println!("{}", sql);
assert_eq!("SELECT `datoms00`.`e` AS `x` FROM `datoms` AS `datoms00`, `datoms` AS `datoms01` WHERE `datoms01`.`v` = `datoms00`.`v` AND `datoms00`.`a` = 65537 AND `datoms01`.`a` = 65536", sql); assert_eq!("SELECT `datoms00`.e AS `x` FROM `datoms` AS `datoms00`, `datoms` AS `datoms01` WHERE `datoms01`.v = `datoms00`.v AND `datoms00`.a = 65537 AND `datoms01`.a = 65536", sql);
assert!(args.is_empty());
} }
} }

View file

@ -0,0 +1,60 @@
// Copyright 2016 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
extern crate mentat_core;
extern crate mentat_query;
extern crate mentat_query_algebrizer;
extern crate mentat_query_parser;
extern crate mentat_query_translator;
extern crate mentat_sql;
use mentat_query::NamespacedKeyword;
use mentat_core::{
Attribute,
Entid,
Schema,
ValueType,
};
use mentat_query_parser::parse_find_string;
use mentat_query_algebrizer::algebrize;
use mentat_query_translator::{
cc_to_exists,
};
use mentat_sql::SQLQuery;
fn associate_ident(schema: &mut Schema, i: NamespacedKeyword, e: Entid) {
schema.entid_map.insert(e, i.clone());
schema.ident_map.insert(i.clone(), e);
}
fn add_attribute(schema: &mut Schema, e: Entid, a: Attribute) {
schema.schema_map.insert(e, a);
}
#[test]
fn test_exists() {
let mut schema = Schema::default();
associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99);
add_attribute(&mut schema, 99, Attribute {
value_type: ValueType::String,
..Default::default()
});
let input = r#"[:find ?x :where [?x :foo/bar "yyy"]]"#;
let parsed = parse_find_string(input).unwrap();
let algebrized = algebrize(&schema, parsed);
let select = cc_to_exists(algebrized.cc);
let SQLQuery { sql, args } = select.to_sql_query().unwrap();
assert_eq!(sql, "SELECT 1 FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0");
assert_eq!(args, vec![("$v0".to_string(), "yyy".to_string())]);
}