Part 2: Make it easier to match tuple results.
Right now, we write code like ```rust match q_once(q, inputs)?.into_tuple()? { Some(vs) => match (vs.len(), vs.get(0), vs.get(1)) { (2, &Some(Binding::Scalar(TypedValue::Long(a))), &Some(Binding::Scalar(TypedValue::Instant(ref b)))) => Some((a, b.clone())), _ => panic!(), }, None => None, } ``` to length-check tuples coming out of the database. It can also lead to a lot of cloning because references are the easiest thing to hand. This commit allows to write code like ```rust match q_once(q, inputs)?.into_tuple()? { Some((Binding::Scalar(TypedValue::Long(a)), Binding::Scalar(TypedValue::Instant(b)))) => Some((a, b)), Some(_) => panic!(), None => None, } ``` which is generally much easier to reason about.
This commit is contained in:
parent
e362ca6213
commit
2cb7d441dc
4 changed files with 223 additions and 4 deletions
159
query-projector/src/binding_tuple.rs
Normal file
159
query-projector/src/binding_tuple.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
// Copyright 2018 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 mentat_core::{
|
||||
Binding,
|
||||
};
|
||||
|
||||
use errors::{
|
||||
ProjectorError,
|
||||
Result,
|
||||
};
|
||||
|
||||
/// A `BindingTuple` is any type that can accommodate a Mentat tuple query result of fixed length.
|
||||
///
|
||||
/// Currently Rust tuples of length 1 through 6 (i.e., `(A)` through `(A, B, C, D, E, F)`) are
|
||||
/// supported as are vectors (i.e., `Vec<>`).
|
||||
pub trait BindingTuple: Sized {
|
||||
fn from_binding_vec(expected: usize, vec: Option<Vec<Binding>>) -> Result<Option<Self>>;
|
||||
}
|
||||
|
||||
// This is a no-op, essentially: we can always produce a vector representation of a tuple result.
|
||||
impl BindingTuple for Vec<Binding> {
|
||||
fn from_binding_vec(expected: usize, vec: Option<Vec<Binding>>) -> Result<Option<Self>> {
|
||||
match vec {
|
||||
None => Ok(None),
|
||||
Some(vec) => {
|
||||
if expected != vec.len() {
|
||||
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len()))
|
||||
} else {
|
||||
Ok(Some(vec))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: generate these repetitive implementations with a little macro.
|
||||
impl BindingTuple for (Binding,) {
|
||||
fn from_binding_vec(expected: usize, vec: Option<Vec<Binding>>) -> Result<Option<Self>> {
|
||||
if expected != 1 {
|
||||
return Err(ProjectorError::UnexpectedResultsTupleLength(1, expected));
|
||||
}
|
||||
match vec {
|
||||
None => Ok(None),
|
||||
Some(vec) => {
|
||||
if expected != vec.len() {
|
||||
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len()))
|
||||
} else {
|
||||
let mut iter = vec.into_iter();
|
||||
Ok(Some((iter.next().unwrap(),)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BindingTuple for (Binding, Binding) {
|
||||
fn from_binding_vec(expected: usize, vec: Option<Vec<Binding>>) -> Result<Option<Self>> {
|
||||
if expected != 2 {
|
||||
return Err(ProjectorError::UnexpectedResultsTupleLength(2, expected));
|
||||
}
|
||||
match vec {
|
||||
None => Ok(None),
|
||||
Some(vec) => {
|
||||
if expected != vec.len() {
|
||||
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len()))
|
||||
} else {
|
||||
let mut iter = vec.into_iter();
|
||||
Ok(Some((iter.next().unwrap(), iter.next().unwrap())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BindingTuple for (Binding, Binding, Binding) {
|
||||
fn from_binding_vec(expected: usize, vec: Option<Vec<Binding>>) -> Result<Option<Self>> {
|
||||
if expected != 3 {
|
||||
return Err(ProjectorError::UnexpectedResultsTupleLength(3, expected));
|
||||
}
|
||||
match vec {
|
||||
None => Ok(None),
|
||||
Some(vec) => {
|
||||
if expected != vec.len() {
|
||||
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len()))
|
||||
} else {
|
||||
let mut iter = vec.into_iter();
|
||||
Ok(Some((iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BindingTuple for (Binding, Binding, Binding, Binding) {
|
||||
fn from_binding_vec(expected: usize, vec: Option<Vec<Binding>>) -> Result<Option<Self>> {
|
||||
if expected != 4 {
|
||||
return Err(ProjectorError::UnexpectedResultsTupleLength(4, expected));
|
||||
}
|
||||
match vec {
|
||||
None => Ok(None),
|
||||
Some(vec) => {
|
||||
if expected != vec.len() {
|
||||
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len()))
|
||||
} else {
|
||||
let mut iter = vec.into_iter();
|
||||
Ok(Some((iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BindingTuple for (Binding, Binding, Binding, Binding, Binding) {
|
||||
fn from_binding_vec(expected: usize, vec: Option<Vec<Binding>>) -> Result<Option<Self>> {
|
||||
if expected != 5 {
|
||||
return Err(ProjectorError::UnexpectedResultsTupleLength(5, expected));
|
||||
}
|
||||
match vec {
|
||||
None => Ok(None),
|
||||
Some(vec) => {
|
||||
if expected != vec.len() {
|
||||
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len()))
|
||||
} else {
|
||||
let mut iter = vec.into_iter();
|
||||
Ok(Some((iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: allow binding tuples of length more than 6. Folks who are binding such large tuples are
|
||||
// probably doing something wrong -- they should investigate a pull expression.
|
||||
impl BindingTuple for (Binding, Binding, Binding, Binding, Binding, Binding) {
|
||||
fn from_binding_vec(expected: usize, vec: Option<Vec<Binding>>) -> Result<Option<Self>> {
|
||||
if expected != 6 {
|
||||
return Err(ProjectorError::UnexpectedResultsTupleLength(6, expected));
|
||||
}
|
||||
match vec {
|
||||
None => Ok(None),
|
||||
Some(vec) => {
|
||||
if expected != vec.len() {
|
||||
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len()))
|
||||
} else {
|
||||
let mut iter = vec.into_iter();
|
||||
Ok(Some((iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -59,6 +59,9 @@ pub enum ProjectorError {
|
|||
#[fail(display = "expected {}, got {}", _0, _1)]
|
||||
UnexpectedResultsType(&'static str, &'static str),
|
||||
|
||||
#[fail(display = "expected tuple of length {}, got tuple of length {}", _0, _1)]
|
||||
UnexpectedResultsTupleLength(usize, usize),
|
||||
|
||||
#[fail(display = "min/max expressions: {} (max 1), corresponding: {}", _0, _1)]
|
||||
AmbiguousAggregates(usize, usize),
|
||||
|
||||
|
|
|
@ -73,6 +73,10 @@ use mentat_query_sql::{
|
|||
pub mod errors;
|
||||
|
||||
mod aggregates;
|
||||
mod binding_tuple;
|
||||
pub use binding_tuple::{
|
||||
BindingTuple,
|
||||
};
|
||||
mod project;
|
||||
mod projectors;
|
||||
mod pull;
|
||||
|
@ -117,7 +121,7 @@ pub use errors::{
|
|||
Result,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct QueryOutput {
|
||||
pub spec: Rc<FindSpec>,
|
||||
pub results: QueryResults,
|
||||
|
@ -267,8 +271,15 @@ impl QueryOutput {
|
|||
self.results.into_coll()
|
||||
}
|
||||
|
||||
pub fn into_tuple(self) -> Result<Option<Vec<Binding>>> {
|
||||
self.results.into_tuple()
|
||||
/// Mentat tuple results can be expressed as multiple different data structures. Some
|
||||
/// structures are generic (vectors) and some are easier for pattern matching (fixed length
|
||||
/// tuples).
|
||||
///
|
||||
/// This is the moral equivalent of `collect` (and `BindingTuple` of `FromIterator`), but
|
||||
/// specialized to tuples of expected length.
|
||||
pub fn into_tuple<B>(self) -> Result<Option<B>> where B: BindingTuple {
|
||||
let expected = self.spec.expected_column_count();
|
||||
self.results.into_tuple().and_then(|vec| B::from_binding_vec(expected, vec))
|
||||
}
|
||||
|
||||
pub fn into_rel(self) -> Result<RelResult<Binding>> {
|
||||
|
@ -515,3 +526,46 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result<Eithe
|
|||
}.map(Either::Right)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_tuple() {
|
||||
let query_output = QueryOutput {
|
||||
spec: Rc::new(FindSpec::FindTuple(vec![Element::Variable(Variable::from_valid_name("?x")),
|
||||
Element::Variable(Variable::from_valid_name("?y"))])),
|
||||
results: QueryResults::Tuple(Some(vec![Binding::Scalar(TypedValue::Long(0)),
|
||||
Binding::Scalar(TypedValue::Long(2))])),
|
||||
};
|
||||
|
||||
assert_eq!(query_output.clone().into_tuple().expect("into_tuple"),
|
||||
Some((Binding::Scalar(TypedValue::Long(0)),
|
||||
Binding::Scalar(TypedValue::Long(2)))));
|
||||
|
||||
match query_output.clone().into_tuple() {
|
||||
Err(ProjectorError::UnexpectedResultsTupleLength(expected, got)) => {
|
||||
assert_eq!((expected, got), (3, 2));
|
||||
},
|
||||
// This forces the result type.
|
||||
Ok(Some((_, _, _))) | _ => panic!("expected error"),
|
||||
}
|
||||
|
||||
let query_output = QueryOutput {
|
||||
spec: Rc::new(FindSpec::FindTuple(vec![Element::Variable(Variable::from_valid_name("?x")),
|
||||
Element::Variable(Variable::from_valid_name("?y"))])),
|
||||
results: QueryResults::Tuple(None),
|
||||
};
|
||||
|
||||
|
||||
match query_output.clone().into_tuple() {
|
||||
Ok(None) => {},
|
||||
// This forces the result type.
|
||||
Ok(Some((_, _))) | _ => panic!("expected error"),
|
||||
}
|
||||
|
||||
match query_output.clone().into_tuple() {
|
||||
Err(ProjectorError::UnexpectedResultsTupleLength(expected, got)) => {
|
||||
assert_eq!((expected, got), (3, 2));
|
||||
},
|
||||
// This forces the result type.
|
||||
Ok(Some((_, _, _))) | _ => panic!("expected error"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,7 +120,10 @@ pub use edn::{
|
|||
};
|
||||
pub use mentat_db::DbError;
|
||||
pub use mentat_query_algebrizer::AlgebrizerError;
|
||||
pub use mentat_query_projector::ProjectorError;
|
||||
pub use mentat_query_projector::{
|
||||
BindingTuple,
|
||||
ProjectorError,
|
||||
};
|
||||
pub use mentat_query_pull::PullError;
|
||||
pub use mentat_sql::SQLError;
|
||||
|
||||
|
|
Loading…
Reference in a new issue