Implement projection and querying. (#353) r=nalexander
* Add a failing test for EDN parsing '…'. * Expose a SQLValueType trait to get value_type_tag values out of a ValueType. * Add accessors to FindSpec. * Implement querying. * Implement rudimentary projection. * Export mentat_db::new_connection. * Export symbols from mentat. * Add rudimentary end-to-end query tests.
This commit is contained in:
parent
f86b24001f
commit
70b112801c
19 changed files with 821 additions and 74 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -48,11 +48,17 @@ path = "db"
|
||||||
[dependencies.mentat_query]
|
[dependencies.mentat_query]
|
||||||
path = "query"
|
path = "query"
|
||||||
|
|
||||||
|
[dependencies.mentat_query_algebrizer]
|
||||||
|
path = "query-algebrizer"
|
||||||
|
|
||||||
[dependencies.mentat_query_parser]
|
[dependencies.mentat_query_parser]
|
||||||
path = "query-parser"
|
path = "query-parser"
|
||||||
|
|
||||||
[dependencies.mentat_query_algebrizer]
|
[dependencies.mentat_query_projector]
|
||||||
path = "query-algebrizer"
|
path = "query-projector"
|
||||||
|
|
||||||
|
[dependencies.mentat_query_sql]
|
||||||
|
path = "query-sql"
|
||||||
|
|
||||||
[dependencies.mentat_query_translator]
|
[dependencies.mentat_query_translator]
|
||||||
path = "query-translator"
|
path = "query-translator"
|
||||||
|
|
19
db/src/db.rs
19
db/src/db.rs
|
@ -331,6 +331,25 @@ pub fn ensure_current_version(conn: &mut rusqlite::Connection) -> Result<DB> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait SQLValueType {
|
||||||
|
fn value_type_tag(&self) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SQLValueType for ValueType {
|
||||||
|
fn value_type_tag(&self) -> i32 {
|
||||||
|
match *self {
|
||||||
|
ValueType::Ref => 0,
|
||||||
|
ValueType::Boolean => 1,
|
||||||
|
ValueType::Instant => 4,
|
||||||
|
// SQLite distinguishes integral from decimal types, allowing long and double to share a tag.
|
||||||
|
ValueType::Long => 5,
|
||||||
|
ValueType::Double => 5,
|
||||||
|
ValueType::String => 10,
|
||||||
|
ValueType::Keyword => 13,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait TypedSQLValue {
|
pub trait TypedSQLValue {
|
||||||
fn from_sql_value_pair(value: rusqlite::types::Value, value_type_tag: i32) -> Result<TypedValue>;
|
fn from_sql_value_pair(value: rusqlite::types::Value, value_type_tag: i32) -> Result<TypedValue>;
|
||||||
fn to_sql_value_pair<'a>(&'a self) -> (ToSqlOutput<'a>, i32);
|
fn to_sql_value_pair<'a>(&'a self) -> (ToSqlOutput<'a>, i32);
|
||||||
|
|
|
@ -40,6 +40,12 @@ mod upsert_resolution;
|
||||||
mod values;
|
mod values;
|
||||||
mod tx;
|
mod tx;
|
||||||
|
|
||||||
|
pub use db::{
|
||||||
|
SQLValueType,
|
||||||
|
TypedSQLValue,
|
||||||
|
new_connection,
|
||||||
|
};
|
||||||
|
|
||||||
pub use tx::transact;
|
pub use tx::transact;
|
||||||
pub use types::{
|
pub use types::{
|
||||||
DB,
|
DB,
|
||||||
|
|
|
@ -193,12 +193,12 @@ pub struct ConjoiningClauses {
|
||||||
pub wheres: Vec<ColumnConstraint>,
|
pub wheres: Vec<ColumnConstraint>,
|
||||||
|
|
||||||
/// A map from var to qualified columns. Used to project.
|
/// A map from var to qualified columns. Used to project.
|
||||||
bindings: BTreeMap<Variable, Vec<QualifiedAlias>>,
|
pub bindings: BTreeMap<Variable, Vec<QualifiedAlias>>,
|
||||||
|
|
||||||
/// A map from var to type. Whenever a var maps unambiguously to two different types, it cannot
|
/// A map from var to type. Whenever a var maps unambiguously to two different types, it cannot
|
||||||
/// yield results, so we don't represent that case here. If a var isn't present in the map, it
|
/// yield results, so we don't represent that case here. If a var isn't present in the map, it
|
||||||
/// means that its type is not known in advance.
|
/// means that its type is not known in advance.
|
||||||
known_types: BTreeMap<Variable, ValueType>,
|
pub known_types: BTreeMap<Variable, ValueType>,
|
||||||
|
|
||||||
/// A mapping, similar to `bindings`, but used to pull type tags out of the store at runtime.
|
/// A mapping, similar to `bindings`, but used to pull type tags out of the store at runtime.
|
||||||
/// If a var isn't present in `known_types`, it should be present here.
|
/// If a var isn't present in `known_types`, it should be present here.
|
||||||
|
|
|
@ -27,9 +27,9 @@ use mentat_query::{
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct AlgebraicQuery {
|
pub struct AlgebraicQuery {
|
||||||
default_source: SrcVar,
|
default_source: SrcVar,
|
||||||
find_spec: FindSpec,
|
pub find_spec: FindSpec,
|
||||||
has_aggregates: bool,
|
has_aggregates: bool,
|
||||||
limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
pub cc: cc::ConjoiningClauses,
|
pub cc: cc::ConjoiningClauses,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
35
query-projector/Cargo.toml
Normal file
35
query-projector/Cargo.toml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
[package]
|
||||||
|
name = "mentat_query_projector"
|
||||||
|
version = "0.0.1"
|
||||||
|
workspace = ".."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
error-chain = "0.9.0"
|
||||||
|
|
||||||
|
[dependencies.rusqlite]
|
||||||
|
version = "0.9.5"
|
||||||
|
# System sqlite might be very old.
|
||||||
|
features = ["bundled"]
|
||||||
|
|
||||||
|
[dependencies.mentat_core]
|
||||||
|
path = "../core"
|
||||||
|
|
||||||
|
[dependencies.mentat_db]
|
||||||
|
path = "../db"
|
||||||
|
|
||||||
|
[dependencies.mentat_sql]
|
||||||
|
path = "../sql"
|
||||||
|
|
||||||
|
[dependencies.mentat_query]
|
||||||
|
path = "../query"
|
||||||
|
|
||||||
|
[dependencies.mentat_query_algebrizer]
|
||||||
|
path = "../query-algebrizer"
|
||||||
|
|
||||||
|
# Only for tests.
|
||||||
|
[dev-dependencies.mentat_query_parser]
|
||||||
|
path = "../query-parser"
|
||||||
|
|
||||||
|
[dependencies.mentat_query_sql]
|
||||||
|
path = "../query-sql"
|
||||||
|
|
6
query-projector/README.md
Normal file
6
query-projector/README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
This module handles the derivation from an algebrized query of two things:
|
||||||
|
|
||||||
|
- A SQL projection: a mapping from columns mentioned in the body of the query to columns in the output.
|
||||||
|
- A Datalog projection: a function that consumes rows of the appropriate shape (as defined by the SQL projection) to yield one of the four kinds of Datalog query result.
|
||||||
|
|
||||||
|
These two must naturally coordinate, and so they are both produced here.
|
423
query-projector/src/lib.rs
Normal file
423
query-projector/src/lib.rs
Normal file
|
@ -0,0 +1,423 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate error_chain;
|
||||||
|
extern crate rusqlite;
|
||||||
|
|
||||||
|
extern crate mentat_core;
|
||||||
|
extern crate mentat_db; // For value conversion.
|
||||||
|
extern crate mentat_query;
|
||||||
|
extern crate mentat_query_algebrizer;
|
||||||
|
extern crate mentat_query_sql;
|
||||||
|
extern crate mentat_sql;
|
||||||
|
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
use rusqlite::{
|
||||||
|
Row,
|
||||||
|
Rows,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mentat_core::{
|
||||||
|
TypedValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mentat_db::{
|
||||||
|
SQLValueType,
|
||||||
|
TypedSQLValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mentat_query::{
|
||||||
|
Element,
|
||||||
|
FindSpec,
|
||||||
|
PlainSymbol,
|
||||||
|
Variable,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mentat_query_algebrizer::{
|
||||||
|
AlgebraicQuery,
|
||||||
|
DatomsColumn,
|
||||||
|
QualifiedAlias,
|
||||||
|
/*
|
||||||
|
ConjoiningClauses,
|
||||||
|
DatomsTable,
|
||||||
|
SourceAlias,
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
use mentat_query_sql::{
|
||||||
|
ColumnOrExpression,
|
||||||
|
/*
|
||||||
|
Constraint,
|
||||||
|
FromClause,
|
||||||
|
*/
|
||||||
|
Name,
|
||||||
|
Projection,
|
||||||
|
ProjectedColumn,
|
||||||
|
/*
|
||||||
|
SelectQuery,
|
||||||
|
TableList,
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
error_chain! {
|
||||||
|
types {
|
||||||
|
Error, ErrorKind, ResultExt, Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign_links {
|
||||||
|
Rusqlite(rusqlite::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
links {
|
||||||
|
DbError(mentat_db::Error, mentat_db::ErrorKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum QueryResults {
|
||||||
|
Scalar(Option<TypedValue>),
|
||||||
|
Tuple(Option<Vec<TypedValue>>),
|
||||||
|
Coll(Vec<TypedValue>),
|
||||||
|
Rel(Vec<Vec<TypedValue>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryResults {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
use QueryResults::*;
|
||||||
|
match self {
|
||||||
|
&Scalar(ref o) => if o.is_some() { 1 } else { 0 },
|
||||||
|
&Tuple(ref o) => if o.is_some() { 1 } else { 0 },
|
||||||
|
&Coll(ref v) => v.len(),
|
||||||
|
&Rel(ref v) => v.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Index = i32; // See rusqlite::RowIndex.
|
||||||
|
type ValueTypeTag = i32;
|
||||||
|
enum TypedIndex {
|
||||||
|
Known(Index, ValueTypeTag),
|
||||||
|
Unknown(Index, Index),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypedIndex {
|
||||||
|
/// Look up this index and type(index) pair in the provided row.
|
||||||
|
/// This function will panic if:
|
||||||
|
///
|
||||||
|
/// - This is an `Unknown` and the retrieved type code isn't an i32.
|
||||||
|
/// - If the retrieved value can't be coerced to a rusqlite `Value`.
|
||||||
|
/// - Either index is out of bounds.
|
||||||
|
///
|
||||||
|
/// Because we construct our SQL projection list, the code that stored the data, and this
|
||||||
|
/// consumer, a panic here implies that we have a bad bug — we put data of a very wrong type in
|
||||||
|
/// a row, and thus can't coerce to Value, we're retrieving from the wrong place, or our
|
||||||
|
/// generated SQL is junk.
|
||||||
|
///
|
||||||
|
/// This function will return a runtime error if the type code is unknown, or the value is
|
||||||
|
/// otherwise not convertible by the DB layer.
|
||||||
|
fn lookup<'a, 'stmt>(&self, row: &Row<'a, 'stmt>) -> Result<TypedValue> {
|
||||||
|
use TypedIndex::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
&Known(value_index, value_type) => {
|
||||||
|
let v: rusqlite::types::Value = row.get(value_index);
|
||||||
|
TypedValue::from_sql_value_pair(v, value_type).map_err(|e| e.into())
|
||||||
|
},
|
||||||
|
&Unknown(value_index, type_index) => {
|
||||||
|
let v: rusqlite::types::Value = row.get(value_index);
|
||||||
|
let value_type_tag: i32 = row.get(type_index);
|
||||||
|
TypedValue::from_sql_value_pair(v, value_type_tag).map_err(|e| e.into())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_name(var: &Variable) -> Name {
|
||||||
|
let &Variable(PlainSymbol(ref s)) = var;
|
||||||
|
s.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_type_tag_name(var: &Variable) -> Name {
|
||||||
|
let &Variable(PlainSymbol(ref s)) = var;
|
||||||
|
format!("{}_value_type_tag", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walk an iterator of `Element`s, collecting projector templates and columns.
|
||||||
|
///
|
||||||
|
/// Returns a pair: the SQL projection (which should always be a `Projection::Columns`)
|
||||||
|
/// and a `Vec` of `TypedIndex` 'keys' to use when looking up values.
|
||||||
|
///
|
||||||
|
/// Callers must ensure that every `Element` is distinct -- a query like
|
||||||
|
///
|
||||||
|
/// ```edn
|
||||||
|
/// [:find ?x ?x :where [?x _ _]]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// should fail to parse. See #358.
|
||||||
|
fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
|
||||||
|
count: usize,
|
||||||
|
elements: I,
|
||||||
|
query: &AlgebraicQuery) -> (Projection, Vec<TypedIndex>) {
|
||||||
|
|
||||||
|
let mut cols = Vec::with_capacity(count);
|
||||||
|
let mut i: i32 = 0;
|
||||||
|
let mut templates = vec![];
|
||||||
|
|
||||||
|
for e in elements {
|
||||||
|
match e {
|
||||||
|
// Each time we come across a variable, we push a SQL column
|
||||||
|
// into the SQL projection, aliased to the name of the variable,
|
||||||
|
// and we push an annotated index into the projector.
|
||||||
|
&Element::Variable(ref var) => {
|
||||||
|
// Every variable should be bound by the top-level CC to at least
|
||||||
|
// one column in the query. If that constraint is violated it's a
|
||||||
|
// bug in our code, so it's appropriate to panic here.
|
||||||
|
let columns = query.cc
|
||||||
|
.bindings
|
||||||
|
.get(var)
|
||||||
|
.expect("Every variable has a binding");
|
||||||
|
|
||||||
|
let qa = columns[0].clone();
|
||||||
|
let name = column_name(var);
|
||||||
|
|
||||||
|
if let Some(t) = query.cc.known_types.get(var) {
|
||||||
|
cols.push(ProjectedColumn(ColumnOrExpression::Column(qa), name));
|
||||||
|
let tag = t.value_type_tag();
|
||||||
|
templates.push(TypedIndex::Known(i, tag));
|
||||||
|
i += 1; // We used one SQL column.
|
||||||
|
} else {
|
||||||
|
let table = qa.0.clone();
|
||||||
|
cols.push(ProjectedColumn(ColumnOrExpression::Column(qa), name));
|
||||||
|
templates.push(TypedIndex::Unknown(i, i + 1));
|
||||||
|
i += 2; // We used two SQL columns.
|
||||||
|
|
||||||
|
// Also project the type from the SQL query.
|
||||||
|
let type_name = value_type_tag_name(var);
|
||||||
|
let type_qa = QualifiedAlias(table, DatomsColumn::ValueTypeTag);
|
||||||
|
cols.push(ProjectedColumn(ColumnOrExpression::Column(type_qa), type_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(Projection::Columns(cols), templates)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Projector {
|
||||||
|
fn project<'stmt>(&self, rows: Rows<'stmt>) -> Result<QueryResults>;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ScalarProjector {
|
||||||
|
template: TypedIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScalarProjector {
|
||||||
|
fn with_template(template: TypedIndex) -> ScalarProjector {
|
||||||
|
ScalarProjector {
|
||||||
|
template: template,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine(sql: Projection, mut templates: Vec<TypedIndex>) -> CombinedProjection {
|
||||||
|
let template = templates.pop().expect("Expected a single template");
|
||||||
|
CombinedProjection {
|
||||||
|
sql_projection: sql,
|
||||||
|
datalog_projector: Box::new(ScalarProjector::with_template(template)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Projector for ScalarProjector {
|
||||||
|
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryResults> {
|
||||||
|
if let Some(r) = rows.next() {
|
||||||
|
let row = r?;
|
||||||
|
let binding = self.template.lookup(&row)?;
|
||||||
|
Ok(QueryResults::Scalar(Some(binding)))
|
||||||
|
} else {
|
||||||
|
Ok(QueryResults::Scalar(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A tuple projector produces a single vector. It's the single-result version of rel.
|
||||||
|
struct TupleProjector {
|
||||||
|
len: usize,
|
||||||
|
templates: Vec<TypedIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TupleProjector {
|
||||||
|
fn with_templates(len: usize, templates: Vec<TypedIndex>) -> TupleProjector {
|
||||||
|
TupleProjector {
|
||||||
|
len: len,
|
||||||
|
templates: templates,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is exactly the same as for rel.
|
||||||
|
fn collect_bindings<'a, 'stmt>(&self, row: Row<'a, 'stmt>) -> Result<Vec<TypedValue>> {
|
||||||
|
assert_eq!(row.column_count(), self.len as i32);
|
||||||
|
self.templates
|
||||||
|
.iter()
|
||||||
|
.map(|ti| ti.lookup(&row))
|
||||||
|
.collect::<Result<Vec<TypedValue>>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine(column_count: usize, sql: Projection, templates: Vec<TypedIndex>) -> CombinedProjection {
|
||||||
|
let p = TupleProjector::with_templates(column_count, templates);
|
||||||
|
CombinedProjection {
|
||||||
|
sql_projection: sql,
|
||||||
|
datalog_projector: Box::new(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Projector for TupleProjector {
|
||||||
|
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryResults> {
|
||||||
|
if let Some(r) = rows.next() {
|
||||||
|
let row = r?;
|
||||||
|
let bindings = self.collect_bindings(row)?;
|
||||||
|
Ok(QueryResults::Tuple(Some(bindings)))
|
||||||
|
} else {
|
||||||
|
Ok(QueryResults::Tuple(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A rel projector produces a vector of vectors.
|
||||||
|
/// Each inner vector is the same size, and sourced from the same columns.
|
||||||
|
/// One inner vector is produced per `Row`.
|
||||||
|
/// Each column in the inner vector is the result of taking one or two columns from
|
||||||
|
/// the `Row`: one for the value and optionally one for the type tag.
|
||||||
|
struct RelProjector {
|
||||||
|
len: usize,
|
||||||
|
templates: Vec<TypedIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RelProjector {
|
||||||
|
fn with_templates(len: usize, templates: Vec<TypedIndex>) -> RelProjector {
|
||||||
|
RelProjector {
|
||||||
|
len: len,
|
||||||
|
templates: templates,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_bindings<'a, 'stmt>(&self, row: Row<'a, 'stmt>) -> Result<Vec<TypedValue>> {
|
||||||
|
assert_eq!(row.column_count(), self.len as i32);
|
||||||
|
self.templates
|
||||||
|
.iter()
|
||||||
|
.map(|ti| ti.lookup(&row))
|
||||||
|
.collect::<Result<Vec<TypedValue>>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine(column_count: usize, sql: Projection, templates: Vec<TypedIndex>) -> CombinedProjection {
|
||||||
|
let p = RelProjector::with_templates(column_count, templates);
|
||||||
|
CombinedProjection {
|
||||||
|
sql_projection: sql,
|
||||||
|
datalog_projector: Box::new(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Projector for RelProjector {
|
||||||
|
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryResults> {
|
||||||
|
let mut out: Vec<Vec<TypedValue>> = vec![];
|
||||||
|
while let Some(r) = rows.next() {
|
||||||
|
let row = r?;
|
||||||
|
let bindings = self.collect_bindings(row)?;
|
||||||
|
out.push(bindings);
|
||||||
|
}
|
||||||
|
Ok(QueryResults::Rel(out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A coll projector produces a vector of values.
|
||||||
|
/// Each value is sourced from the same column.
|
||||||
|
struct CollProjector {
|
||||||
|
template: TypedIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CollProjector {
|
||||||
|
fn with_template(template: TypedIndex) -> CollProjector {
|
||||||
|
CollProjector {
|
||||||
|
template: template,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine(sql: Projection, mut templates: Vec<TypedIndex>) -> CombinedProjection {
|
||||||
|
let template = templates.pop().expect("Expected a single template");
|
||||||
|
CombinedProjection {
|
||||||
|
sql_projection: sql,
|
||||||
|
datalog_projector: Box::new(CollProjector::with_template(template)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Projector for CollProjector {
|
||||||
|
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryResults> {
|
||||||
|
let mut out: Vec<TypedValue> = vec![];
|
||||||
|
while let Some(r) = rows.next() {
|
||||||
|
let row = r?;
|
||||||
|
let binding = self.template.lookup(&row)?;
|
||||||
|
out.push(binding);
|
||||||
|
}
|
||||||
|
Ok(QueryResults::Coll(out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Combines the two things you need to turn a query into SQL and turn its results into
|
||||||
|
/// `QueryResults`.
|
||||||
|
pub struct CombinedProjection {
|
||||||
|
/// A SQL projection, mapping columns mentioned in the body of the query to columns in the
|
||||||
|
/// output.
|
||||||
|
pub sql_projection: Projection,
|
||||||
|
|
||||||
|
/// A Datalog projection. This consumes rows of the appropriate shape (as defined by
|
||||||
|
/// the SQL projection) to yield one of the four kinds of Datalog query result.
|
||||||
|
pub datalog_projector: Box<Projector>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute a suitable SQL projection for an algebrized query.
|
||||||
|
/// This takes into account a number of things:
|
||||||
|
/// - The variable list in the find spec.
|
||||||
|
/// - The presence of any aggregate operations in the find spec. TODO: for now we only handle
|
||||||
|
/// simple variables
|
||||||
|
/// - The bindings established by the topmost CC.
|
||||||
|
/// - The types known at algebrizing time.
|
||||||
|
/// - The types extracted from the store for unknown attributes.
|
||||||
|
pub fn query_projection(query: &AlgebraicQuery) -> CombinedProjection {
|
||||||
|
use self::FindSpec::*;
|
||||||
|
|
||||||
|
match query.find_spec {
|
||||||
|
FindColl(ref element) => {
|
||||||
|
let (cols, templates) = project_elements(1, iter::once(element), query);
|
||||||
|
CollProjector::combine(cols, templates)
|
||||||
|
},
|
||||||
|
|
||||||
|
FindScalar(ref element) => {
|
||||||
|
let (cols, templates) = project_elements(1, iter::once(element), query);
|
||||||
|
ScalarProjector::combine(cols, templates)
|
||||||
|
},
|
||||||
|
|
||||||
|
FindRel(ref elements) => {
|
||||||
|
let column_count = query.find_spec.expected_column_count();
|
||||||
|
let (cols, templates) = project_elements(column_count, elements, query);
|
||||||
|
RelProjector::combine(column_count, cols, templates)
|
||||||
|
},
|
||||||
|
|
||||||
|
FindTuple(ref elements) => {
|
||||||
|
let column_count = query.find_spec.expected_column_count();
|
||||||
|
let (cols, templates) = project_elements(column_count, elements, query);
|
||||||
|
TupleProjector::combine(column_count, cols, templates)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
21
query-sql/Cargo.toml
Normal file
21
query-sql/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "mentat_query_sql"
|
||||||
|
version = "0.0.1"
|
||||||
|
workspace = ".."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
[dependencies.mentat_core]
|
||||||
|
path = "../core"
|
||||||
|
|
||||||
|
[dependencies.mentat_sql]
|
||||||
|
path = "../sql"
|
||||||
|
|
||||||
|
[dependencies.mentat_query]
|
||||||
|
path = "../query"
|
||||||
|
|
||||||
|
[dependencies.mentat_query_algebrizer]
|
||||||
|
path = "../query-algebrizer"
|
||||||
|
|
||||||
|
# Only for tests.
|
||||||
|
[dev-dependencies.mentat_query_parser]
|
||||||
|
path = "../query-parser"
|
|
@ -8,7 +8,10 @@
|
||||||
// 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.
|
||||||
|
|
||||||
#![allow(dead_code, unused_imports)]
|
extern crate mentat_core;
|
||||||
|
extern crate mentat_query;
|
||||||
|
extern crate mentat_query_algebrizer;
|
||||||
|
extern crate mentat_sql;
|
||||||
|
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
Entid,
|
Entid,
|
||||||
|
@ -16,16 +19,11 @@ use mentat_core::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_query_algebrizer::{
|
use mentat_query_algebrizer::{
|
||||||
AlgebraicQuery,
|
|
||||||
ConjoiningClauses,
|
|
||||||
DatomsColumn,
|
DatomsColumn,
|
||||||
DatomsTable,
|
|
||||||
QualifiedAlias,
|
QualifiedAlias,
|
||||||
SourceAlias,
|
SourceAlias,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_sql;
|
|
||||||
|
|
||||||
use mentat_sql::{
|
use mentat_sql::{
|
||||||
BuildQueryResult,
|
BuildQueryResult,
|
||||||
QueryBuilder,
|
QueryBuilder,
|
||||||
|
@ -80,6 +78,7 @@ impl Constraint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
enum JoinOp {
|
enum JoinOp {
|
||||||
Inner,
|
Inner,
|
||||||
}
|
}
|
||||||
|
@ -94,6 +93,7 @@ pub struct Join {
|
||||||
// TODO: constraints (ON, USING).
|
// TODO: constraints (ON, USING).
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
enum TableOrSubquery {
|
enum TableOrSubquery {
|
||||||
Table(SourceAlias),
|
Table(SourceAlias),
|
||||||
// TODO: Subquery.
|
// TODO: Subquery.
|
||||||
|
@ -278,6 +278,7 @@ impl SelectQuery {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use mentat_query_algebrizer::DatomsTable;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_end_to_end() {
|
fn test_end_to_end() {
|
|
@ -19,3 +19,9 @@ path = "../query-algebrizer"
|
||||||
# Only for tests.
|
# Only for tests.
|
||||||
[dev-dependencies.mentat_query_parser]
|
[dev-dependencies.mentat_query_parser]
|
||||||
path = "../query-parser"
|
path = "../query-parser"
|
||||||
|
|
||||||
|
[dependencies.mentat_query_projector]
|
||||||
|
path = "../query-projector"
|
||||||
|
|
||||||
|
[dependencies.mentat_query_sql]
|
||||||
|
path = "../query-sql"
|
||||||
|
|
|
@ -11,16 +11,18 @@
|
||||||
extern crate mentat_core;
|
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_query_projector;
|
||||||
|
extern crate mentat_query_sql;
|
||||||
extern crate mentat_sql;
|
extern crate mentat_sql;
|
||||||
|
|
||||||
mod translate;
|
mod translate;
|
||||||
mod types;
|
|
||||||
|
|
||||||
pub use types::{
|
pub use mentat_query_sql::{
|
||||||
Projection,
|
Projection,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use translate::{
|
pub use translate::{
|
||||||
cc_to_exists,
|
cc_to_exists,
|
||||||
cc_to_select,
|
cc_to_select,
|
||||||
|
query_to_select,
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,13 @@
|
||||||
|
|
||||||
#![allow(dead_code, unused_imports)]
|
#![allow(dead_code, unused_imports)]
|
||||||
|
|
||||||
|
use mentat_query::{
|
||||||
|
Element,
|
||||||
|
FindSpec,
|
||||||
|
PlainSymbol,
|
||||||
|
Variable,
|
||||||
|
};
|
||||||
|
|
||||||
use mentat_query_algebrizer::{
|
use mentat_query_algebrizer::{
|
||||||
AlgebraicQuery,
|
AlgebraicQuery,
|
||||||
ColumnConstraint,
|
ColumnConstraint,
|
||||||
|
@ -20,11 +27,19 @@ use mentat_query_algebrizer::{
|
||||||
SourceAlias,
|
SourceAlias,
|
||||||
};
|
};
|
||||||
|
|
||||||
use types::{
|
use mentat_query_projector::{
|
||||||
|
CombinedProjection,
|
||||||
|
Projector,
|
||||||
|
query_projection,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mentat_query_sql::{
|
||||||
ColumnOrExpression,
|
ColumnOrExpression,
|
||||||
Constraint,
|
Constraint,
|
||||||
FromClause,
|
FromClause,
|
||||||
|
Name,
|
||||||
Projection,
|
Projection,
|
||||||
|
ProjectedColumn,
|
||||||
SelectQuery,
|
SelectQuery,
|
||||||
TableList,
|
TableList,
|
||||||
};
|
};
|
||||||
|
@ -57,10 +72,12 @@ impl ToConstraint for ColumnConstraint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct CombinedSelectQuery {
|
||||||
|
pub query: SelectQuery,
|
||||||
|
pub projector: Box<Projector>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Consume a provided `ConjoiningClauses` to yield a new
|
fn cc_to_select_query(projection: Projection, cc: ConjoiningClauses) -> SelectQuery {
|
||||||
/// `SelectQuery`. A projection list must also be provided.
|
|
||||||
pub fn cc_to_select(projection: Projection, cc: ConjoiningClauses) -> SelectQuery {
|
|
||||||
SelectQuery {
|
SelectQuery {
|
||||||
projection: projection,
|
projection: projection,
|
||||||
from: FromClause::TableList(TableList(cc.from)),
|
from: FromClause::TableList(TableList(cc.from)),
|
||||||
|
@ -71,6 +88,20 @@ pub fn cc_to_select(projection: Projection, cc: ConjoiningClauses) -> SelectQuer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cc_to_exists(cc: ConjoiningClauses) -> SelectQuery {
|
/// Consume a provided `ConjoiningClauses` to yield a new
|
||||||
cc_to_select(Projection::One, cc)
|
/// `SelectQuery`. A projection list must also be provided.
|
||||||
|
pub fn cc_to_select(projection: CombinedProjection, cc: ConjoiningClauses) -> CombinedSelectQuery {
|
||||||
|
let CombinedProjection { sql_projection, datalog_projector } = projection;
|
||||||
|
CombinedSelectQuery {
|
||||||
|
query: cc_to_select_query(sql_projection, cc),
|
||||||
|
projector: datalog_projector,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query_to_select(query: AlgebraicQuery) -> CombinedSelectQuery {
|
||||||
|
cc_to_select(query_projection(&query), query.cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cc_to_exists(cc: ConjoiningClauses) -> SelectQuery {
|
||||||
|
cc_to_select_query(Projection::One, cc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ use mentat_core::{
|
||||||
use mentat_query_parser::parse_find_string;
|
use mentat_query_parser::parse_find_string;
|
||||||
use mentat_query_algebrizer::algebrize;
|
use mentat_query_algebrizer::algebrize;
|
||||||
use mentat_query_translator::{
|
use mentat_query_translator::{
|
||||||
cc_to_exists,
|
query_to_select,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_sql::SQLQuery;
|
use mentat_sql::SQLQuery;
|
||||||
|
@ -42,7 +42,26 @@ fn add_attribute(schema: &mut Schema, e: Entid, a: Attribute) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_exists() {
|
#[should_panic(expected = "parse failed")]
|
||||||
|
fn test_coll() {
|
||||||
|
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).expect("parse failed");
|
||||||
|
let algebrized = algebrize(&schema, parsed);
|
||||||
|
let select = query_to_select(algebrized);
|
||||||
|
let SQLQuery { sql, args } = select.query.to_sql_query().unwrap();
|
||||||
|
assert_eq!(sql, "SELECT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0");
|
||||||
|
assert_eq!(args, vec![("$v0".to_string(), "yyy".to_string())]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rel() {
|
||||||
let mut schema = Schema::default();
|
let mut schema = Schema::default();
|
||||||
associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99);
|
associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 99);
|
||||||
add_attribute(&mut schema, 99, Attribute {
|
add_attribute(&mut schema, 99, Attribute {
|
||||||
|
@ -51,10 +70,10 @@ fn test_exists() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let input = r#"[:find ?x :where [?x :foo/bar "yyy"]]"#;
|
let input = r#"[:find ?x :where [?x :foo/bar "yyy"]]"#;
|
||||||
let parsed = parse_find_string(input).unwrap();
|
let parsed = parse_find_string(input).expect("parse failed");
|
||||||
let algebrized = algebrize(&schema, parsed);
|
let algebrized = algebrize(&schema, parsed);
|
||||||
let select = cc_to_exists(algebrized.cc);
|
let select = query_to_select(algebrized);
|
||||||
let SQLQuery { sql, args } = select.to_sql_query().unwrap();
|
let SQLQuery { sql, args } = select.query.to_sql_query().unwrap();
|
||||||
assert_eq!(sql, "SELECT 1 FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0");
|
assert_eq!(sql, "SELECT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0");
|
||||||
assert_eq!(args, vec![("$v0".to_string(), "yyy".to_string())]);
|
assert_eq!(args, vec![("$v0".to_string(), "yyy".to_string())]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -340,15 +340,27 @@ pub enum FindSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the provided `FindSpec` returns at most one result.
|
/// Returns true if the provided `FindSpec` returns at most one result.
|
||||||
pub fn is_unit_limited(spec: &FindSpec) -> bool {
|
impl FindSpec {
|
||||||
match spec {
|
pub fn is_unit_limited(&self) -> bool {
|
||||||
&FindSpec::FindScalar(..) => true,
|
use FindSpec::*;
|
||||||
&FindSpec::FindTuple(..) => true,
|
match self {
|
||||||
&FindSpec::FindRel(..) => false,
|
&FindScalar(..) => true,
|
||||||
&FindSpec::FindColl(..) => false,
|
&FindTuple(..) => true,
|
||||||
|
&FindRel(..) => false,
|
||||||
|
&FindColl(..) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expected_column_count(&self) -> usize {
|
||||||
|
use FindSpec::*;
|
||||||
|
match self {
|
||||||
|
&FindScalar(..) => 1,
|
||||||
|
&FindColl(..) => 1,
|
||||||
|
&FindTuple(ref elems) | &FindRel(ref elems) => elems.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Returns true if the provided `FindSpec` cares about distinct results.
|
/// Returns true if the provided `FindSpec` cares about distinct results.
|
||||||
///
|
///
|
||||||
/// I use the words "cares about" because find is generally defined in terms of producing distinct
|
/// I use the words "cares about" because find is generally defined in terms of producing distinct
|
||||||
|
@ -367,8 +379,9 @@ pub fn is_unit_limited(spec: &FindSpec) -> bool {
|
||||||
/// sampling projections we might not need to do it at the SQL level because we're consuming into
|
/// sampling projections we might not need to do it at the SQL level because we're consuming into
|
||||||
/// a dupe-eliminating data structure like a Set, or we know that a particular query cannot produce
|
/// a dupe-eliminating data structure like a Set, or we know that a particular query cannot produce
|
||||||
/// duplicate results.
|
/// duplicate results.
|
||||||
pub fn requires_distinct(spec: &FindSpec) -> bool {
|
pub fn requires_distinct(&self) -> bool {
|
||||||
return !is_unit_limited(spec);
|
!self.is_unit_limited()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that the "implicit blank" rule applies.
|
// Note that the "implicit blank" rule applies.
|
||||||
|
|
|
@ -15,6 +15,7 @@ use rusqlite;
|
||||||
use edn;
|
use edn;
|
||||||
use mentat_db;
|
use mentat_db;
|
||||||
use mentat_query_parser;
|
use mentat_query_parser;
|
||||||
|
use mentat_query_projector;
|
||||||
use mentat_sql;
|
use mentat_sql;
|
||||||
use mentat_tx_parser;
|
use mentat_tx_parser;
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ error_chain! {
|
||||||
links {
|
links {
|
||||||
DbError(mentat_db::Error, mentat_db::ErrorKind);
|
DbError(mentat_db::Error, mentat_db::ErrorKind);
|
||||||
QueryParseError(mentat_query_parser::Error, mentat_query_parser::ErrorKind);
|
QueryParseError(mentat_query_parser::Error, mentat_query_parser::ErrorKind);
|
||||||
|
ProjectorError(mentat_query_projector::Error, mentat_query_projector::ErrorKind);
|
||||||
SqlError(mentat_sql::Error, mentat_sql::ErrorKind);
|
SqlError(mentat_sql::Error, mentat_sql::ErrorKind);
|
||||||
TxParseError(mentat_tx_parser::Error, mentat_tx_parser::ErrorKind);
|
TxParseError(mentat_tx_parser::Error, mentat_tx_parser::ErrorKind);
|
||||||
}
|
}
|
||||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -25,6 +25,7 @@ extern crate mentat_db;
|
||||||
extern crate mentat_query;
|
extern crate mentat_query;
|
||||||
extern crate mentat_query_algebrizer;
|
extern crate mentat_query_algebrizer;
|
||||||
extern crate mentat_query_parser;
|
extern crate mentat_query_parser;
|
||||||
|
extern crate mentat_query_projector;
|
||||||
extern crate mentat_query_translator;
|
extern crate mentat_query_translator;
|
||||||
extern crate mentat_sql;
|
extern crate mentat_sql;
|
||||||
extern crate mentat_tx_parser;
|
extern crate mentat_tx_parser;
|
||||||
|
@ -46,6 +47,17 @@ pub fn get_connection() -> Connection {
|
||||||
return Connection::open_in_memory().unwrap();
|
return Connection::open_in_memory().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use mentat_db::{
|
||||||
|
new_connection,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use query::{
|
||||||
|
NamespacedKeyword,
|
||||||
|
PlainSymbol,
|
||||||
|
QueryResults,
|
||||||
|
q_once,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use edn::symbols::Keyword;
|
use edn::symbols::Keyword;
|
||||||
|
|
45
src/query.rs
45
src/query.rs
|
@ -10,6 +10,9 @@
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use rusqlite;
|
||||||
|
use rusqlite::types::ToSql;
|
||||||
|
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
Schema,
|
Schema,
|
||||||
TypedValue,
|
TypedValue,
|
||||||
|
@ -17,6 +20,11 @@ use mentat_core::{
|
||||||
|
|
||||||
use mentat_query_algebrizer::algebrize;
|
use mentat_query_algebrizer::algebrize;
|
||||||
|
|
||||||
|
pub use mentat_query::{
|
||||||
|
NamespacedKeyword,
|
||||||
|
PlainSymbol,
|
||||||
|
};
|
||||||
|
|
||||||
use mentat_query_parser::{
|
use mentat_query_parser::{
|
||||||
parse_find_string,
|
parse_find_string,
|
||||||
};
|
};
|
||||||
|
@ -26,21 +34,15 @@ use mentat_sql::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_query_translator::{
|
use mentat_query_translator::{
|
||||||
cc_to_select,
|
query_to_select,
|
||||||
Projection,
|
};
|
||||||
|
|
||||||
|
pub use mentat_query_projector::{
|
||||||
|
QueryResults,
|
||||||
};
|
};
|
||||||
|
|
||||||
use errors::Result;
|
use errors::Result;
|
||||||
|
|
||||||
use rusqlite;
|
|
||||||
|
|
||||||
pub enum QueryResults {
|
|
||||||
Scalar(Option<TypedValue>),
|
|
||||||
Tuple(Vec<TypedValue>),
|
|
||||||
Coll(Vec<TypedValue>),
|
|
||||||
Rel(Vec<Vec<TypedValue>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type QueryExecutionResult = Result<QueryResults>;
|
pub type QueryExecutionResult = Result<QueryResults>;
|
||||||
|
|
||||||
/// Take an EDN query string, a reference to a open SQLite connection, a Mentat DB, and an optional
|
/// Take an EDN query string, a reference to a open SQLite connection, a Mentat DB, and an optional
|
||||||
|
@ -59,21 +61,22 @@ pub fn q_once<'sqlite, 'schema, 'query>
|
||||||
// TODO: validate inputs.
|
// TODO: validate inputs.
|
||||||
let parsed = parse_find_string(query)?;
|
let parsed = parse_find_string(query)?;
|
||||||
let algebrized = algebrize(schema, parsed);
|
let algebrized = algebrize(schema, parsed);
|
||||||
let projection = Projection::Star;
|
let select = query_to_select(algebrized);
|
||||||
let select = cc_to_select(projection, algebrized.cc);
|
let SQLQuery { sql, args } = select.query.to_sql_query()?;
|
||||||
let SQLQuery { sql, args } = select.to_sql_query()?;
|
|
||||||
|
|
||||||
/*
|
|
||||||
let mut statement = sqlite.prepare(sql.as_str())?;
|
let mut statement = sqlite.prepare(sql.as_str())?;
|
||||||
|
|
||||||
let mut rows = if args.is_empty() {
|
let rows = if args.is_empty() {
|
||||||
statement.query(&[])?
|
statement.query(&[])?
|
||||||
} else {
|
} else {
|
||||||
statement.query_named(args.map(|(k, v)| (k.as_str(), &v)))?
|
let refs: Vec<(&str, &ToSql)> =
|
||||||
|
args.iter()
|
||||||
|
.map(|&(ref k, ref v)| (k.as_str(), v as &ToSql))
|
||||||
|
.collect();
|
||||||
|
statement.query_named(refs.as_slice())?
|
||||||
};
|
};
|
||||||
*/
|
|
||||||
|
|
||||||
|
select.projector
|
||||||
|
.project(rows)
|
||||||
Ok(QueryResults::Scalar(Some(TypedValue::Boolean(true))))
|
.map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
142
tests/query.rs
Normal file
142
tests/query.rs
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
// 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 time;
|
||||||
|
|
||||||
|
extern crate mentat;
|
||||||
|
extern crate mentat_core;
|
||||||
|
extern crate mentat_db;
|
||||||
|
|
||||||
|
use mentat_core::{
|
||||||
|
TypedValue,
|
||||||
|
ValueType,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mentat::{
|
||||||
|
NamespacedKeyword,
|
||||||
|
QueryResults,
|
||||||
|
new_connection,
|
||||||
|
q_once,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rel() {
|
||||||
|
let mut c = new_connection("").expect("Couldn't open conn.");
|
||||||
|
let db = mentat_db::db::ensure_current_version(&mut c).expect("Couldn't open DB.");
|
||||||
|
|
||||||
|
// Rel.
|
||||||
|
let start = time::PreciseTime::now();
|
||||||
|
let results = q_once(&c, &db.schema,
|
||||||
|
"[:find ?x ?ident :where [?x :db/ident ?ident]]", None)
|
||||||
|
.expect("Query failed");
|
||||||
|
let end = time::PreciseTime::now();
|
||||||
|
|
||||||
|
// This will need to change each time we add a default ident.
|
||||||
|
assert_eq!(37, results.len());
|
||||||
|
|
||||||
|
// Every row is a pair of a Ref and a Keyword.
|
||||||
|
if let QueryResults::Rel(ref rel) = results {
|
||||||
|
for r in rel {
|
||||||
|
assert_eq!(r.len(), 2);
|
||||||
|
assert!(r[0].matches_type(ValueType::Ref));
|
||||||
|
assert!(r[1].matches_type(ValueType::Keyword));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Expected rel.");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{:?}", results);
|
||||||
|
println!("Rel took {}µs", start.to(end).num_microseconds().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_failing_scalar() {
|
||||||
|
let mut c = new_connection("").expect("Couldn't open conn.");
|
||||||
|
let db = mentat_db::db::ensure_current_version(&mut c).expect("Couldn't open DB.");
|
||||||
|
|
||||||
|
// Scalar that fails.
|
||||||
|
let start = time::PreciseTime::now();
|
||||||
|
let results = q_once(&c, &db.schema,
|
||||||
|
"[:find ?x . :where [?x :db/fulltext true]]", None)
|
||||||
|
.expect("Query failed");
|
||||||
|
let end = time::PreciseTime::now();
|
||||||
|
|
||||||
|
assert_eq!(0, results.len());
|
||||||
|
|
||||||
|
if let QueryResults::Scalar(None) = results {
|
||||||
|
} else {
|
||||||
|
panic!("Expected failed scalar.");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Failing scalar took {}µs", start.to(end).num_microseconds().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scalar() {
|
||||||
|
let mut c = new_connection("").expect("Couldn't open conn.");
|
||||||
|
let db = mentat_db::db::ensure_current_version(&mut c).expect("Couldn't open DB.");
|
||||||
|
|
||||||
|
// Scalar that succeeds.
|
||||||
|
let start = time::PreciseTime::now();
|
||||||
|
let results = q_once(&c, &db.schema,
|
||||||
|
"[:find ?ident . :where [24 :db/ident ?ident]]", None)
|
||||||
|
.expect("Query failed");
|
||||||
|
let end = time::PreciseTime::now();
|
||||||
|
|
||||||
|
assert_eq!(1, results.len());
|
||||||
|
|
||||||
|
if let QueryResults::Scalar(Some(TypedValue::Keyword(ref kw))) = results {
|
||||||
|
// Should be '24'.
|
||||||
|
assert_eq!(&NamespacedKeyword::new("db.type", "keyword"), kw);
|
||||||
|
assert_eq!(24,
|
||||||
|
db.schema.get_entid(kw).unwrap());
|
||||||
|
} else {
|
||||||
|
panic!("Expected scalar.");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{:?}", results);
|
||||||
|
println!("Scalar took {}µs", start.to(end).num_microseconds().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tuple() {
|
||||||
|
let mut c = new_connection("").expect("Couldn't open conn.");
|
||||||
|
let db = mentat_db::db::ensure_current_version(&mut c).expect("Couldn't open DB.");
|
||||||
|
|
||||||
|
// Tuple.
|
||||||
|
let start = time::PreciseTime::now();
|
||||||
|
let results = q_once(&c, &db.schema,
|
||||||
|
"[:find [?index ?cardinality]
|
||||||
|
:where [:db/txInstant :db/index ?index]
|
||||||
|
[:db/txInstant :db/cardinality ?cardinality]]",
|
||||||
|
None)
|
||||||
|
.expect("Query failed");
|
||||||
|
let end = time::PreciseTime::now();
|
||||||
|
|
||||||
|
assert_eq!(1, results.len());
|
||||||
|
|
||||||
|
if let QueryResults::Tuple(Some(ref tuple)) = results {
|
||||||
|
let cardinality_one = NamespacedKeyword::new("db.cardinality", "one");
|
||||||
|
assert_eq!(tuple.len(), 2);
|
||||||
|
assert_eq!(tuple[0], TypedValue::Boolean(true));
|
||||||
|
assert_eq!(tuple[1], TypedValue::Ref(db.schema.get_entid(&cardinality_one).unwrap()));
|
||||||
|
} else {
|
||||||
|
panic!("Expected tuple.");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{:?}", results);
|
||||||
|
println!("Tuple took {}µs", start.to(end).num_microseconds().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_coll() {
|
||||||
|
// We can't test Coll yet, because the EDN parser is incomplete.
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue