diff --git a/query-projector/src/binding_tuple.rs b/query-projector/src/binding_tuple.rs new file mode 100644 index 00000000..5af2abd6 --- /dev/null +++ b/query-projector/src/binding_tuple.rs @@ -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>) -> Result>; +} + +// This is a no-op, essentially: we can always produce a vector representation of a tuple result. +impl BindingTuple for Vec { + fn from_binding_vec(expected: usize, vec: Option>) -> Result> { + 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>) -> Result> { + 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>) -> Result> { + 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>) -> Result> { + 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>) -> Result> { + 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>) -> Result> { + 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>) -> Result> { + 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()))) + } + } + } + } +} diff --git a/query-projector/src/errors.rs b/query-projector/src/errors.rs index c27a74d5..040a6f63 100644 --- a/query-projector/src/errors.rs +++ b/query-projector/src/errors.rs @@ -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), diff --git a/query-projector/src/lib.rs b/query-projector/src/lib.rs index de80ca8e..0e345fe7 100644 --- a/query-projector/src/lib.rs +++ b/query-projector/src/lib.rs @@ -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, pub results: QueryResults, @@ -267,8 +271,15 @@ impl QueryOutput { self.results.into_coll() } - pub fn into_tuple(self) -> Result>> { - 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(self) -> Result> 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> { @@ -515,3 +526,46 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result { + 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"), + } +} diff --git a/src/lib.rs b/src/lib.rs index 35eafb05..78650a99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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;