Parse :in, pass inputs through to querying. (#418) r=nalexander
This commit downgrades error_chain to 0.8.1 in order to fix trait bounds on errors.
This commit is contained in:
commit
aa14a71019
22 changed files with 361 additions and 78 deletions
|
@ -18,7 +18,7 @@ rustc_version = "0.1.7"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.19.3"
|
clap = "2.19.3"
|
||||||
error-chain = "0.9.0"
|
error-chain = "0.8.1"
|
||||||
nickel = "0.9.0"
|
nickel = "0.9.0"
|
||||||
slog = "1.4.0"
|
slog = "1.4.0"
|
||||||
slog-scope = "0.2.2"
|
slog-scope = "0.2.2"
|
||||||
|
|
|
@ -17,6 +17,7 @@ extern crate edn;
|
||||||
pub mod values;
|
pub mod values;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use self::ordered_float::OrderedFloat;
|
use self::ordered_float::OrderedFloat;
|
||||||
use self::edn::NamespacedKeyword;
|
use self::edn::NamespacedKeyword;
|
||||||
|
@ -44,19 +45,33 @@ pub enum ValueType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValueType {
|
impl ValueType {
|
||||||
pub fn to_edn_value(&self) -> edn::Value {
|
pub fn to_edn_value(self) -> edn::Value {
|
||||||
match self {
|
match self {
|
||||||
&ValueType::Ref => values::DB_TYPE_REF.clone(),
|
ValueType::Ref => values::DB_TYPE_REF.clone(),
|
||||||
&ValueType::Boolean => values::DB_TYPE_BOOLEAN.clone(),
|
ValueType::Boolean => values::DB_TYPE_BOOLEAN.clone(),
|
||||||
&ValueType::Instant => values::DB_TYPE_INSTANT.clone(),
|
ValueType::Instant => values::DB_TYPE_INSTANT.clone(),
|
||||||
&ValueType::Long => values::DB_TYPE_LONG.clone(),
|
ValueType::Long => values::DB_TYPE_LONG.clone(),
|
||||||
&ValueType::Double => values::DB_TYPE_DOUBLE.clone(),
|
ValueType::Double => values::DB_TYPE_DOUBLE.clone(),
|
||||||
&ValueType::String => values::DB_TYPE_STRING.clone(),
|
ValueType::String => values::DB_TYPE_STRING.clone(),
|
||||||
&ValueType::Keyword => values::DB_TYPE_KEYWORD.clone(),
|
ValueType::Keyword => values::DB_TYPE_KEYWORD.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ValueType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", match *self {
|
||||||
|
ValueType::Ref => "db.type/ref",
|
||||||
|
ValueType::Boolean => "db.type/boolean",
|
||||||
|
ValueType::Instant => "db.type/instant",
|
||||||
|
ValueType::Long => "db.type/long",
|
||||||
|
ValueType::Double => "db.type/double",
|
||||||
|
ValueType::String => "db.type/string",
|
||||||
|
ValueType::Keyword => "db.type/keyword",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a Mentat value in a particular value set.
|
/// Represents a Mentat value in a particular value set.
|
||||||
// TODO: expand to include :db.type/{instant,url,uuid}.
|
// TODO: expand to include :db.type/{instant,url,uuid}.
|
||||||
// TODO: BigInt?
|
// TODO: BigInt?
|
||||||
|
|
|
@ -4,7 +4,7 @@ version = "0.0.1"
|
||||||
workspace = ".."
|
workspace = ".."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
error-chain = "0.9.0"
|
error-chain = "0.8.1"
|
||||||
itertools = "0.5.9"
|
itertools = "0.5.9"
|
||||||
lazy_static = "0.2.2"
|
lazy_static = "0.2.2"
|
||||||
ordered-float = "0.4.0"
|
ordered-float = "0.4.0"
|
||||||
|
|
|
@ -4,7 +4,7 @@ version = "0.0.1"
|
||||||
workspace = ".."
|
workspace = ".."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
error-chain = "0.9.0"
|
error-chain = "0.8.1"
|
||||||
|
|
||||||
[dependencies.mentat_core]
|
[dependencies.mentat_core]
|
||||||
path = "../core"
|
path = "../core"
|
||||||
|
|
72
query-algebrizer/src/clauses/inputs.rs
Normal file
72
query-algebrizer/src/clauses/inputs.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use mentat_core::{
|
||||||
|
TypedValue,
|
||||||
|
ValueType,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mentat_query::{
|
||||||
|
Variable,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use errors::{
|
||||||
|
Error,
|
||||||
|
ErrorKind,
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Define the inputs to a query. This is in two parts: a set of values known now, and a set of
|
||||||
|
/// types known now.
|
||||||
|
/// The separate map of types is to allow queries to be algebrized without full knowledge of
|
||||||
|
/// the bindings that will be used at execution time.
|
||||||
|
/// When built correctly, `types` is guaranteed to contain the types of `values` -- use
|
||||||
|
/// `QueryInputs::new` or `QueryInputs::with_values` to construct an instance.
|
||||||
|
pub struct QueryInputs {
|
||||||
|
pub types: BTreeMap<Variable, ValueType>,
|
||||||
|
pub values: BTreeMap<Variable, TypedValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for QueryInputs {
|
||||||
|
fn default() -> Self {
|
||||||
|
QueryInputs { types: BTreeMap::default(), values: BTreeMap::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryInputs {
|
||||||
|
pub fn with_value_sequence(vals: Vec<(Variable, TypedValue)>) -> QueryInputs {
|
||||||
|
let values: BTreeMap<Variable, TypedValue> = vals.into_iter().collect();
|
||||||
|
QueryInputs::with_values(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_values(values: BTreeMap<Variable, TypedValue>) -> QueryInputs {
|
||||||
|
QueryInputs {
|
||||||
|
types: values.iter().map(|(var, val)| (var.clone(), val.value_type())).collect(),
|
||||||
|
values: values,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(mut types: BTreeMap<Variable, ValueType>,
|
||||||
|
values: BTreeMap<Variable, TypedValue>) -> Result<QueryInputs> {
|
||||||
|
// Make sure that the types of the values agree with those in types, and collect.
|
||||||
|
for (var, v) in values.iter() {
|
||||||
|
let t = v.value_type();
|
||||||
|
let old = types.insert(var.clone(), t);
|
||||||
|
if let Some(old) = old {
|
||||||
|
if old != t {
|
||||||
|
bail!(ErrorKind::InputTypeDisagreement(var.name(), old, t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(QueryInputs { types: types, values: values })
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,6 +59,7 @@ use types::{
|
||||||
TableAlias,
|
TableAlias,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod inputs;
|
||||||
mod or;
|
mod or;
|
||||||
mod pattern;
|
mod pattern;
|
||||||
mod predicate;
|
mod predicate;
|
||||||
|
@ -66,6 +67,8 @@ mod resolve;
|
||||||
|
|
||||||
use validate::validate_or_join;
|
use validate::validate_or_join;
|
||||||
|
|
||||||
|
pub use self::inputs::QueryInputs;
|
||||||
|
|
||||||
// We do this a lot for errors.
|
// We do this a lot for errors.
|
||||||
trait RcCloned<T> {
|
trait RcCloned<T> {
|
||||||
fn cloned(&self) -> T;
|
fn cloned(&self) -> T;
|
||||||
|
@ -89,6 +92,7 @@ trait Contains<K, T> {
|
||||||
|
|
||||||
trait Intersection<K> {
|
trait Intersection<K> {
|
||||||
fn with_intersected_keys(&self, ks: &BTreeSet<K>) -> Self;
|
fn with_intersected_keys(&self, ks: &BTreeSet<K>) -> Self;
|
||||||
|
fn keep_intersected_keys(&mut self, ks: &BTreeSet<K>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Ord, T> Contains<K, T> for BTreeSet<K> {
|
impl<K: Ord, T> Contains<K, T> for BTreeSet<K> {
|
||||||
|
@ -108,6 +112,22 @@ impl<K: Clone + Ord, V: Clone> Intersection<K> for BTreeMap<K, V> {
|
||||||
.filter_map(|(k, v)| ks.when_contains(k, || (k.clone(), v.clone())))
|
.filter_map(|(k, v)| ks.when_contains(k, || (k.clone(), v.clone())))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove all keys from the map that are not present in `ks`.
|
||||||
|
/// This implementation is terrible because there's no mutable iterator for BTreeMap.
|
||||||
|
fn keep_intersected_keys(&mut self, ks: &BTreeSet<K>) {
|
||||||
|
let mut to_remove = Vec::with_capacity(self.len() - ks.len());
|
||||||
|
{
|
||||||
|
for k in self.keys() {
|
||||||
|
if !ks.contains(k) {
|
||||||
|
to_remove.push(k.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k in to_remove.into_iter() {
|
||||||
|
self.remove(&k);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `ConjoiningClauses` (CC) is a collection of clauses that are combined with `JOIN`.
|
/// A `ConjoiningClauses` (CC) is a collection of clauses that are combined with `JOIN`.
|
||||||
|
@ -223,6 +243,38 @@ impl ConjoiningClauses {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_inputs<T>(in_variables: BTreeSet<Variable>, inputs: T) -> ConjoiningClauses
|
||||||
|
where T: Into<Option<QueryInputs>> {
|
||||||
|
ConjoiningClauses::with_inputs_and_alias_counter(in_variables, inputs, RcCounter::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_inputs_and_alias_counter<T>(in_variables: BTreeSet<Variable>,
|
||||||
|
inputs: T,
|
||||||
|
alias_counter: RcCounter) -> ConjoiningClauses
|
||||||
|
where T: Into<Option<QueryInputs>> {
|
||||||
|
match inputs.into() {
|
||||||
|
None => ConjoiningClauses::with_alias_counter(alias_counter),
|
||||||
|
Some(QueryInputs { mut types, mut values }) => {
|
||||||
|
// Discard any bindings not mentioned in our :in clause.
|
||||||
|
types.keep_intersected_keys(&in_variables);
|
||||||
|
values.keep_intersected_keys(&in_variables);
|
||||||
|
|
||||||
|
let mut cc = ConjoiningClauses {
|
||||||
|
alias_counter: alias_counter,
|
||||||
|
input_variables: in_variables,
|
||||||
|
value_bindings: values,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pre-fill our type mappings with the types of the input bindings.
|
||||||
|
cc.known_types
|
||||||
|
.extend(types.iter()
|
||||||
|
.map(|(k, v)| (k.clone(), unit_type_set(*v))));
|
||||||
|
cc
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cloning.
|
/// Cloning.
|
||||||
|
@ -254,28 +306,16 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConjoiningClauses {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn with_value_bindings(bindings: BTreeMap<Variable, TypedValue>) -> ConjoiningClauses {
|
|
||||||
let mut cc = ConjoiningClauses {
|
|
||||||
value_bindings: bindings,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Pre-fill our type mappings with the types of the input bindings.
|
|
||||||
cc.known_types
|
|
||||||
.extend(cc.value_bindings
|
|
||||||
.iter()
|
|
||||||
.map(|(k, v)| (k.clone(), unit_type_set(v.value_type()))));
|
|
||||||
cc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
pub fn bound_value(&self, var: &Variable) -> Option<TypedValue> {
|
pub fn bound_value(&self, var: &Variable) -> Option<TypedValue> {
|
||||||
self.value_bindings.get(var).cloned()
|
self.value_bindings.get(var).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a set of the variables externally bound to values.
|
||||||
|
pub fn value_bound_variables(&self) -> BTreeSet<Variable> {
|
||||||
|
self.value_bindings.keys().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a single `ValueType` if the given variable is known to have a precise type.
|
/// Return a single `ValueType` if the given variable is known to have a precise type.
|
||||||
/// Returns `None` if the type of the variable is unknown.
|
/// Returns `None` if the type of the variable is unknown.
|
||||||
/// Returns `None` if the type of the variable is known but not precise -- "double
|
/// Returns `None` if the type of the variable is known but not precise -- "double
|
||||||
|
|
|
@ -270,6 +270,7 @@ mod testing {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use mentat_core::attribute::Unique;
|
use mentat_core::attribute::Unique;
|
||||||
|
@ -288,6 +289,7 @@ mod testing {
|
||||||
};
|
};
|
||||||
|
|
||||||
use clauses::{
|
use clauses::{
|
||||||
|
QueryInputs,
|
||||||
add_attribute,
|
add_attribute,
|
||||||
associate_ident,
|
associate_ident,
|
||||||
ident,
|
ident,
|
||||||
|
@ -660,7 +662,9 @@ mod testing {
|
||||||
|
|
||||||
let b: BTreeMap<Variable, TypedValue> =
|
let b: BTreeMap<Variable, TypedValue> =
|
||||||
vec![(y.clone(), TypedValue::Boolean(true))].into_iter().collect();
|
vec![(y.clone(), TypedValue::Boolean(true))].into_iter().collect();
|
||||||
let mut cc = ConjoiningClauses::with_value_bindings(b);
|
let inputs = QueryInputs::with_values(b);
|
||||||
|
let variables: BTreeSet<Variable> = vec![Variable::from_valid_name("?y")].into_iter().collect();
|
||||||
|
let mut cc = ConjoiningClauses::with_inputs(variables, inputs);
|
||||||
|
|
||||||
cc.apply_pattern(&schema, Pattern {
|
cc.apply_pattern(&schema, Pattern {
|
||||||
source: None,
|
source: None,
|
||||||
|
@ -705,7 +709,9 @@ mod testing {
|
||||||
|
|
||||||
let b: BTreeMap<Variable, TypedValue> =
|
let b: BTreeMap<Variable, TypedValue> =
|
||||||
vec![(y.clone(), TypedValue::Long(42))].into_iter().collect();
|
vec![(y.clone(), TypedValue::Long(42))].into_iter().collect();
|
||||||
let mut cc = ConjoiningClauses::with_value_bindings(b);
|
let inputs = QueryInputs::with_values(b);
|
||||||
|
let variables: BTreeSet<Variable> = vec![Variable::from_valid_name("?y")].into_iter().collect();
|
||||||
|
let mut cc = ConjoiningClauses::with_inputs(variables, inputs);
|
||||||
|
|
||||||
cc.apply_pattern(&schema, Pattern {
|
cc.apply_pattern(&schema, Pattern {
|
||||||
source: None,
|
source: None,
|
||||||
|
@ -737,7 +743,9 @@ mod testing {
|
||||||
|
|
||||||
let b: BTreeMap<Variable, TypedValue> =
|
let b: BTreeMap<Variable, TypedValue> =
|
||||||
vec![(y.clone(), TypedValue::Long(42))].into_iter().collect();
|
vec![(y.clone(), TypedValue::Long(42))].into_iter().collect();
|
||||||
let mut cc = ConjoiningClauses::with_value_bindings(b);
|
let inputs = QueryInputs::with_values(b);
|
||||||
|
let variables: BTreeSet<Variable> = vec![Variable::from_valid_name("?y")].into_iter().collect();
|
||||||
|
let mut cc = ConjoiningClauses::with_inputs(variables, inputs);
|
||||||
|
|
||||||
cc.apply_pattern(&schema, Pattern {
|
cc.apply_pattern(&schema, Pattern {
|
||||||
source: None,
|
source: None,
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
extern crate mentat_query;
|
extern crate mentat_query;
|
||||||
|
|
||||||
|
use mentat_core::ValueType;
|
||||||
|
|
||||||
use self::mentat_query::{
|
use self::mentat_query::{
|
||||||
PlainSymbol,
|
PlainSymbol,
|
||||||
};
|
};
|
||||||
|
@ -20,6 +22,11 @@ error_chain! {
|
||||||
}
|
}
|
||||||
|
|
||||||
errors {
|
errors {
|
||||||
|
InputTypeDisagreement(var: PlainSymbol, declared: ValueType, provided: ValueType) {
|
||||||
|
description("input type disagreement")
|
||||||
|
display("value of type {} provided for var {}, expected {}", provided, var, declared)
|
||||||
|
}
|
||||||
|
|
||||||
UnknownFunction(name: PlainSymbol) {
|
UnknownFunction(name: PlainSymbol) {
|
||||||
description("no such function")
|
description("no such function")
|
||||||
display("no function named {}", name)
|
display("no function named {}", name)
|
||||||
|
|
|
@ -15,6 +15,7 @@ extern crate mentat_core;
|
||||||
extern crate mentat_query;
|
extern crate mentat_query;
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
use std::ops::Sub;
|
||||||
|
|
||||||
mod errors;
|
mod errors;
|
||||||
mod types;
|
mod types;
|
||||||
|
@ -41,6 +42,10 @@ pub use errors::{
|
||||||
Result,
|
Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use clauses::{
|
||||||
|
QueryInputs,
|
||||||
|
};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct AlgebraicQuery {
|
pub struct AlgebraicQuery {
|
||||||
default_source: SrcVar,
|
default_source: SrcVar,
|
||||||
|
@ -74,16 +79,20 @@ impl AlgebraicQuery {
|
||||||
pub fn is_known_empty(&self) -> bool {
|
pub fn is_known_empty(&self) -> bool {
|
||||||
self.cc.is_known_empty()
|
self.cc.is_known_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a set of the input variables mentioned in the `:in` clause that have not yet been
|
||||||
|
/// bound. We do this by looking at the CC.
|
||||||
|
pub fn unbound_variables(&self) -> BTreeSet<Variable> {
|
||||||
|
self.cc.input_variables.sub(&self.cc.value_bound_variables())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn algebrize_with_counter(schema: &Schema, parsed: FindQuery, counter: usize) -> Result<AlgebraicQuery> {
|
pub fn algebrize_with_counter(schema: &Schema, parsed: FindQuery, counter: usize) -> Result<AlgebraicQuery> {
|
||||||
let alias_counter = RcCounter::with_initial(counter);
|
algebrize_with_inputs(schema, parsed, counter, QueryInputs::default())
|
||||||
let cc = clauses::ConjoiningClauses::with_alias_counter(alias_counter);
|
|
||||||
algebrize_with_cc(schema, parsed, cc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn algebrize(schema: &Schema, parsed: FindQuery) -> Result<AlgebraicQuery> {
|
pub fn algebrize(schema: &Schema, parsed: FindQuery) -> Result<AlgebraicQuery> {
|
||||||
algebrize_with_cc(schema, parsed, clauses::ConjoiningClauses::default())
|
algebrize_with_inputs(schema, parsed, 0, QueryInputs::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Take an ordering list. Any variables that aren't fixed by the query are used to produce
|
/// Take an ordering list. Any variables that aren't fixed by the query are used to produce
|
||||||
|
@ -122,8 +131,13 @@ fn validate_and_simplify_order(cc: &ConjoiningClauses, order: Option<Vec<Order>>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
pub fn algebrize_with_inputs(schema: &Schema,
|
||||||
pub fn algebrize_with_cc(schema: &Schema, parsed: FindQuery, mut cc: ConjoiningClauses) -> Result<AlgebraicQuery> {
|
parsed: FindQuery,
|
||||||
|
counter: usize,
|
||||||
|
inputs: QueryInputs) -> Result<AlgebraicQuery> {
|
||||||
|
let alias_counter = RcCounter::with_initial(counter);
|
||||||
|
let mut cc = ConjoiningClauses::with_inputs_and_alias_counter(parsed.in_vars, inputs, alias_counter);
|
||||||
|
|
||||||
// TODO: integrate default source into pattern processing.
|
// TODO: integrate default source into pattern processing.
|
||||||
// TODO: flesh out the rest of find-into-context.
|
// TODO: flesh out the rest of find-into-context.
|
||||||
let where_clauses = parsed.where_clauses;
|
let where_clauses = parsed.where_clauses;
|
||||||
|
|
|
@ -5,7 +5,7 @@ workspace = ".."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
combine = "2.2.2"
|
combine = "2.2.2"
|
||||||
error-chain = "0.9.0"
|
error-chain = "0.8.1"
|
||||||
matches = "0.1"
|
matches = "0.1"
|
||||||
|
|
||||||
[dependencies.edn]
|
[dependencies.edn]
|
||||||
|
|
|
@ -15,6 +15,8 @@ extern crate mentat_query;
|
||||||
|
|
||||||
use std; // To refer to std::result::Result.
|
use std; // To refer to std::result::Result.
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use self::combine::{eof, many, many1, optional, parser, satisfy, satisfy_map, Parser, ParseResult, Stream};
|
use self::combine::{eof, many, many1, optional, parser, satisfy, satisfy_map, Parser, ParseResult, Stream};
|
||||||
use self::combine::combinator::{choice, or, try};
|
use self::combine::combinator::{choice, or, try};
|
||||||
|
|
||||||
|
@ -65,6 +67,11 @@ error_chain! {
|
||||||
}
|
}
|
||||||
|
|
||||||
errors {
|
errors {
|
||||||
|
DuplicateVariableError {
|
||||||
|
description("duplicate variable")
|
||||||
|
display("duplicates in variable list")
|
||||||
|
}
|
||||||
|
|
||||||
NotAVariableError(value: edn::ValueAndSpan) {
|
NotAVariableError(value: edn::ValueAndSpan) {
|
||||||
description("not a variable")
|
description("not a variable")
|
||||||
display("not a variable: '{}'", value)
|
display("not a variable: '{}'", value)
|
||||||
|
@ -328,6 +335,8 @@ def_parser!(Find, spec, FindSpec, {
|
||||||
|
|
||||||
def_matches_keyword!(Find, literal_find, "find");
|
def_matches_keyword!(Find, literal_find, "find");
|
||||||
|
|
||||||
|
def_matches_keyword!(Find, literal_in, "in");
|
||||||
|
|
||||||
def_matches_keyword!(Find, literal_with, "with");
|
def_matches_keyword!(Find, literal_with, "with");
|
||||||
|
|
||||||
def_matches_keyword!(Find, literal_where, "where");
|
def_matches_keyword!(Find, literal_where, "where");
|
||||||
|
@ -337,11 +346,26 @@ def_matches_keyword!(Find, literal_order, "order");
|
||||||
/// Express something close to a builder pattern for a `FindQuery`.
|
/// Express something close to a builder pattern for a `FindQuery`.
|
||||||
enum FindQueryPart {
|
enum FindQueryPart {
|
||||||
FindSpec(FindSpec),
|
FindSpec(FindSpec),
|
||||||
With(Vec<Variable>),
|
With(BTreeSet<Variable>),
|
||||||
|
In(BTreeSet<Variable>),
|
||||||
WhereClauses(Vec<WhereClause>),
|
WhereClauses(Vec<WhereClause>),
|
||||||
Order(Vec<Order>),
|
Order(Vec<Order>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def_parser!(Find, vars, BTreeSet<Variable>, {
|
||||||
|
vector().of_exactly(many(Query::variable()).and_then(|vars: Vec<Variable>| {
|
||||||
|
let given = vars.len();
|
||||||
|
let set: BTreeSet<Variable> = vars.into_iter().collect();
|
||||||
|
if given != set.len() {
|
||||||
|
// TODO: find out what the variable is!
|
||||||
|
let e = Box::new(Error::from_kind(ErrorKind::DuplicateVariableError));
|
||||||
|
Err(combine::primitives::Error::Other(e))
|
||||||
|
} else {
|
||||||
|
Ok(set)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
/// This is awkward, but will do for now. We use `keyword_map()` to optionally accept vector find
|
/// This is awkward, but will do for now. We use `keyword_map()` to optionally accept vector find
|
||||||
/// queries, then we use `FindQueryPart` to collect parts that have heterogeneous types; and then we
|
/// queries, then we use `FindQueryPart` to collect parts that have heterogeneous types; and then we
|
||||||
/// construct a `FindQuery` from them.
|
/// construct a `FindQuery` from them.
|
||||||
|
@ -349,8 +373,9 @@ def_parser!(Find, query, FindQuery, {
|
||||||
let p_find_spec = Find::literal_find()
|
let p_find_spec = Find::literal_find()
|
||||||
.with(vector().of_exactly(Find::spec().map(FindQueryPart::FindSpec)));
|
.with(vector().of_exactly(Find::spec().map(FindQueryPart::FindSpec)));
|
||||||
|
|
||||||
let p_with_vars = Find::literal_with()
|
let p_in_vars = Find::literal_in().with(Find::vars().map(FindQueryPart::In));
|
||||||
.with(vector().of_exactly(many(Query::variable()).map(FindQueryPart::With)));
|
|
||||||
|
let p_with_vars = Find::literal_with().with(Find::vars().map(FindQueryPart::With));
|
||||||
|
|
||||||
let p_where_clauses = Find::literal_where()
|
let p_where_clauses = Find::literal_where()
|
||||||
.with(vector().of_exactly(Where::clauses().map(FindQueryPart::WhereClauses))).expected(":where clauses");
|
.with(vector().of_exactly(Where::clauses().map(FindQueryPart::WhereClauses))).expected(":where clauses");
|
||||||
|
@ -359,14 +384,16 @@ def_parser!(Find, query, FindQuery, {
|
||||||
.with(vector().of_exactly(many1(Query::order()).map(FindQueryPart::Order)));
|
.with(vector().of_exactly(many1(Query::order()).map(FindQueryPart::Order)));
|
||||||
|
|
||||||
(or(map(), keyword_map()))
|
(or(map(), keyword_map()))
|
||||||
.of_exactly(many(choice::<[&mut Parser<Input = ValueStream, Output = FindQueryPart>; 4], _>([
|
.of_exactly(many(choice::<[&mut Parser<Input = ValueStream, Output = FindQueryPart>; 5], _>([
|
||||||
&mut try(p_find_spec),
|
&mut try(p_find_spec),
|
||||||
|
&mut try(p_in_vars),
|
||||||
&mut try(p_with_vars),
|
&mut try(p_with_vars),
|
||||||
&mut try(p_where_clauses),
|
&mut try(p_where_clauses),
|
||||||
&mut try(p_order_clauses),
|
&mut try(p_order_clauses),
|
||||||
])))
|
])))
|
||||||
.and_then(|parts: Vec<FindQueryPart>| -> std::result::Result<FindQuery, combine::primitives::Error<edn::ValueAndSpan, edn::ValueAndSpan>> {
|
.and_then(|parts: Vec<FindQueryPart>| -> std::result::Result<FindQuery, combine::primitives::Error<edn::ValueAndSpan, edn::ValueAndSpan>> {
|
||||||
let mut find_spec = None;
|
let mut find_spec = None;
|
||||||
|
let mut in_vars = None;
|
||||||
let mut with_vars = None;
|
let mut with_vars = None;
|
||||||
let mut where_clauses = None;
|
let mut where_clauses = None;
|
||||||
let mut order_clauses = None;
|
let mut order_clauses = None;
|
||||||
|
@ -375,6 +402,7 @@ def_parser!(Find, query, FindQuery, {
|
||||||
match part {
|
match part {
|
||||||
FindQueryPart::FindSpec(x) => find_spec = Some(x),
|
FindQueryPart::FindSpec(x) => find_spec = Some(x),
|
||||||
FindQueryPart::With(x) => with_vars = Some(x),
|
FindQueryPart::With(x) => with_vars = Some(x),
|
||||||
|
FindQueryPart::In(x) => in_vars = Some(x),
|
||||||
FindQueryPart::WhereClauses(x) => where_clauses = Some(x),
|
FindQueryPart::WhereClauses(x) => where_clauses = Some(x),
|
||||||
FindQueryPart::Order(x) => order_clauses = Some(x),
|
FindQueryPart::Order(x) => order_clauses = Some(x),
|
||||||
}
|
}
|
||||||
|
@ -383,9 +411,9 @@ def_parser!(Find, query, FindQuery, {
|
||||||
Ok(FindQuery {
|
Ok(FindQuery {
|
||||||
find_spec: find_spec.clone().ok_or(combine::primitives::Error::Unexpected("expected :find".into()))?,
|
find_spec: find_spec.clone().ok_or(combine::primitives::Error::Unexpected("expected :find".into()))?,
|
||||||
default_source: SrcVar::DefaultSrc,
|
default_source: SrcVar::DefaultSrc,
|
||||||
with: with_vars.unwrap_or(vec![]),
|
with: with_vars.unwrap_or(BTreeSet::default()),
|
||||||
in_vars: vec![], // TODO
|
in_vars: in_vars.unwrap_or(BTreeSet::default()),
|
||||||
in_sources: vec![], // TODO
|
in_sources: BTreeSet::default(), // TODO
|
||||||
order: order_clauses,
|
order: order_clauses,
|
||||||
where_clauses: where_clauses.ok_or(combine::primitives::Error::Unexpected("expected :where".into()))?,
|
where_clauses: where_clauses.ok_or(combine::primitives::Error::Unexpected("expected :where".into()))?,
|
||||||
})
|
})
|
||||||
|
@ -521,6 +549,33 @@ mod test {
|
||||||
vec![variable(e.clone())]);
|
vec![variable(e.clone())]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_repeated_vars() {
|
||||||
|
let e = edn::PlainSymbol::new("?e");
|
||||||
|
let f = edn::PlainSymbol::new("?f");
|
||||||
|
let input = edn::Value::Vector(vec![edn::Value::PlainSymbol(e.clone()),
|
||||||
|
edn::Value::PlainSymbol(f.clone()),]);
|
||||||
|
assert_parses_to!(Find::vars, input,
|
||||||
|
vec![variable(e.clone()), variable(f.clone())].into_iter().collect());
|
||||||
|
|
||||||
|
let g = edn::PlainSymbol::new("?g");
|
||||||
|
let input = edn::Value::Vector(vec![edn::Value::PlainSymbol(g.clone()),
|
||||||
|
edn::Value::PlainSymbol(g.clone()),]);
|
||||||
|
|
||||||
|
let mut par = Find::vars();
|
||||||
|
let result = par.parse(input.with_spans().into_atom_stream())
|
||||||
|
.map(|x| x.0)
|
||||||
|
.map_err(|e| if let Some(combine::primitives::Error::Other(x)) = e.errors.into_iter().next() {
|
||||||
|
// Pattern matching on boxes is rocket science until Rust Nightly features hit
|
||||||
|
// stable. ErrorKind isn't Clone, so convert to strings. We could pattern match
|
||||||
|
// for exact comparison here.
|
||||||
|
x.downcast::<Error>().ok().map(|e| e.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
});
|
||||||
|
assert_eq!(result, Err(Some("duplicates in variable list".to_string())));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_or() {
|
fn test_or() {
|
||||||
let oj = edn::PlainSymbol::new("or");
|
let oj = edn::PlainSymbol::new("or");
|
||||||
|
|
|
@ -4,7 +4,7 @@ version = "0.0.1"
|
||||||
workspace = ".."
|
workspace = ".."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
error-chain = "0.9.0"
|
error-chain = "0.8.1"
|
||||||
|
|
||||||
[dependencies.rusqlite]
|
[dependencies.rusqlite]
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
|
|
@ -14,11 +14,6 @@ use mentat_core::{
|
||||||
ValueType,
|
ValueType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_query::{
|
|
||||||
Direction,
|
|
||||||
Variable,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query_algebrizer::{
|
use mentat_query_algebrizer::{
|
||||||
AlgebraicQuery,
|
AlgebraicQuery,
|
||||||
ColumnAlternation,
|
ColumnAlternation,
|
||||||
|
|
|
@ -382,6 +382,7 @@ fn test_order_by() {
|
||||||
FROM `datoms` AS `datoms00` \
|
FROM `datoms` AS `datoms00` \
|
||||||
WHERE `datoms00`.a = 99 \
|
WHERE `datoms00`.a = 99 \
|
||||||
ORDER BY `?y` DESC");
|
ORDER BY `?y` DESC");
|
||||||
|
assert_eq!(args, vec![]);
|
||||||
|
|
||||||
// Unknown type.
|
// Unknown type.
|
||||||
let input = r#"[:find ?x :with ?y :where [?x _ ?y] :order ?y ?x]"#;
|
let input = r#"[:find ?x :with ?y :where [?x _ ?y] :order ?y ?x]"#;
|
||||||
|
@ -390,4 +391,5 @@ fn test_order_by() {
|
||||||
`all_datoms00`.value_type_tag AS `?y_value_type_tag` \
|
`all_datoms00`.value_type_tag AS `?y_value_type_tag` \
|
||||||
FROM `all_datoms` AS `all_datoms00` \
|
FROM `all_datoms` AS `all_datoms00` \
|
||||||
ORDER BY `?y_value_type_tag` ASC, `?y` ASC, `?x` ASC");
|
ORDER BY `?y_value_type_tag` ASC, `?y` ASC, `?x` ASC");
|
||||||
|
assert_eq!(args, vec![]);
|
||||||
}
|
}
|
|
@ -33,12 +33,19 @@
|
||||||
extern crate edn;
|
extern crate edn;
|
||||||
extern crate mentat_core;
|
extern crate mentat_core;
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::{
|
||||||
|
BTreeSet,
|
||||||
|
};
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use edn::{BigInt, OrderedFloat};
|
use edn::{BigInt, OrderedFloat};
|
||||||
pub use edn::{NamespacedKeyword, PlainSymbol};
|
pub use edn::{NamespacedKeyword, PlainSymbol};
|
||||||
use mentat_core::TypedValue;
|
|
||||||
|
use mentat_core::{
|
||||||
|
TypedValue,
|
||||||
|
};
|
||||||
|
|
||||||
pub type SrcVarName = String; // Do not include the required syntactic '$'.
|
pub type SrcVarName = String; // Do not include the required syntactic '$'.
|
||||||
|
|
||||||
|
@ -138,7 +145,7 @@ pub enum Direction {
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct Order(pub Direction, pub Variable); // Future: Element instead of Variable?
|
pub struct Order(pub Direction, pub Variable); // Future: Element instead of Variable?
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum SrcVar {
|
pub enum SrcVar {
|
||||||
DefaultSrc,
|
DefaultSrc,
|
||||||
NamedSrc(SrcVarName),
|
NamedSrc(SrcVarName),
|
||||||
|
@ -600,9 +607,9 @@ pub enum WhereClause {
|
||||||
pub struct FindQuery {
|
pub struct FindQuery {
|
||||||
pub find_spec: FindSpec,
|
pub find_spec: FindSpec,
|
||||||
pub default_source: SrcVar,
|
pub default_source: SrcVar,
|
||||||
pub with: Vec<Variable>,
|
pub with: BTreeSet<Variable>,
|
||||||
pub in_vars: Vec<Variable>,
|
pub in_vars: BTreeSet<Variable>,
|
||||||
pub in_sources: Vec<SrcVar>,
|
pub in_sources: BTreeSet<SrcVar>,
|
||||||
pub where_clauses: Vec<WhereClause>,
|
pub where_clauses: Vec<WhereClause>,
|
||||||
pub order: Option<Vec<Order>>,
|
pub order: Option<Vec<Order>>,
|
||||||
// TODO: in_rules;
|
// TODO: in_rules;
|
||||||
|
@ -734,4 +741,4 @@ impl ContainsVariables for Pattern {
|
||||||
acc_ref(acc, v)
|
acc_ref(acc, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ version = "0.0.1"
|
||||||
workspace = ".."
|
workspace = ".."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
error-chain = "0.9.0"
|
error-chain = "0.8.1"
|
||||||
ordered-float = "0.4.0"
|
ordered-float = "0.4.0"
|
||||||
|
|
||||||
[dependencies.mentat_core]
|
[dependencies.mentat_core]
|
||||||
|
|
11
src/conn.rs
11
src/conn.rs
|
@ -10,26 +10,29 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use rusqlite;
|
use rusqlite;
|
||||||
|
|
||||||
use edn;
|
use edn;
|
||||||
use errors::*;
|
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
Schema,
|
Schema,
|
||||||
TypedValue,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_db::db;
|
use mentat_db::db;
|
||||||
use mentat_db::{
|
use mentat_db::{
|
||||||
transact,
|
transact,
|
||||||
PartitionMap,
|
PartitionMap,
|
||||||
TxReport,
|
TxReport,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_tx_parser;
|
use mentat_tx_parser;
|
||||||
|
|
||||||
|
use errors::*;
|
||||||
use query::{
|
use query::{
|
||||||
q_once,
|
q_once,
|
||||||
|
QueryInputs,
|
||||||
QueryResults,
|
QueryResults,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -112,7 +115,7 @@ impl Conn {
|
||||||
query: &str,
|
query: &str,
|
||||||
inputs: T,
|
inputs: T,
|
||||||
limit: U) -> Result<QueryResults>
|
limit: U) -> Result<QueryResults>
|
||||||
where T: Into<Option<HashMap<String, TypedValue>>>,
|
where T: Into<Option<QueryInputs>>,
|
||||||
U: Into<Option<u64>>
|
U: Into<Option<u64>>
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
use rusqlite;
|
use rusqlite;
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use edn;
|
use edn;
|
||||||
use mentat_db;
|
use mentat_db;
|
||||||
use mentat_query_algebrizer;
|
use mentat_query_algebrizer;
|
||||||
|
@ -40,6 +42,11 @@ error_chain! {
|
||||||
}
|
}
|
||||||
|
|
||||||
errors {
|
errors {
|
||||||
|
UnboundVariables(names: BTreeSet<String>) {
|
||||||
|
description("unbound variables at execution time")
|
||||||
|
display("variables {:?} unbound at execution time", names)
|
||||||
|
}
|
||||||
|
|
||||||
InvalidArgumentName(name: String) {
|
InvalidArgumentName(name: String) {
|
||||||
description("invalid argument name")
|
description("invalid argument name")
|
||||||
display("invalid argument name: '{}'", name)
|
display("invalid argument name: '{}'", name)
|
||||||
|
|
|
@ -54,7 +54,9 @@ pub use mentat_db::{
|
||||||
pub use query::{
|
pub use query::{
|
||||||
NamespacedKeyword,
|
NamespacedKeyword,
|
||||||
PlainSymbol,
|
PlainSymbol,
|
||||||
|
QueryInputs,
|
||||||
QueryResults,
|
QueryResults,
|
||||||
|
Variable,
|
||||||
q_once,
|
q_once,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
39
src/query.rs
39
src/query.rs
|
@ -8,21 +8,25 @@
|
||||||
// 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.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use rusqlite;
|
use rusqlite;
|
||||||
use rusqlite::types::ToSql;
|
use rusqlite::types::ToSql;
|
||||||
|
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
Schema,
|
Schema,
|
||||||
TypedValue,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_query_algebrizer::algebrize;
|
use mentat_query_algebrizer::{
|
||||||
|
algebrize_with_inputs,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use mentat_query_algebrizer::{
|
||||||
|
QueryInputs,
|
||||||
|
};
|
||||||
|
|
||||||
pub use mentat_query::{
|
pub use mentat_query::{
|
||||||
NamespacedKeyword,
|
NamespacedKeyword,
|
||||||
PlainSymbol,
|
PlainSymbol,
|
||||||
|
Variable,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_query_parser::{
|
use mentat_query_parser::{
|
||||||
|
@ -41,31 +45,31 @@ pub use mentat_query_projector::{
|
||||||
QueryResults,
|
QueryResults,
|
||||||
};
|
};
|
||||||
|
|
||||||
use errors::Result;
|
use errors::{
|
||||||
|
ErrorKind,
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
|
||||||
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 an open SQLite connection, a Mentat schema, and an
|
||||||
/// collection of input bindings (which should be keyed by `"?varname"`), and execute the query
|
/// optional collection of input bindings (which should be keyed by `"?varname"`), and execute the
|
||||||
/// immediately, blocking the current thread.
|
/// query immediately, blocking the current thread.
|
||||||
/// Returns a structure that corresponds to the kind of input query, populated with `TypedValue`
|
/// Returns a structure that corresponds to the kind of input query, populated with `TypedValue`
|
||||||
/// instances.
|
/// instances.
|
||||||
/// The caller is responsible for ensuring that the SQLite connection is in a transaction if
|
/// The caller is responsible for ensuring that the SQLite connection has an open transaction if
|
||||||
/// isolation is required.
|
/// isolation is required.
|
||||||
#[allow(unused_variables)]
|
|
||||||
pub fn q_once<'sqlite, 'schema, 'query, T, U>
|
pub fn q_once<'sqlite, 'schema, 'query, T, U>
|
||||||
(sqlite: &'sqlite rusqlite::Connection,
|
(sqlite: &'sqlite rusqlite::Connection,
|
||||||
schema: &'schema Schema,
|
schema: &'schema Schema,
|
||||||
query: &'query str,
|
query: &'query str,
|
||||||
inputs: T,
|
inputs: T,
|
||||||
limit: U) -> QueryExecutionResult
|
limit: U) -> QueryExecutionResult
|
||||||
where T: Into<Option<HashMap<String, TypedValue>>>,
|
where T: Into<Option<QueryInputs>>,
|
||||||
U: Into<Option<u64>>
|
U: Into<Option<u64>>
|
||||||
{
|
{
|
||||||
// TODO: validate inputs.
|
|
||||||
|
|
||||||
let parsed = parse_find_string(query)?;
|
let parsed = parse_find_string(query)?;
|
||||||
let mut algebrized = algebrize(schema, parsed)?;
|
let mut algebrized = algebrize_with_inputs(schema, parsed, 0, inputs.into().unwrap_or(QueryInputs::default()))?;
|
||||||
|
|
||||||
if algebrized.is_known_empty() {
|
if algebrized.is_known_empty() {
|
||||||
// We don't need to do any SQL work at all.
|
// We don't need to do any SQL work at all.
|
||||||
|
@ -74,6 +78,13 @@ pub fn q_once<'sqlite, 'schema, 'query, T, U>
|
||||||
|
|
||||||
algebrized.apply_limit(limit.into());
|
algebrized.apply_limit(limit.into());
|
||||||
|
|
||||||
|
// Because this is q_once, we can check that all of our `:in` variables are bound at this point.
|
||||||
|
// If they aren't, the user has made an error -- perhaps writing the wrong variable in `:in`, or
|
||||||
|
// not binding in the `QueryInput`.
|
||||||
|
let unbound = algebrized.unbound_variables();
|
||||||
|
if !unbound.is_empty() {
|
||||||
|
bail!(ErrorKind::UnboundVariables(unbound.into_iter().map(|v| v.to_string()).collect()));
|
||||||
|
}
|
||||||
let select = query_to_select(algebrized);
|
let select = query_to_select(algebrized);
|
||||||
let SQLQuery { sql, args } = select.query.to_sql_query()?;
|
let SQLQuery { sql, args } = select.query.to_sql_query()?;
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,18 @@ use mentat_core::{
|
||||||
|
|
||||||
use mentat::{
|
use mentat::{
|
||||||
NamespacedKeyword,
|
NamespacedKeyword,
|
||||||
|
QueryInputs,
|
||||||
QueryResults,
|
QueryResults,
|
||||||
|
Variable,
|
||||||
new_connection,
|
new_connection,
|
||||||
q_once,
|
q_once,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use mentat::errors::{
|
||||||
|
Error,
|
||||||
|
ErrorKind,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rel() {
|
fn test_rel() {
|
||||||
let mut c = new_connection("").expect("Couldn't open conn.");
|
let mut c = new_connection("").expect("Couldn't open conn.");
|
||||||
|
@ -159,3 +166,41 @@ fn test_coll() {
|
||||||
println!("Coll took {}µs", start.to(end).num_microseconds().unwrap());
|
println!("Coll took {}µs", start.to(end).num_microseconds().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_inputs() {
|
||||||
|
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.");
|
||||||
|
|
||||||
|
// entids::DB_INSTALL_VALUE_TYPE = 5.
|
||||||
|
let ee = (Variable::from_valid_name("?e"), TypedValue::Ref(5));
|
||||||
|
let inputs = QueryInputs::with_value_sequence(vec![ee]);
|
||||||
|
let results = q_once(&c, &db.schema,
|
||||||
|
"[:find ?i . :in ?e :where [?e :db/ident ?i]]", inputs, None)
|
||||||
|
.expect("query to succeed");
|
||||||
|
|
||||||
|
if let QueryResults::Scalar(Some(TypedValue::Keyword(value))) = results {
|
||||||
|
assert_eq!(value.as_ref(), &NamespacedKeyword::new("db.install", "valueType"));
|
||||||
|
} else {
|
||||||
|
panic!("Expected scalar.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that a query won't be run without all of its `:in` variables being bound.
|
||||||
|
#[test]
|
||||||
|
fn test_unbound_inputs() {
|
||||||
|
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.");
|
||||||
|
|
||||||
|
// Bind the wrong var by 'mistake'.
|
||||||
|
let xx = (Variable::from_valid_name("?x"), TypedValue::Ref(5));
|
||||||
|
let inputs = QueryInputs::with_value_sequence(vec![xx]);
|
||||||
|
let results = q_once(&c, &db.schema,
|
||||||
|
"[:find ?i . :in ?e :where [?e :db/ident ?i]]", inputs, None);
|
||||||
|
|
||||||
|
match results {
|
||||||
|
Result::Err(Error(ErrorKind::UnboundVariables(vars), _)) => {
|
||||||
|
assert_eq!(vars, vec!["?e".to_string()].into_iter().collect());
|
||||||
|
},
|
||||||
|
_ => panic!("Expected unbound variables."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ workspace = ".."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
combine = "2.2.2"
|
combine = "2.2.2"
|
||||||
error-chain = "0.9.0"
|
error-chain = "0.8.1"
|
||||||
|
|
||||||
[dependencies.edn]
|
[dependencies.edn]
|
||||||
path = "../edn"
|
path = "../edn"
|
||||||
|
|
Loading…
Reference in a new issue