2017-03-06 22:40:10 +00:00
|
|
|
// 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.
|
|
|
|
|
2018-06-04 20:53:31 +00:00
|
|
|
extern crate failure;
|
|
|
|
|
2018-03-12 22:18:50 +00:00
|
|
|
extern crate indexmap;
|
2017-03-06 22:40:10 +00:00
|
|
|
extern crate rusqlite;
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
extern crate db_traits;
|
2018-08-08 18:23:07 +00:00
|
|
|
extern crate edn;
|
2017-03-06 22:40:10 +00:00
|
|
|
extern crate mentat_core;
|
2018-08-08 23:36:23 +00:00
|
|
|
#[macro_use]
|
2018-08-08 17:35:06 +00:00
|
|
|
extern crate core_traits;
|
2020-01-14 15:46:21 +00:00
|
|
|
extern crate mentat_db; // For value conversion.
|
2017-03-06 22:40:10 +00:00
|
|
|
extern crate mentat_query_algebrizer;
|
2018-05-04 19:56:00 +00:00
|
|
|
extern crate mentat_query_pull;
|
2017-03-06 22:40:10 +00:00
|
|
|
extern crate mentat_query_sql;
|
2020-01-14 15:46:21 +00:00
|
|
|
extern crate query_projector_traits;
|
|
|
|
extern crate query_pull_traits;
|
2017-03-06 22:40:10 +00:00
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
use std::collections::BTreeSet;
|
2018-03-06 17:01:20 +00:00
|
|
|
|
2017-03-06 22:40:10 +00:00
|
|
|
use std::iter;
|
2018-03-12 22:18:50 +00:00
|
|
|
|
2018-01-30 22:11:41 +00:00
|
|
|
use std::rc::Rc;
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
use rusqlite::{Row, Rows};
|
2017-03-06 22:40:10 +00:00
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
use core_traits::{Binding, TypedValue};
|
2018-08-08 17:36:41 +00:00
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
use mentat_core::{Schema, ValueTypeTag};
|
2017-03-06 22:40:10 +00:00
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
use mentat_core::util::Either;
|
2018-03-06 17:01:20 +00:00
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
use mentat_db::TypedSQLValue;
|
2017-03-06 22:40:10 +00:00
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
use edn::query::{Element, FindSpec, Limit, Variable};
|
2017-03-06 22:40:10 +00:00
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
use mentat_query_algebrizer::{AlgebraicQuery, VariableBindings};
|
2017-03-06 22:40:10 +00:00
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
use mentat_query_sql::{GroupBy, Projection};
|
2017-03-06 22:40:10 +00:00
|
|
|
|
2018-08-08 20:03:27 +00:00
|
|
|
pub mod translate;
|
|
|
|
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
mod binding_tuple;
|
2020-08-06 03:03:58 +00:00
|
|
|
pub use crate::binding_tuple::BindingTuple;
|
2018-03-30 19:19:02 +00:00
|
|
|
mod project;
|
2018-05-04 19:56:00 +00:00
|
|
|
mod projectors;
|
|
|
|
mod pull;
|
2018-04-24 22:04:00 +00:00
|
|
|
mod relresult;
|
2017-03-06 22:40:10 +00:00
|
|
|
|
2020-08-06 03:03:58 +00:00
|
|
|
use crate::project::{project_elements, ProjectedElements};
|
2017-03-06 22:40:10 +00:00
|
|
|
|
2020-08-06 03:03:58 +00:00
|
|
|
pub use crate::project::projected_column_for_var;
|
2018-03-30 19:19:02 +00:00
|
|
|
|
2020-08-06 03:03:58 +00:00
|
|
|
pub use crate::projectors::{ConstantProjector, Projector};
|
2018-05-04 19:56:00 +00:00
|
|
|
|
2020-08-06 03:03:58 +00:00
|
|
|
use crate::projectors::{
|
2020-01-14 15:46:21 +00:00
|
|
|
CollProjector, CollTwoStagePullProjector, RelProjector, RelTwoStagePullProjector,
|
|
|
|
ScalarProjector, ScalarTwoStagePullProjector, TupleProjector, TupleTwoStagePullProjector,
|
2018-05-04 19:56:00 +00:00
|
|
|
};
|
|
|
|
|
2020-08-06 03:03:58 +00:00
|
|
|
pub use crate::relresult::{RelResult, StructuredRelResult};
|
2018-04-24 22:04:00 +00:00
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
use query_projector_traits::errors::{ProjectorError, Result};
|
2017-03-06 22:40:10 +00:00
|
|
|
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
2018-01-30 22:11:41 +00:00
|
|
|
pub struct QueryOutput {
|
|
|
|
pub spec: Rc<FindSpec>,
|
|
|
|
pub results: QueryResults,
|
|
|
|
}
|
|
|
|
|
2018-03-06 17:01:20 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
2017-03-06 22:40:10 +00:00
|
|
|
pub enum QueryResults {
|
2018-04-24 22:08:38 +00:00
|
|
|
Scalar(Option<Binding>),
|
|
|
|
Tuple(Option<Vec<Binding>>),
|
|
|
|
Coll(Vec<Binding>),
|
|
|
|
Rel(RelResult<Binding>),
|
2017-03-06 22:40:10 +00:00
|
|
|
}
|
|
|
|
|
2018-01-30 22:11:41 +00:00
|
|
|
impl From<QueryOutput> for QueryResults {
|
|
|
|
fn from(o: QueryOutput) -> QueryResults {
|
|
|
|
o.results
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl QueryOutput {
|
2020-01-14 15:46:21 +00:00
|
|
|
pub fn empty_factory(spec: &FindSpec) -> Box<dyn Fn() -> QueryResults> {
|
2018-01-30 22:11:41 +00:00
|
|
|
use self::FindSpec::*;
|
2020-02-21 15:27:39 +00:00
|
|
|
match *spec {
|
|
|
|
FindScalar(_) => Box::new(|| QueryResults::Scalar(None)),
|
|
|
|
FindTuple(_) => Box::new(|| QueryResults::Tuple(None)),
|
|
|
|
FindColl(_) => Box::new(|| QueryResults::Coll(vec![])),
|
|
|
|
FindRel(ref es) => {
|
2018-04-24 22:04:00 +00:00
|
|
|
let width = es.len();
|
|
|
|
Box::new(move || QueryResults::Rel(RelResult::empty(width)))
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2018-01-30 22:11:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.results.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.results.is_empty()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn empty(spec: &Rc<FindSpec>) -> QueryOutput {
|
|
|
|
use self::FindSpec::*;
|
2020-02-21 15:27:39 +00:00
|
|
|
let results = match **spec {
|
|
|
|
FindScalar(_) => QueryResults::Scalar(None),
|
|
|
|
FindTuple(_) => QueryResults::Tuple(None),
|
|
|
|
FindColl(_) => QueryResults::Coll(vec![]),
|
|
|
|
FindRel(ref es) => QueryResults::Rel(RelResult::empty(es.len())),
|
2020-01-14 15:46:21 +00:00
|
|
|
};
|
2018-01-30 22:11:41 +00:00
|
|
|
QueryOutput {
|
|
|
|
spec: spec.clone(),
|
2020-02-21 15:27:39 +00:00
|
|
|
results,
|
2018-01-30 22:11:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-06 17:01:20 +00:00
|
|
|
pub fn from_constants(spec: &Rc<FindSpec>, bindings: VariableBindings) -> QueryResults {
|
|
|
|
use self::FindSpec::*;
|
2020-02-21 15:27:39 +00:00
|
|
|
match **spec {
|
|
|
|
FindScalar(Element::Variable(ref var))
|
|
|
|
| FindScalar(Element::Corresponding(ref var)) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
let val = bindings.get(var).cloned().map(|v| v.into());
|
2018-03-06 17:01:20 +00:00
|
|
|
QueryResults::Scalar(val)
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
FindScalar(Element::Aggregate(ref _agg)) => {
|
2018-05-04 19:56:00 +00:00
|
|
|
// TODO: static aggregates.
|
|
|
|
unimplemented!();
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
FindScalar(Element::Pull(ref _pull)) => {
|
2018-05-04 19:56:00 +00:00
|
|
|
// TODO: static pull.
|
2018-03-12 22:18:50 +00:00
|
|
|
unimplemented!();
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
FindTuple(ref elements) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
let values = elements
|
|
|
|
.iter()
|
2020-02-21 15:27:39 +00:00
|
|
|
.map(|e| match *e {
|
|
|
|
Element::Variable(ref var) | Element::Corresponding(ref var) => bindings
|
2020-01-14 15:46:21 +00:00
|
|
|
.get(var)
|
|
|
|
.cloned()
|
|
|
|
.expect("every var to have a binding")
|
|
|
|
.into(),
|
2020-02-21 15:27:39 +00:00
|
|
|
Element::Pull(ref _pull) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
// TODO: static pull.
|
|
|
|
unreachable!();
|
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
Element::Aggregate(ref _agg) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
// TODO: static computation of aggregates, then
|
|
|
|
// implement the condition in `is_fully_bound`.
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect();
|
2018-03-06 17:01:20 +00:00
|
|
|
QueryResults::Tuple(Some(values))
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
FindColl(Element::Variable(ref var)) | FindColl(Element::Corresponding(ref var)) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
let val = bindings
|
|
|
|
.get(var)
|
|
|
|
.cloned()
|
|
|
|
.expect("every var to have a binding")
|
|
|
|
.into();
|
2018-03-06 17:01:20 +00:00
|
|
|
QueryResults::Coll(vec![val])
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
FindColl(Element::Pull(ref _pull)) => {
|
2018-05-04 19:56:00 +00:00
|
|
|
// TODO: static pull.
|
|
|
|
unimplemented!();
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
FindColl(Element::Aggregate(ref _agg)) => {
|
2018-03-12 22:18:50 +00:00
|
|
|
// Does it even make sense to write
|
|
|
|
// [:find [(max ?x) ...] :where [_ :foo/bar ?x]]
|
|
|
|
// ?
|
|
|
|
// TODO
|
|
|
|
unimplemented!();
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
FindRel(ref elements) => {
|
2018-04-24 22:04:00 +00:00
|
|
|
let width = elements.len();
|
2020-01-14 15:46:21 +00:00
|
|
|
let values = elements
|
|
|
|
.iter()
|
2020-02-21 15:27:39 +00:00
|
|
|
.map(|e| match *e {
|
|
|
|
Element::Variable(ref var) | Element::Corresponding(ref var) => bindings
|
2020-01-14 15:46:21 +00:00
|
|
|
.get(var)
|
|
|
|
.cloned()
|
|
|
|
.expect("every var to have a binding")
|
|
|
|
.into(),
|
2020-02-21 15:27:39 +00:00
|
|
|
Element::Pull(ref _pull) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
// TODO: static pull.
|
|
|
|
unreachable!();
|
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
Element::Aggregate(ref _agg) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
// TODO: static computation of aggregates, then
|
|
|
|
// implement the condition in `is_fully_bound`.
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect();
|
2018-04-24 22:04:00 +00:00
|
|
|
QueryResults::Rel(RelResult { width, values })
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2018-03-06 17:01:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-24 22:08:38 +00:00
|
|
|
pub fn into_scalar(self) -> Result<Option<Binding>> {
|
2018-01-30 22:11:41 +00:00
|
|
|
self.results.into_scalar()
|
|
|
|
}
|
|
|
|
|
2018-04-24 22:08:38 +00:00
|
|
|
pub fn into_coll(self) -> Result<Vec<Binding>> {
|
2018-01-30 22:11:41 +00:00
|
|
|
self.results.into_coll()
|
|
|
|
}
|
|
|
|
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
/// 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.
|
2020-01-14 15:46:21 +00:00
|
|
|
pub fn into_tuple<B>(self) -> Result<Option<B>>
|
|
|
|
where
|
|
|
|
B: BindingTuple,
|
|
|
|
{
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
let expected = self.spec.expected_column_count();
|
2020-01-14 15:46:21 +00:00
|
|
|
self.results
|
|
|
|
.into_tuple()
|
|
|
|
.and_then(|vec| B::from_binding_vec(expected, vec))
|
2018-01-30 22:11:41 +00:00
|
|
|
}
|
|
|
|
|
2018-04-24 22:08:38 +00:00
|
|
|
pub fn into_rel(self) -> Result<RelResult<Binding>> {
|
2018-01-30 22:11:41 +00:00
|
|
|
self.results.into_rel()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-06 22:40:10 +00:00
|
|
|
impl QueryResults {
|
|
|
|
pub fn len(&self) -> usize {
|
2020-08-06 03:03:58 +00:00
|
|
|
use crate::QueryResults::*;
|
2020-02-21 15:27:39 +00:00
|
|
|
match *self {
|
|
|
|
Scalar(ref o) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
if o.is_some() {
|
|
|
|
1
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
Tuple(ref o) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
if o.is_some() {
|
|
|
|
1
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
Coll(ref v) => v.len(),
|
|
|
|
Rel(ref r) => r.row_count(),
|
2017-03-06 22:40:10 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-07 04:18:38 +00:00
|
|
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
2020-08-06 03:03:58 +00:00
|
|
|
use crate::QueryResults::*;
|
2020-02-21 15:27:39 +00:00
|
|
|
match *self {
|
|
|
|
Scalar(ref o) => o.is_none(),
|
|
|
|
Tuple(ref o) => o.is_none(),
|
|
|
|
Coll(ref v) => v.is_empty(),
|
|
|
|
Rel(ref r) => r.is_empty(),
|
2017-03-07 04:18:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-24 22:08:38 +00:00
|
|
|
pub fn into_scalar(self) -> Result<Option<Binding>> {
|
2017-12-06 22:34:48 +00:00
|
|
|
match self {
|
|
|
|
QueryResults::Scalar(o) => Ok(o),
|
2018-06-04 20:53:31 +00:00
|
|
|
QueryResults::Coll(_) => bail!(ProjectorError::UnexpectedResultsType("coll", "scalar")),
|
2020-01-14 15:46:21 +00:00
|
|
|
QueryResults::Tuple(_) => {
|
|
|
|
bail!(ProjectorError::UnexpectedResultsType("tuple", "scalar"))
|
|
|
|
}
|
2018-06-04 20:53:31 +00:00
|
|
|
QueryResults::Rel(_) => bail!(ProjectorError::UnexpectedResultsType("rel", "scalar")),
|
2017-12-06 22:34:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-24 22:08:38 +00:00
|
|
|
pub fn into_coll(self) -> Result<Vec<Binding>> {
|
2017-12-06 22:34:48 +00:00
|
|
|
match self {
|
2020-01-14 15:46:21 +00:00
|
|
|
QueryResults::Scalar(_) => {
|
|
|
|
bail!(ProjectorError::UnexpectedResultsType("scalar", "coll"))
|
|
|
|
}
|
2017-12-06 22:34:48 +00:00
|
|
|
QueryResults::Coll(c) => Ok(c),
|
2018-06-04 20:53:31 +00:00
|
|
|
QueryResults::Tuple(_) => bail!(ProjectorError::UnexpectedResultsType("tuple", "coll")),
|
|
|
|
QueryResults::Rel(_) => bail!(ProjectorError::UnexpectedResultsType("rel", "coll")),
|
2017-12-06 22:34:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-24 22:08:38 +00:00
|
|
|
pub fn into_tuple(self) -> Result<Option<Vec<Binding>>> {
|
2017-12-06 22:34:48 +00:00
|
|
|
match self {
|
2020-01-14 15:46:21 +00:00
|
|
|
QueryResults::Scalar(_) => {
|
|
|
|
bail!(ProjectorError::UnexpectedResultsType("scalar", "tuple"))
|
|
|
|
}
|
2018-06-04 20:53:31 +00:00
|
|
|
QueryResults::Coll(_) => bail!(ProjectorError::UnexpectedResultsType("coll", "tuple")),
|
2017-12-06 22:34:48 +00:00
|
|
|
QueryResults::Tuple(t) => Ok(t),
|
2018-06-04 20:53:31 +00:00
|
|
|
QueryResults::Rel(_) => bail!(ProjectorError::UnexpectedResultsType("rel", "tuple")),
|
2017-12-06 22:34:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-24 22:08:38 +00:00
|
|
|
pub fn into_rel(self) -> Result<RelResult<Binding>> {
|
2017-12-06 22:34:48 +00:00
|
|
|
match self {
|
2020-01-14 15:46:21 +00:00
|
|
|
QueryResults::Scalar(_) => {
|
|
|
|
bail!(ProjectorError::UnexpectedResultsType("scalar", "rel"))
|
|
|
|
}
|
2018-06-04 20:53:31 +00:00
|
|
|
QueryResults::Coll(_) => bail!(ProjectorError::UnexpectedResultsType("coll", "rel")),
|
|
|
|
QueryResults::Tuple(_) => bail!(ProjectorError::UnexpectedResultsType("tuple", "rel")),
|
2017-12-06 22:34:48 +00:00
|
|
|
QueryResults::Rel(r) => Ok(r),
|
|
|
|
}
|
|
|
|
}
|
2017-03-06 22:40:10 +00:00
|
|
|
}
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
type Index = usize; // See rusqlite::RowIndex.
|
2017-03-06 22:40:10 +00:00
|
|
|
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:
|
|
|
|
///
|
2018-03-12 22:18:50 +00:00
|
|
|
/// - This is an `Unknown` and the retrieved type tag isn't an i32.
|
2017-03-06 22:40:10 +00:00
|
|
|
/// - If the retrieved value can't be coerced to a rusqlite `Value`.
|
|
|
|
/// - Either index is out of bounds.
|
|
|
|
///
|
2018-03-12 22:18:50 +00:00
|
|
|
/// Because we construct our SQL projection list, the tag that stored the data, and this
|
2017-03-06 22:40:10 +00:00
|
|
|
/// 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.
|
|
|
|
///
|
2018-03-12 22:18:50 +00:00
|
|
|
/// This function will return a runtime error if the type tag is unknown, or the value is
|
2017-03-06 22:40:10 +00:00
|
|
|
/// otherwise not convertible by the DB layer.
|
2019-07-22 11:41:53 +00:00
|
|
|
fn lookup<'a>(&self, row: &Row<'a>) -> Result<Binding> {
|
2020-08-06 03:03:58 +00:00
|
|
|
use crate::TypedIndex::*;
|
2017-03-06 22:40:10 +00:00
|
|
|
|
2020-02-21 15:27:39 +00:00
|
|
|
match *self {
|
|
|
|
Known(value_index, value_type) => {
|
2019-07-22 12:36:32 +00:00
|
|
|
let v: rusqlite::types::Value = row.get(value_index).unwrap();
|
2018-04-24 22:08:38 +00:00
|
|
|
TypedValue::from_sql_value_pair(v, value_type)
|
|
|
|
.map(|v| v.into())
|
|
|
|
.map_err(|e| e.into())
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
Unknown(value_index, type_index) => {
|
2019-07-22 12:36:32 +00:00
|
|
|
let v: rusqlite::types::Value = row.get(value_index).unwrap();
|
|
|
|
let value_type_tag: i32 = row.get(type_index).unwrap();
|
2018-04-24 22:08:38 +00:00
|
|
|
TypedValue::from_sql_value_pair(v, value_type_tag)
|
|
|
|
.map(|v| v.into())
|
|
|
|
.map_err(|e| e.into())
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2017-03-06 22:40:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-12 22:18:50 +00:00
|
|
|
/// Combines the things you need to turn a query into SQL and turn its results into
|
|
|
|
/// `QueryResults`: SQL-related projection information (`DISTINCT`, columns, etc.) and
|
|
|
|
/// a Datalog projector that turns SQL into structures.
|
2017-03-06 22:40:10 +00:00
|
|
|
pub struct CombinedProjection {
|
|
|
|
/// A SQL projection, mapping columns mentioned in the body of the query to columns in the
|
|
|
|
/// output.
|
|
|
|
pub sql_projection: Projection,
|
|
|
|
|
2018-03-12 22:18:50 +00:00
|
|
|
/// If a query contains aggregates, we need to generate a nested subquery: an inner query
|
|
|
|
/// that returns our distinct variable bindings (and any `:with` vars), and an outer query
|
|
|
|
/// that applies aggregation. That's so we can put `DISTINCT` in the inner query and apply
|
|
|
|
/// aggregation afterwards -- `SELECT DISTINCT count(foo)` counts _then_ uniques, and we need
|
|
|
|
/// the opposite to implement Datalog distinct semantics.
|
|
|
|
/// If this is the case, `sql_projection` will be the outer query's projection list, and
|
|
|
|
/// `pre_aggregate_projection` will be the inner.
|
|
|
|
/// If the query doesn't use aggregation, this field will be `None`.
|
|
|
|
pub pre_aggregate_projection: Option<Projection>,
|
|
|
|
|
2017-03-06 22:40:10 +00:00
|
|
|
/// 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.
|
2020-01-14 15:46:21 +00:00
|
|
|
pub datalog_projector: Box<dyn Projector>,
|
2017-03-22 21:02:00 +00:00
|
|
|
|
|
|
|
/// True if this query requires the SQL query to include DISTINCT.
|
|
|
|
pub distinct: bool,
|
2018-03-12 22:18:50 +00:00
|
|
|
|
|
|
|
// A list of column names to use as a GROUP BY clause.
|
|
|
|
pub group_by_cols: Vec<GroupBy>,
|
2017-03-22 21:02:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl CombinedProjection {
|
2017-04-19 23:16:19 +00:00
|
|
|
fn flip_distinct_for_limit(mut self, limit: &Limit) -> Self {
|
|
|
|
if *limit == Limit::Fixed(1) {
|
2017-03-22 21:02:00 +00:00
|
|
|
self.distinct = false;
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
2017-03-06 22:40:10 +00:00
|
|
|
}
|
|
|
|
|
2018-05-04 19:56:00 +00:00
|
|
|
trait IsPull {
|
|
|
|
fn is_pull(&self) -> bool;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IsPull for Element {
|
|
|
|
fn is_pull(&self) -> bool {
|
2020-08-06 03:03:58 +00:00
|
|
|
matches!(*self, Element::Pull(_))
|
2018-05-04 19:56:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-06 22:40:10 +00:00
|
|
|
/// 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.
|
2020-01-14 15:46:21 +00:00
|
|
|
pub fn query_projection(
|
|
|
|
schema: &Schema,
|
|
|
|
query: &AlgebraicQuery,
|
|
|
|
) -> Result<Either<ConstantProjector, CombinedProjection>> {
|
2017-03-06 22:40:10 +00:00
|
|
|
use self::FindSpec::*;
|
|
|
|
|
2018-01-30 22:11:41 +00:00
|
|
|
let spec = query.find_spec.clone();
|
2018-03-06 17:01:20 +00:00
|
|
|
if query.is_fully_unit_bound() {
|
|
|
|
// Do a few gyrations to produce empty results of the right kind for the query.
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
let variables: BTreeSet<Variable> = spec
|
|
|
|
.columns()
|
2020-02-21 15:27:39 +00:00
|
|
|
.map(|e| match *e {
|
|
|
|
Element::Variable(ref var) | Element::Corresponding(ref var) => var.clone(),
|
2020-01-14 15:46:21 +00:00
|
|
|
|
|
|
|
// Pull expressions can never be fully bound.
|
|
|
|
// TODO: but the interior can be, in which case we
|
|
|
|
// can handle this and simply project.
|
2020-02-21 15:27:39 +00:00
|
|
|
Element::Pull(_) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
unreachable!();
|
|
|
|
}
|
2020-02-21 15:27:39 +00:00
|
|
|
Element::Aggregate(ref _agg) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
// TODO: static computation of aggregates, then
|
|
|
|
// implement the condition in `is_fully_bound`.
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect();
|
2018-03-06 17:01:20 +00:00
|
|
|
|
|
|
|
// TODO: error handling
|
|
|
|
let results = QueryOutput::from_constants(&spec, query.cc.value_bindings(&variables));
|
2020-01-14 15:46:21 +00:00
|
|
|
let f = Box::new(move || results.clone());
|
2018-03-06 17:01:20 +00:00
|
|
|
|
|
|
|
Ok(Either::Left(ConstantProjector::new(spec, f)))
|
|
|
|
} else if query.is_known_empty() {
|
2017-03-07 04:18:38 +00:00
|
|
|
// Do a few gyrations to produce empty results of the right kind for the query.
|
2018-01-30 22:11:41 +00:00
|
|
|
let empty = QueryOutput::empty_factory(&spec);
|
2018-03-06 17:01:20 +00:00
|
|
|
Ok(Either::Left(ConstantProjector::new(spec, empty)))
|
2017-03-07 04:18:38 +00:00
|
|
|
} else {
|
2018-01-30 22:11:41 +00:00
|
|
|
match *query.find_spec {
|
2017-03-07 04:18:38 +00:00
|
|
|
FindColl(ref element) => {
|
2018-03-12 22:18:50 +00:00
|
|
|
let elements = project_elements(1, iter::once(element), query)?;
|
2018-05-04 19:56:00 +00:00
|
|
|
if element.is_pull() {
|
|
|
|
CollTwoStagePullProjector::combine(spec, elements)
|
|
|
|
} else {
|
|
|
|
CollProjector::combine(spec, elements)
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
|
|
|
.map(|p| p.flip_distinct_for_limit(&query.limit))
|
|
|
|
}
|
2017-03-07 04:18:38 +00:00
|
|
|
|
|
|
|
FindScalar(ref element) => {
|
2018-03-12 22:18:50 +00:00
|
|
|
let elements = project_elements(1, iter::once(element), query)?;
|
2018-05-04 19:56:00 +00:00
|
|
|
if element.is_pull() {
|
|
|
|
ScalarTwoStagePullProjector::combine(schema, spec, elements)
|
|
|
|
} else {
|
|
|
|
ScalarProjector::combine(spec, elements)
|
|
|
|
}
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
2017-03-07 04:18:38 +00:00
|
|
|
|
|
|
|
FindRel(ref elements) => {
|
2018-05-04 19:56:00 +00:00
|
|
|
let is_pull = elements.iter().any(|e| e.is_pull());
|
2017-03-07 04:18:38 +00:00
|
|
|
let column_count = query.find_spec.expected_column_count();
|
2018-03-12 22:18:50 +00:00
|
|
|
let elements = project_elements(column_count, elements, query)?;
|
2018-05-04 19:56:00 +00:00
|
|
|
if is_pull {
|
|
|
|
RelTwoStagePullProjector::combine(spec, column_count, elements)
|
|
|
|
} else {
|
|
|
|
RelProjector::combine(spec, column_count, elements)
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
|
|
|
.map(|p| p.flip_distinct_for_limit(&query.limit))
|
|
|
|
}
|
2017-03-07 04:18:38 +00:00
|
|
|
|
|
|
|
FindTuple(ref elements) => {
|
2018-05-04 19:56:00 +00:00
|
|
|
let is_pull = elements.iter().any(|e| e.is_pull());
|
2017-03-07 04:18:38 +00:00
|
|
|
let column_count = query.find_spec.expected_column_count();
|
2018-03-12 22:18:50 +00:00
|
|
|
let elements = project_elements(column_count, elements, query)?;
|
2018-05-04 19:56:00 +00:00
|
|
|
if is_pull {
|
|
|
|
TupleTwoStagePullProjector::combine(spec, column_count, elements)
|
|
|
|
} else {
|
|
|
|
TupleProjector::combine(spec, column_count, elements)
|
|
|
|
}
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
.map(Either::Right)
|
2017-03-06 22:40:10 +00:00
|
|
|
}
|
2017-04-26 22:17:02 +00:00
|
|
|
}
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_into_tuple() {
|
|
|
|
let query_output = QueryOutput {
|
2020-01-14 15:46:21 +00:00
|
|
|
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)),
|
|
|
|
])),
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
};
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
assert_eq!(
|
|
|
|
query_output.clone().into_tuple().expect("into_tuple"),
|
|
|
|
Some((
|
|
|
|
Binding::Scalar(TypedValue::Long(0)),
|
|
|
|
Binding::Scalar(TypedValue::Long(2))
|
|
|
|
))
|
|
|
|
);
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
|
2020-08-06 03:03:58 +00:00
|
|
|
match query_output.into_tuple() {
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
Err(ProjectorError::UnexpectedResultsTupleLength(expected, got)) => {
|
|
|
|
assert_eq!((expected, got), (3, 2));
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
// This forces the result type.
|
2020-08-06 03:03:58 +00:00
|
|
|
Ok(Some((_, _, _))) => panic!("expected error"),
|
|
|
|
#[allow(clippy::wildcard_in_or_patterns)]
|
|
|
|
_ => panic!("expected error"),
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let query_output = QueryOutput {
|
2020-01-14 15:46:21 +00:00
|
|
|
spec: Rc::new(FindSpec::FindTuple(vec![
|
|
|
|
Element::Variable(Variable::from_valid_name("?x")),
|
|
|
|
Element::Variable(Variable::from_valid_name("?y")),
|
|
|
|
])),
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
results: QueryResults::Tuple(None),
|
|
|
|
};
|
|
|
|
|
|
|
|
match query_output.clone().into_tuple() {
|
2020-01-14 15:46:21 +00:00
|
|
|
Ok(None) => {}
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
// This forces the result type.
|
2020-08-06 03:03:58 +00:00
|
|
|
Ok(Some((_, _))) => panic!("expected error"),
|
|
|
|
#[allow(clippy::wildcard_in_or_patterns)]
|
|
|
|
_ => panic!("expected error"),
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
}
|
|
|
|
|
2020-08-06 03:03:58 +00:00
|
|
|
match query_output.into_tuple() {
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
Err(ProjectorError::UnexpectedResultsTupleLength(expected, got)) => {
|
|
|
|
assert_eq!((expected, got), (3, 2));
|
2020-01-14 15:46:21 +00:00
|
|
|
}
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
// This forces the result type.
|
2020-08-06 03:03:58 +00:00
|
|
|
Ok(Some((_, _, _))) => panic!("expected error"),
|
|
|
|
#[allow(clippy::wildcard_in_or_patterns)]
|
|
|
|
_ => panic!("expected error"),
|
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.
2018-06-28 19:15:26 +00:00
|
|
|
}
|
|
|
|
}
|