Part 4: pass inputs through algebrizing and execution. (#418)
This also adds a test that an `UnboundVariables` error is raised if a variable mentioned in the `:in` clause isn't bound.
This commit is contained in:
parent
dfc846e483
commit
60c082b61e
10 changed files with 159 additions and 48 deletions
|
@ -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;
|
||||||
|
@ -240,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.
|
||||||
|
@ -271,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;
|
||||||
|
|
|
@ -34,7 +34,6 @@ extern crate edn;
|
||||||
extern crate mentat_core;
|
extern crate mentat_core;
|
||||||
|
|
||||||
use std::collections::{
|
use std::collections::{
|
||||||
BTreeMap,
|
|
||||||
BTreeSet,
|
BTreeSet,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,7 +45,6 @@ pub use edn::{NamespacedKeyword, PlainSymbol};
|
||||||
|
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
TypedValue,
|
TypedValue,
|
||||||
ValueType,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type SrcVarName = String; // Do not include the required syntactic '$'.
|
pub type SrcVarName = String; // Do not include the required syntactic '$'.
|
||||||
|
@ -743,4 +741,4 @@ impl ContainsVariables for Pattern {
|
||||||
acc_ref(acc, v)
|
acc_ref(acc, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,7 +10,6 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use rusqlite;
|
use rusqlite;
|
||||||
|
@ -19,7 +18,6 @@ use edn;
|
||||||
|
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
Schema,
|
Schema,
|
||||||
TypedValue,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_db::db;
|
use mentat_db::db;
|
||||||
|
@ -29,13 +27,12 @@ use mentat_db::{
|
||||||
TxReport,
|
TxReport,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_query::Variable;
|
|
||||||
|
|
||||||
use mentat_tx_parser;
|
use mentat_tx_parser;
|
||||||
|
|
||||||
use errors::*;
|
use errors::*;
|
||||||
use query::{
|
use query::{
|
||||||
q_once,
|
q_once,
|
||||||
|
QueryInputs,
|
||||||
QueryResults,
|
QueryResults,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -118,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<Variable, 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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
38
src/query.rs
38
src/query.rs
|
@ -8,17 +8,20 @@
|
||||||
// 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,
|
||||||
|
@ -42,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<Variable, 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.
|
||||||
|
@ -75,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."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue