Begin serializing queries to SQL. r=nalexander
This commit is contained in:
parent
a9cd9b1e87
commit
9ecd02ef95
5 changed files with 307 additions and 0 deletions
|
@ -50,5 +50,8 @@ path = "query-parser"
|
||||||
[dependencies.mentat_query_algebrizer]
|
[dependencies.mentat_query_algebrizer]
|
||||||
path = "query-algebrizer"
|
path = "query-algebrizer"
|
||||||
|
|
||||||
|
[dependencies.mentat_query_translator]
|
||||||
|
path = "query-translator"
|
||||||
|
|
||||||
[dependencies.mentat_tx_parser]
|
[dependencies.mentat_tx_parser]
|
||||||
path = "tx-parser"
|
path = "tx-parser"
|
||||||
|
|
13
query-translator/Cargo.toml
Normal file
13
query-translator/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "mentat_query_translator"
|
||||||
|
version = "0.0.1"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
[dependencies.mentat_sql]
|
||||||
|
path = "../sql"
|
||||||
|
|
||||||
|
[dependencies.mentat_query]
|
||||||
|
path = "../query"
|
||||||
|
|
||||||
|
[dependencies.mentat_query_algebrizer]
|
||||||
|
path = "../query-algebrizer"
|
3
query-translator/README.md
Normal file
3
query-translator/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
This crate turns an algebrized Datalog query into a domain-specific representation of a SQL query, and then uses `mentat_sql` to turn that into a SQL string to be executed.
|
||||||
|
|
||||||
|
This subsumes both planning and query construction, because in Mentat the SQL query is effectively a query plan.
|
15
query-translator/src/lib.rs
Normal file
15
query-translator/src/lib.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// 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_query;
|
||||||
|
extern crate mentat_query_algebrizer;
|
||||||
|
extern crate mentat_sql;
|
||||||
|
|
||||||
|
mod types;
|
273
query-translator/src/types.rs
Normal file
273
query-translator/src/types.rs
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
// 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,
|
||||||
|
ConjoiningClauses,
|
||||||
|
DatomsColumn,
|
||||||
|
DatomsTable,
|
||||||
|
QualifiedAlias,
|
||||||
|
SourceAlias,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mentat_sql::{
|
||||||
|
BuildQueryError,
|
||||||
|
BuildQueryResult,
|
||||||
|
QueryBuilder,
|
||||||
|
QueryFragment,
|
||||||
|
SQLiteQueryBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
|
//---------------------------------------------------------
|
||||||
|
// A Mentat-focused representation of a SQL query.
|
||||||
|
|
||||||
|
enum ColumnOrExpression {
|
||||||
|
Column(QualifiedAlias),
|
||||||
|
Integer(i64), // Because it's so common.
|
||||||
|
}
|
||||||
|
|
||||||
|
type Name = String;
|
||||||
|
struct Projection (ColumnOrExpression, Name);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Op(String); // TODO
|
||||||
|
enum Constraint {
|
||||||
|
Infix {
|
||||||
|
op: Op,
|
||||||
|
left: ColumnOrExpression,
|
||||||
|
right: ColumnOrExpression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JoinOp {
|
||||||
|
Inner,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short-hand for a list of tables all inner-joined.
|
||||||
|
struct TableList(Vec<SourceAlias>);
|
||||||
|
|
||||||
|
struct Join {
|
||||||
|
left: TableOrSubquery,
|
||||||
|
op: JoinOp,
|
||||||
|
right: TableOrSubquery,
|
||||||
|
// TODO: constraints (ON, USING).
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TableOrSubquery {
|
||||||
|
Table(SourceAlias),
|
||||||
|
// TODO: Subquery.
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FromClause {
|
||||||
|
TableList(TableList), // Short-hand for a pile of inner joins.
|
||||||
|
Join(Join),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SelectQuery {
|
||||||
|
projection: Vec<Projection>,
|
||||||
|
from: FromClause,
|
||||||
|
constraints: Vec<Constraint>,
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------
|
||||||
|
// Turn that representation into SQL.
|
||||||
|
|
||||||
|
impl QueryFragment for ColumnOrExpression {
|
||||||
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
|
||||||
|
use self::ColumnOrExpression::*;
|
||||||
|
match self {
|
||||||
|
&Column(QualifiedAlias(ref table, ref column)) => {
|
||||||
|
out.push_identifier(table.as_str())?;
|
||||||
|
out.push_sql(".");
|
||||||
|
out.push_identifier(column.as_str())
|
||||||
|
},
|
||||||
|
&Integer(i) => {
|
||||||
|
out.push_sql(i.to_string().as_str());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryFragment for Projection {
|
||||||
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
|
||||||
|
self.0.push_sql(out)?;
|
||||||
|
out.push_sql(" AS ");
|
||||||
|
out.push_identifier(self.1.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryFragment for Op {
|
||||||
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
|
||||||
|
// No escaping needed.
|
||||||
|
out.push_sql(self.0.as_str());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryFragment for Constraint {
|
||||||
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
|
||||||
|
use self::Constraint::*;
|
||||||
|
match self {
|
||||||
|
&Infix { ref op, ref left, ref right } => {
|
||||||
|
left.push_sql(out)?;
|
||||||
|
out.push_sql(" ");
|
||||||
|
op.push_sql(out)?;
|
||||||
|
out.push_sql(" ");
|
||||||
|
right.push_sql(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryFragment for JoinOp {
|
||||||
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
|
||||||
|
out.push_sql(" JOIN ");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't own SourceAlias or QueryFragment, so we can't implement the trait.
|
||||||
|
fn source_alias_push_sql(out: &mut QueryBuilder, sa: &SourceAlias) -> BuildQueryResult {
|
||||||
|
let &SourceAlias(ref table, ref alias) = sa;
|
||||||
|
out.push_identifier(table.name())?;
|
||||||
|
out.push_sql(" AS ");
|
||||||
|
out.push_identifier(alias.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryFragment for TableList {
|
||||||
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
|
||||||
|
if self.0.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
source_alias_push_sql(out, &self.0[0])?;
|
||||||
|
|
||||||
|
for sa in self.0.iter().skip(1) {
|
||||||
|
out.push_sql(", ");
|
||||||
|
source_alias_push_sql(out, sa)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryFragment for Join {
|
||||||
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
|
||||||
|
self.left.push_sql(out)?;
|
||||||
|
self.op.push_sql(out)?;
|
||||||
|
self.right.push_sql(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryFragment for FromClause {
|
||||||
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
|
||||||
|
use self::FromClause::*;
|
||||||
|
match self {
|
||||||
|
&TableList(ref table_list) => table_list.push_sql(out),
|
||||||
|
&Join(ref join) => join.push_sql(out),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryFragment for SelectQuery {
|
||||||
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
|
||||||
|
out.push_sql("SELECT ");
|
||||||
|
self.projection[0].push_sql(out)?;
|
||||||
|
|
||||||
|
for projection in self.projection[1..].iter() {
|
||||||
|
out.push_sql(", ");
|
||||||
|
projection.push_sql(out)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push_sql(" FROM ");
|
||||||
|
self.from.push_sql(out)?;
|
||||||
|
|
||||||
|
if self.constraints.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push_sql(" WHERE ");
|
||||||
|
self.constraints[0].push_sql(out)?;
|
||||||
|
|
||||||
|
for constraint in self.constraints[1..].iter() {
|
||||||
|
out.push_sql(" AND ");
|
||||||
|
constraint.push_sql(out)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectQuery {
|
||||||
|
fn to_sql_string(&self) -> Result<String, BuildQueryError> {
|
||||||
|
let mut builder = SQLiteQueryBuilder::new();
|
||||||
|
self.push_sql(&mut builder).map(|_| builder.finish())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_end_to_end() {
|
||||||
|
|
||||||
|
// [:find ?x :where [?x 65537 ?v] [?x 65536 ?v]]
|
||||||
|
let datoms00 = "datoms00".to_string();
|
||||||
|
let datoms01 = "datoms01".to_string();
|
||||||
|
let eq = Op("=".to_string());
|
||||||
|
let source_aliases = vec![
|
||||||
|
SourceAlias(DatomsTable::Datoms, datoms00.clone()),
|
||||||
|
SourceAlias(DatomsTable::Datoms, datoms01.clone()),
|
||||||
|
];
|
||||||
|
let query = SelectQuery {
|
||||||
|
projection: vec![
|
||||||
|
Projection(
|
||||||
|
ColumnOrExpression::Column(QualifiedAlias(datoms00.clone(), DatomsColumn::Entity)),
|
||||||
|
"x".to_string(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
from: FromClause::TableList(TableList(source_aliases)),
|
||||||
|
constraints: vec![
|
||||||
|
//ColumnOrExpression::Expression(TypedValue::Integer(15)),
|
||||||
|
Constraint::Infix {
|
||||||
|
op: eq.clone(),
|
||||||
|
left: ColumnOrExpression::Column(QualifiedAlias(datoms01.clone(), DatomsColumn::Value)),
|
||||||
|
right: ColumnOrExpression::Column(QualifiedAlias(datoms00.clone(), DatomsColumn::Value)),
|
||||||
|
},
|
||||||
|
Constraint::Infix {
|
||||||
|
op: eq.clone(),
|
||||||
|
left: ColumnOrExpression::Column(QualifiedAlias(datoms00.clone(), DatomsColumn::Attribute)),
|
||||||
|
right: ColumnOrExpression::Integer(65537),
|
||||||
|
},
|
||||||
|
Constraint::Infix {
|
||||||
|
op: eq.clone(),
|
||||||
|
left: ColumnOrExpression::Column(QualifiedAlias(datoms01.clone(), DatomsColumn::Attribute)),
|
||||||
|
right: ColumnOrExpression::Integer(65536),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let sql = query.to_sql_string().unwrap();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue