Add 'Binding', a structured value type to return from queries. (#657) r=nalexander
Bump to 0.7: breaking change.
This commit is contained in:
parent
1818e0b98e
commit
a2e13f624c
16 changed files with 396 additions and 166 deletions
|
@ -12,7 +12,7 @@ authors = [
|
|||
"Thom Chiovoloni <tchiovoloni@mozilla.com>",
|
||||
]
|
||||
name = "mentat"
|
||||
version = "0.6.2"
|
||||
version = "0.7.0"
|
||||
build = "build/version.rs"
|
||||
|
||||
[features]
|
||||
|
|
|
@ -6,6 +6,7 @@ workspace = ".."
|
|||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
enum-set = { git = "https://github.com/rnewman/enum-set" }
|
||||
indexmap = "1"
|
||||
lazy_static = "0.2"
|
||||
num = "0.1"
|
||||
ordered-float = { version = "0.5", features = ["serde"] }
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
extern crate chrono;
|
||||
extern crate enum_set;
|
||||
extern crate indexmap;
|
||||
extern crate ordered_float;
|
||||
extern crate uuid;
|
||||
extern crate serde;
|
||||
|
@ -57,6 +58,7 @@ pub use types::{
|
|||
Entid,
|
||||
KnownEntid,
|
||||
TypedValue,
|
||||
Binding,
|
||||
ValueType,
|
||||
ValueTypeTag,
|
||||
now,
|
||||
|
|
|
@ -22,6 +22,10 @@ use ::chrono::{
|
|||
Timelike, // For truncation.
|
||||
};
|
||||
|
||||
use ::indexmap::{
|
||||
IndexMap,
|
||||
};
|
||||
|
||||
use ::edn::{
|
||||
self,
|
||||
FromMicros,
|
||||
|
@ -49,6 +53,12 @@ impl From<KnownEntid> for Entid {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<KnownEntid> for Binding {
|
||||
fn from(k: KnownEntid) -> Binding {
|
||||
Binding::Scalar(TypedValue::Ref(k.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KnownEntid> for TypedValue {
|
||||
fn from(k: KnownEntid) -> TypedValue {
|
||||
TypedValue::Ref(k.0)
|
||||
|
@ -172,6 +182,90 @@ pub enum TypedValue {
|
|||
Uuid(Uuid), // It's only 128 bits, so this should be acceptable to clone.
|
||||
}
|
||||
|
||||
/// The values bound in a query specification can be:
|
||||
///
|
||||
/// * Vecs of structured values, for multi-valued component attributes or nested expressions.
|
||||
/// * Single structured values, for single-valued component attributes or nested expressions.
|
||||
/// * Single typed values, for simple attributes.
|
||||
///
|
||||
/// The `Binding` enum defines these three options.
|
||||
///
|
||||
/// Datomic also supports structured inputs; at present Mentat does not, but this type
|
||||
/// would also serve that purpose.
|
||||
///
|
||||
/// Note that maps are not ordered, and so `Binding` is neither `Ord` nor `PartialOrd`.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Binding {
|
||||
Scalar(TypedValue),
|
||||
Vec(Rc<Vec<Binding>>),
|
||||
Map(Rc<StructuredMap>),
|
||||
}
|
||||
|
||||
impl From<TypedValue> for Binding {
|
||||
fn from(src: TypedValue) -> Self {
|
||||
Binding::Scalar(src)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a> From<&'a str> for Binding {
|
||||
fn from(value: &'a str) -> Binding {
|
||||
Binding::Scalar(TypedValue::String(Rc::new(value.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Binding {
|
||||
fn from(value: String) -> Binding {
|
||||
Binding::Scalar(TypedValue::String(Rc::new(value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Binding {
|
||||
pub fn val(self) -> Option<TypedValue> {
|
||||
match self {
|
||||
Binding::Scalar(v) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A pull expression expands a binding into a structure. The returned structure
|
||||
/// associates attributes named in the input or retrieved from the store with values.
|
||||
/// This association is a `StructuredMap`.
|
||||
///
|
||||
/// Note that 'attributes' in Datomic's case can mean:
|
||||
/// - Reversed attribute keywords (:artist/_country).
|
||||
/// - An alias using `:as` (:artist/name :as "Band name").
|
||||
///
|
||||
/// We entirely support the former, and partially support the latter -- you can alias
|
||||
/// using a different keyword only.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct StructuredMap(IndexMap<Rc<NamespacedKeyword>, Binding>);
|
||||
|
||||
impl Binding {
|
||||
/// Returns true if the provided type is `Some` and matches this value's type, or if the
|
||||
/// provided type is `None`.
|
||||
#[inline]
|
||||
pub fn is_congruent_with<T: Into<Option<ValueType>>>(&self, t: T) -> bool {
|
||||
t.into().map_or(true, |x| self.matches_type(x))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn matches_type(&self, t: ValueType) -> bool {
|
||||
self.value_type() == Some(t)
|
||||
}
|
||||
|
||||
pub fn value_type(&self) -> Option<ValueType> {
|
||||
match self {
|
||||
&Binding::Scalar(ref v) => Some(v.value_type()),
|
||||
|
||||
&Binding::Map(_) => None,
|
||||
&Binding::Vec(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl TypedValue {
|
||||
/// Returns true if the provided type is `Some` and matches this value's type, or if the
|
||||
/// provided type is `None`.
|
||||
|
@ -382,6 +476,85 @@ impl TypedValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl Binding {
|
||||
pub fn into_known_entid(self) -> Option<KnownEntid> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::Ref(v)) => Some(KnownEntid(v)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_entid(self) -> Option<Entid> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::Ref(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_kw(self) -> Option<Rc<NamespacedKeyword>> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::Keyword(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_boolean(self) -> Option<bool> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::Boolean(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_long(self) -> Option<i64> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::Long(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_double(self) -> Option<f64> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::Double(v)) => Some(v.into_inner()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_instant(self) -> Option<DateTime<Utc>> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::Instant(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_timestamp(self) -> Option<i64> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::Instant(v)) => Some(v.timestamp()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> Option<Rc<String>> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::String(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_uuid(self) -> Option<Uuid> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::Uuid(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_uuid_string(self) -> Option<String> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::Uuid(v)) => Some(v.hyphenated().to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_typed_value() {
|
||||
assert!(TypedValue::Boolean(false).is_congruent_with(None));
|
||||
|
|
|
@ -34,6 +34,7 @@ use rusqlite::{
|
|||
};
|
||||
|
||||
use mentat_core::{
|
||||
Binding,
|
||||
TypedValue,
|
||||
ValueType,
|
||||
ValueTypeTag,
|
||||
|
@ -84,6 +85,7 @@ pub use project::{
|
|||
|
||||
pub use relresult::{
|
||||
RelResult,
|
||||
StructuredRelResult,
|
||||
};
|
||||
|
||||
use errors::{
|
||||
|
@ -99,10 +101,10 @@ pub struct QueryOutput {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum QueryResults {
|
||||
Scalar(Option<TypedValue>),
|
||||
Tuple(Option<Vec<TypedValue>>),
|
||||
Coll(Vec<TypedValue>),
|
||||
Rel(RelResult),
|
||||
Scalar(Option<Binding>),
|
||||
Tuple(Option<Vec<Binding>>),
|
||||
Coll(Vec<Binding>),
|
||||
Rel(RelResult<Binding>),
|
||||
}
|
||||
|
||||
impl From<QueryOutput> for QueryResults {
|
||||
|
@ -153,7 +155,9 @@ impl QueryOutput {
|
|||
match &**spec {
|
||||
&FindScalar(Element::Variable(ref var)) |
|
||||
&FindScalar(Element::Corresponding(ref var)) => {
|
||||
let val = bindings.get(var).cloned();
|
||||
let val = bindings.get(var)
|
||||
.cloned()
|
||||
.map(|v| v.into());
|
||||
QueryResults::Scalar(val)
|
||||
},
|
||||
&FindScalar(Element::Aggregate(ref _agg)) => {
|
||||
|
@ -165,7 +169,10 @@ impl QueryOutput {
|
|||
.map(|e| match e {
|
||||
&Element::Variable(ref var) |
|
||||
&Element::Corresponding(ref var) => {
|
||||
bindings.get(var).cloned().expect("every var to have a binding")
|
||||
bindings.get(var)
|
||||
.cloned()
|
||||
.expect("every var to have a binding")
|
||||
.into()
|
||||
},
|
||||
&Element::Aggregate(ref _agg) => {
|
||||
// TODO: static computation of aggregates, then
|
||||
|
@ -178,7 +185,10 @@ impl QueryOutput {
|
|||
},
|
||||
&FindColl(Element::Variable(ref var)) |
|
||||
&FindColl(Element::Corresponding(ref var)) => {
|
||||
let val = bindings.get(var).cloned().expect("every var to have a binding");
|
||||
let val = bindings.get(var)
|
||||
.cloned()
|
||||
.expect("every var to have a binding")
|
||||
.into();
|
||||
QueryResults::Coll(vec![val])
|
||||
},
|
||||
&FindColl(Element::Aggregate(ref _agg)) => {
|
||||
|
@ -193,7 +203,10 @@ impl QueryOutput {
|
|||
let values = elements.iter().map(|e| match e {
|
||||
&Element::Variable(ref var) |
|
||||
&Element::Corresponding(ref var) => {
|
||||
bindings.get(var).cloned().expect("every var to have a binding")
|
||||
bindings.get(var)
|
||||
.cloned()
|
||||
.expect("every var to have a binding")
|
||||
.into()
|
||||
},
|
||||
&Element::Aggregate(ref _agg) => {
|
||||
// TODO: static computation of aggregates, then
|
||||
|
@ -206,19 +219,19 @@ impl QueryOutput {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_scalar(self) -> Result<Option<TypedValue>> {
|
||||
pub fn into_scalar(self) -> Result<Option<Binding>> {
|
||||
self.results.into_scalar()
|
||||
}
|
||||
|
||||
pub fn into_coll(self) -> Result<Vec<TypedValue>> {
|
||||
pub fn into_coll(self) -> Result<Vec<Binding>> {
|
||||
self.results.into_coll()
|
||||
}
|
||||
|
||||
pub fn into_tuple(self) -> Result<Option<Vec<TypedValue>>> {
|
||||
pub fn into_tuple(self) -> Result<Option<Vec<Binding>>> {
|
||||
self.results.into_tuple()
|
||||
}
|
||||
|
||||
pub fn into_rel(self) -> Result<RelResult> {
|
||||
pub fn into_rel(self) -> Result<RelResult<Binding>> {
|
||||
self.results.into_rel()
|
||||
}
|
||||
}
|
||||
|
@ -244,7 +257,7 @@ impl QueryResults {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_scalar(self) -> Result<Option<TypedValue>> {
|
||||
pub fn into_scalar(self) -> Result<Option<Binding>> {
|
||||
match self {
|
||||
QueryResults::Scalar(o) => Ok(o),
|
||||
QueryResults::Coll(_) => bail!(ErrorKind::UnexpectedResultsType("coll", "scalar")),
|
||||
|
@ -253,7 +266,7 @@ impl QueryResults {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_coll(self) -> Result<Vec<TypedValue>> {
|
||||
pub fn into_coll(self) -> Result<Vec<Binding>> {
|
||||
match self {
|
||||
QueryResults::Scalar(_) => bail!(ErrorKind::UnexpectedResultsType("scalar", "coll")),
|
||||
QueryResults::Coll(c) => Ok(c),
|
||||
|
@ -262,7 +275,7 @@ impl QueryResults {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_tuple(self) -> Result<Option<Vec<TypedValue>>> {
|
||||
pub fn into_tuple(self) -> Result<Option<Vec<Binding>>> {
|
||||
match self {
|
||||
QueryResults::Scalar(_) => bail!(ErrorKind::UnexpectedResultsType("scalar", "tuple")),
|
||||
QueryResults::Coll(_) => bail!(ErrorKind::UnexpectedResultsType("coll", "tuple")),
|
||||
|
@ -271,7 +284,7 @@ impl QueryResults {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_rel(self) -> Result<RelResult> {
|
||||
pub fn into_rel(self) -> Result<RelResult<Binding>> {
|
||||
match self {
|
||||
QueryResults::Scalar(_) => bail!(ErrorKind::UnexpectedResultsType("scalar", "rel")),
|
||||
QueryResults::Coll(_) => bail!(ErrorKind::UnexpectedResultsType("coll", "rel")),
|
||||
|
@ -302,18 +315,22 @@ impl TypedIndex {
|
|||
///
|
||||
/// This function will return a runtime error if the type tag is unknown, or the value is
|
||||
/// otherwise not convertible by the DB layer.
|
||||
fn lookup<'a, 'stmt>(&self, row: &Row<'a, 'stmt>) -> Result<TypedValue> {
|
||||
fn lookup<'a, 'stmt>(&self, row: &Row<'a, 'stmt>) -> Result<Binding> {
|
||||
use TypedIndex::*;
|
||||
|
||||
match self {
|
||||
&Known(value_index, value_type) => {
|
||||
let v: rusqlite::types::Value = row.get(value_index);
|
||||
TypedValue::from_sql_value_pair(v, value_type).map_err(|e| e.into())
|
||||
TypedValue::from_sql_value_pair(v, value_type)
|
||||
.map(|v| v.into())
|
||||
.map_err(|e| e.into())
|
||||
},
|
||||
&Unknown(value_index, type_index) => {
|
||||
let v: rusqlite::types::Value = row.get(value_index);
|
||||
let value_type_tag: i32 = row.get(type_index);
|
||||
TypedValue::from_sql_value_pair(v, value_type_tag).map_err(|e| e.into())
|
||||
TypedValue::from_sql_value_pair(v, value_type_tag)
|
||||
.map(|v| v.into())
|
||||
.map_err(|e| e.into())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -422,7 +439,7 @@ impl TupleProjector {
|
|||
}
|
||||
|
||||
// This is exactly the same as for rel.
|
||||
fn collect_bindings<'a, 'stmt>(&self, row: Row<'a, 'stmt>) -> Result<Vec<TypedValue>> {
|
||||
fn collect_bindings<'a, 'stmt>(&self, row: Row<'a, 'stmt>) -> Result<Vec<Binding>> {
|
||||
// There will be at least as many SQL columns as Datalog columns.
|
||||
// gte 'cos we might be querying extra columns for ordering.
|
||||
// The templates will take care of ignoring columns.
|
||||
|
@ -430,7 +447,7 @@ impl TupleProjector {
|
|||
self.templates
|
||||
.iter()
|
||||
.map(|ti| ti.lookup(&row))
|
||||
.collect::<Result<Vec<TypedValue>>>()
|
||||
.collect::<Result<Vec<Binding>>>()
|
||||
}
|
||||
|
||||
fn combine(spec: Rc<FindSpec>, column_count: usize, elements: ProjectedElements) -> Result<CombinedProjection> {
|
||||
|
@ -485,7 +502,7 @@ impl RelProjector {
|
|||
}
|
||||
}
|
||||
|
||||
fn collect_bindings_into<'a, 'stmt, 'out>(&self, row: Row<'a, 'stmt>, out: &mut Vec<TypedValue>) -> Result<()> {
|
||||
fn collect_bindings_into<'a, 'stmt, 'out>(&self, row: Row<'a, 'stmt>, out: &mut Vec<Binding>) -> Result<()> {
|
||||
// There will be at least as many SQL columns as Datalog columns.
|
||||
// gte 'cos we might be querying extra columns for ordering.
|
||||
// The templates will take care of ignoring columns.
|
||||
|
@ -580,7 +597,7 @@ impl CollProjector {
|
|||
|
||||
impl Projector for CollProjector {
|
||||
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryOutput> {
|
||||
let mut out: Vec<TypedValue> = vec![];
|
||||
let mut out: Vec<_> = vec![];
|
||||
while let Some(r) = rows.next() {
|
||||
let row = r?;
|
||||
let binding = self.template.lookup(&row)?;
|
||||
|
@ -664,7 +681,7 @@ pub fn query_projection(query: &AlgebraicQuery) -> Result<Either<ConstantProject
|
|||
|
||||
// TODO: error handling
|
||||
let results = QueryOutput::from_constants(&spec, query.cc.value_bindings(&variables));
|
||||
let f = Box::new(move || {results.clone()});
|
||||
let f = Box::new(move || { results.clone() });
|
||||
|
||||
Ok(Either::Left(ConstantProjector::new(spec, f)))
|
||||
} else if query.is_known_empty() {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
use mentat_core::{
|
||||
Binding,
|
||||
TypedValue,
|
||||
};
|
||||
|
||||
|
@ -31,13 +32,15 @@ use mentat_core::{
|
|||
/// - By consuming the results using `into_iter`. This allocates short-lived vectors,
|
||||
/// but gives you ownership of the enclosed `TypedValue`s.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RelResult {
|
||||
pub struct RelResult<T> {
|
||||
pub width: usize,
|
||||
pub values: Vec<TypedValue>,
|
||||
pub values: Vec<T>,
|
||||
}
|
||||
|
||||
impl RelResult {
|
||||
pub fn empty(width: usize) -> RelResult {
|
||||
pub type StructuredRelResult = RelResult<Binding>;
|
||||
|
||||
impl<T> RelResult<T> {
|
||||
pub fn empty(width: usize) -> RelResult<T> {
|
||||
RelResult {
|
||||
width: width,
|
||||
values: Vec::new(),
|
||||
|
@ -52,12 +55,12 @@ impl RelResult {
|
|||
self.values.len() / self.width
|
||||
}
|
||||
|
||||
pub fn rows(&self) -> ::std::slice::Chunks<TypedValue> {
|
||||
pub fn rows(&self) -> ::std::slice::Chunks<T> {
|
||||
// TODO: Nightly-only API `exact_chunks`. #47115.
|
||||
self.values.chunks(self.width)
|
||||
}
|
||||
|
||||
pub fn row(&self, index: usize) -> Option<&[TypedValue]> {
|
||||
pub fn row(&self, index: usize) -> Option<&[T]> {
|
||||
let end = self.width * (index + 1);
|
||||
if end > self.values.len() {
|
||||
None
|
||||
|
@ -70,15 +73,15 @@ impl RelResult {
|
|||
|
||||
#[test]
|
||||
fn test_rel_result() {
|
||||
let empty = RelResult::empty(3);
|
||||
let unit = RelResult {
|
||||
let empty = StructuredRelResult::empty(3);
|
||||
let unit = StructuredRelResult {
|
||||
width: 1,
|
||||
values: vec![TypedValue::Long(5)],
|
||||
values: vec![TypedValue::Long(5).into()],
|
||||
};
|
||||
let two_by_two = RelResult {
|
||||
let two_by_two = StructuredRelResult {
|
||||
width: 2,
|
||||
values: vec![TypedValue::Long(5), TypedValue::Boolean(true),
|
||||
TypedValue::Long(-2), TypedValue::Boolean(false)],
|
||||
values: vec![TypedValue::Long(5).into(), TypedValue::Boolean(true).into(),
|
||||
TypedValue::Long(-2).into(), TypedValue::Boolean(false).into()],
|
||||
};
|
||||
|
||||
assert!(empty.is_empty());
|
||||
|
@ -93,18 +96,18 @@ fn test_rel_result() {
|
|||
assert_eq!(unit.row(1), None);
|
||||
assert_eq!(two_by_two.row(2), None);
|
||||
|
||||
assert_eq!(unit.row(0), Some(vec![TypedValue::Long(5)].as_slice()));
|
||||
assert_eq!(two_by_two.row(0), Some(vec![TypedValue::Long(5), TypedValue::Boolean(true)].as_slice()));
|
||||
assert_eq!(two_by_two.row(1), Some(vec![TypedValue::Long(-2), TypedValue::Boolean(false)].as_slice()));
|
||||
assert_eq!(unit.row(0), Some(vec![TypedValue::Long(5).into()].as_slice()));
|
||||
assert_eq!(two_by_two.row(0), Some(vec![TypedValue::Long(5).into(), TypedValue::Boolean(true).into()].as_slice()));
|
||||
assert_eq!(two_by_two.row(1), Some(vec![TypedValue::Long(-2).into(), TypedValue::Boolean(false).into()].as_slice()));
|
||||
|
||||
let mut rr = two_by_two.rows();
|
||||
assert_eq!(rr.next(), Some(vec![TypedValue::Long(5), TypedValue::Boolean(true)].as_slice()));
|
||||
assert_eq!(rr.next(), Some(vec![TypedValue::Long(-2), TypedValue::Boolean(false)].as_slice()));
|
||||
assert_eq!(rr.next(), Some(vec![TypedValue::Long(5).into(), TypedValue::Boolean(true).into()].as_slice()));
|
||||
assert_eq!(rr.next(), Some(vec![TypedValue::Long(-2).into(), TypedValue::Boolean(false).into()].as_slice()));
|
||||
assert_eq!(rr.next(), None);
|
||||
}
|
||||
|
||||
// Primarily for testing.
|
||||
impl From<Vec<Vec<TypedValue>>> for RelResult {
|
||||
impl From<Vec<Vec<TypedValue>>> for RelResult<Binding> {
|
||||
fn from(src: Vec<Vec<TypedValue>>) -> Self {
|
||||
if src.is_empty() {
|
||||
RelResult::empty(0)
|
||||
|
@ -112,23 +115,23 @@ impl From<Vec<Vec<TypedValue>>> for RelResult {
|
|||
let width = src.get(0).map(|r| r.len()).unwrap_or(0);
|
||||
RelResult {
|
||||
width: width,
|
||||
values: src.into_iter().flat_map(|r| r.into_iter()).collect(),
|
||||
values: src.into_iter().flat_map(|r| r.into_iter().map(|v| v.into())).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubvecIntoIterator {
|
||||
pub struct SubvecIntoIterator<T> {
|
||||
width: usize,
|
||||
values: ::std::vec::IntoIter<TypedValue>,
|
||||
values: ::std::vec::IntoIter<T>,
|
||||
}
|
||||
|
||||
impl Iterator for SubvecIntoIterator {
|
||||
impl<T> Iterator for SubvecIntoIterator<T> {
|
||||
// TODO: this is a good opportunity to use `SmallVec` instead: most queries
|
||||
// return a handful of columns.
|
||||
type Item = Vec<TypedValue>;
|
||||
fn next(&mut self) -> Option<Vec<TypedValue>> {
|
||||
let result: Vec<TypedValue> = (&mut self.values).take(self.width).collect();
|
||||
type Item = Vec<T>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let result: Vec<_> = (&mut self.values).take(self.width).collect();
|
||||
if result.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
@ -137,9 +140,9 @@ impl Iterator for SubvecIntoIterator {
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for RelResult {
|
||||
type Item = Vec<TypedValue>;
|
||||
type IntoIter = SubvecIntoIterator;
|
||||
impl<T> IntoIterator for RelResult<T> {
|
||||
type Item = Vec<T>;
|
||||
type IntoIter = SubvecIntoIterator<T>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
SubvecIntoIterator {
|
||||
|
|
|
@ -735,7 +735,7 @@ fn test_ground_scalar() {
|
|||
let constant = translate_to_constant(&schema, query);
|
||||
assert_eq!(constant.project_without_rows().unwrap()
|
||||
.into_scalar().unwrap(),
|
||||
Some(TypedValue::typed_string("yyy")));
|
||||
Some(TypedValue::typed_string("yyy").into()));
|
||||
|
||||
// Verify that we accept bound input constants.
|
||||
let query = r#"[:find ?x . :in ?v :where [(ground ?v) ?x]]"#;
|
||||
|
@ -743,7 +743,7 @@ fn test_ground_scalar() {
|
|||
let constant = translate_with_inputs_to_constant(&schema, query, inputs);
|
||||
assert_eq!(constant.project_without_rows().unwrap()
|
||||
.into_scalar().unwrap(),
|
||||
Some(TypedValue::typed_string("aaa")));
|
||||
Some(TypedValue::typed_string("aaa").into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -765,7 +765,7 @@ fn test_ground_tuple() {
|
|||
let constant = translate_with_inputs_to_constant(&schema, query, inputs);
|
||||
assert_eq!(constant.project_without_rows().unwrap()
|
||||
.into_tuple().unwrap(),
|
||||
Some(vec![TypedValue::Long(2), TypedValue::typed_string("aaa")]));
|
||||
Some(vec![TypedValue::Long(2).into(), TypedValue::typed_string("aaa").into()]));
|
||||
|
||||
// TODO: treat 2 as an input variable that could be bound late, rather than eagerly binding it.
|
||||
// In that case the query wouldn't be constant, and would look more like:
|
||||
|
|
19
src/conn.rs
19
src/conn.rs
|
@ -923,6 +923,7 @@ mod tests {
|
|||
|
||||
use mentat_core::{
|
||||
CachedAttributes,
|
||||
Binding,
|
||||
TypedValue,
|
||||
};
|
||||
|
||||
|
@ -1038,7 +1039,7 @@ mod tests {
|
|||
|
||||
let during = in_progress.q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None)
|
||||
.expect("query succeeded");
|
||||
assert_eq!(during.results, QueryResults::Scalar(Some(TypedValue::Ref(one))));
|
||||
assert_eq!(during.results, QueryResults::Scalar(Some(TypedValue::Ref(one).into())));
|
||||
|
||||
let report = in_progress.transact(t2).expect("t2 succeeded");
|
||||
in_progress.commit().expect("commit succeeded");
|
||||
|
@ -1083,10 +1084,10 @@ mod tests {
|
|||
values).expect("prepare succeeded");
|
||||
|
||||
let yeses = prepared.run(None).expect("result");
|
||||
assert_eq!(yeses.results, QueryResults::Coll(vec![TypedValue::Ref(yes)]));
|
||||
assert_eq!(yeses.results, QueryResults::Coll(vec![TypedValue::Ref(yes).into()]));
|
||||
|
||||
let yeses_again = prepared.run(None).expect("result");
|
||||
assert_eq!(yeses_again.results, QueryResults::Coll(vec![TypedValue::Ref(yes)]));
|
||||
assert_eq!(yeses_again.results, QueryResults::Coll(vec![TypedValue::Ref(yes).into()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1119,7 +1120,7 @@ mod tests {
|
|||
let during = in_progress.q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None)
|
||||
.expect("query succeeded");
|
||||
|
||||
assert_eq!(during.results, QueryResults::Scalar(Some(TypedValue::Ref(one))));
|
||||
assert_eq!(during.results, QueryResults::Scalar(Some(TypedValue::Ref(one).into())));
|
||||
|
||||
// And we can do direct lookup, too.
|
||||
let kw = in_progress.lookup_value_for_attribute(one, &edn::NamespacedKeyword::new("db", "ident"))
|
||||
|
@ -1233,7 +1234,7 @@ mod tests {
|
|||
let entities = conn.q_once(&sqlite, r#"[:find ?e . :where [?e :foo/bar 400]]"#, None).expect("Expected query to work").into_scalar().expect("expected rel results");
|
||||
let first = entities.expect("expected a result");
|
||||
let entid = match first {
|
||||
TypedValue::Ref(entid) => entid,
|
||||
Binding::Scalar(TypedValue::Ref(entid)) => entid,
|
||||
x => panic!("expected Some(Ref), got {:?}", x),
|
||||
};
|
||||
|
||||
|
@ -1279,7 +1280,7 @@ mod tests {
|
|||
let mut ip = conn.begin_transaction(&mut sqlite).expect("began");
|
||||
|
||||
let ident = ip.q_once(query.as_str(), None).into_scalar_result().expect("query");
|
||||
assert_eq!(ident, Some(TypedValue::typed_ns_keyword("db.type", "string")));
|
||||
assert_eq!(ident, Some(TypedValue::typed_ns_keyword("db.type", "string").into()));
|
||||
|
||||
let start = time::PreciseTime::now();
|
||||
ip.q_once(query.as_str(), None).into_scalar_result().expect("query");
|
||||
|
@ -1292,7 +1293,7 @@ mod tests {
|
|||
assert!(ip.cache.is_attribute_cached_forward(db_ident));
|
||||
|
||||
let ident = ip.q_once(query.as_str(), None).into_scalar_result().expect("query");
|
||||
assert_eq!(ident, Some(TypedValue::typed_ns_keyword("db.type", "string")));
|
||||
assert_eq!(ident, Some(TypedValue::typed_ns_keyword("db.type", "string").into()));
|
||||
|
||||
let start = time::PreciseTime::now();
|
||||
ip.q_once(query.as_str(), None).into_scalar_result().expect("query");
|
||||
|
@ -1309,7 +1310,7 @@ mod tests {
|
|||
let mut ip = conn.begin_transaction(&mut sqlite).expect("began");
|
||||
|
||||
let ident = ip.q_once(query.as_str(), None).into_scalar_result().expect("query");
|
||||
assert_eq!(ident, Some(TypedValue::typed_ns_keyword("db.type", "string")));
|
||||
assert_eq!(ident, Some(TypedValue::typed_ns_keyword("db.type", "string").into()));
|
||||
ip.cache(&kw!(:db/ident), CacheDirection::Forward, CacheAction::Register).expect("registered");
|
||||
ip.cache(&kw!(:db/valueType), CacheDirection::Forward, CacheAction::Register).expect("registered");
|
||||
|
||||
|
@ -1344,7 +1345,7 @@ mod tests {
|
|||
[?neighborhood :neighborhood/district ?d]
|
||||
[?d :district/name ?district]]"#;
|
||||
let hood = "Beacon Hill";
|
||||
let inputs = QueryInputs::with_value_sequence(vec![(var!(?hood), TypedValue::typed_string(hood))]);
|
||||
let inputs = QueryInputs::with_value_sequence(vec![(var!(?hood), TypedValue::typed_string(hood).into())]);
|
||||
let mut prepared = in_progress.q_prepare(query, inputs)
|
||||
.expect("prepared");
|
||||
match &prepared {
|
||||
|
|
|
@ -41,6 +41,7 @@ pub use mentat_core::{
|
|||
KnownEntid,
|
||||
NamespacedKeyword,
|
||||
Schema,
|
||||
Binding,
|
||||
TypedValue,
|
||||
Uuid,
|
||||
Utc,
|
||||
|
|
27
src/query.rs
27
src/query.rs
|
@ -18,6 +18,7 @@ use mentat_core::{
|
|||
HasSchema,
|
||||
KnownEntid,
|
||||
Schema,
|
||||
Binding,
|
||||
TypedValue,
|
||||
};
|
||||
|
||||
|
@ -117,26 +118,26 @@ impl<'sqlite> PreparedQuery<'sqlite> {
|
|||
}
|
||||
|
||||
pub trait IntoResult {
|
||||
fn into_scalar_result(self) -> Result<Option<TypedValue>>;
|
||||
fn into_coll_result(self) -> Result<Vec<TypedValue>>;
|
||||
fn into_tuple_result(self) -> Result<Option<Vec<TypedValue>>>;
|
||||
fn into_rel_result(self) -> Result<RelResult>;
|
||||
fn into_scalar_result(self) -> Result<Option<Binding>>;
|
||||
fn into_coll_result(self) -> Result<Vec<Binding>>;
|
||||
fn into_tuple_result(self) -> Result<Option<Vec<Binding>>>;
|
||||
fn into_rel_result(self) -> Result<RelResult<Binding>>;
|
||||
}
|
||||
|
||||
impl IntoResult for QueryExecutionResult {
|
||||
fn into_scalar_result(self) -> Result<Option<TypedValue>> {
|
||||
fn into_scalar_result(self) -> Result<Option<Binding>> {
|
||||
self?.into_scalar().map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn into_coll_result(self) -> Result<Vec<TypedValue>> {
|
||||
fn into_coll_result(self) -> Result<Vec<Binding>> {
|
||||
self?.into_coll().map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn into_tuple_result(self) -> Result<Option<Vec<TypedValue>>> {
|
||||
fn into_tuple_result(self) -> Result<Option<Vec<Binding>>> {
|
||||
self?.into_tuple().map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn into_rel_result(self) -> Result<RelResult> {
|
||||
fn into_rel_result(self) -> Result<RelResult<Binding>> {
|
||||
self?.into_rel().map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
@ -232,7 +233,10 @@ pub fn lookup_value<'sqlite, 'schema, 'cache, E, A>
|
|||
if known.is_attribute_cached_forward(attrid) {
|
||||
Ok(known.get_value_for_entid(known.schema, attrid, entid).cloned())
|
||||
} else {
|
||||
fetch_values(sqlite, known, entid, attrid, true).into_scalar_result()
|
||||
fetch_values(sqlite, known, entid, attrid, true)
|
||||
.into_scalar_result()
|
||||
// Safe to unwrap: we never retrieve structure.
|
||||
.map(|r| r.map(|v| v.val().unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,7 +255,10 @@ pub fn lookup_values<'sqlite, E, A>
|
|||
.cloned()
|
||||
.unwrap_or_else(|| vec![]))
|
||||
} else {
|
||||
fetch_values(sqlite, known, entid, attrid, false).into_coll_result()
|
||||
fetch_values(sqlite, known, entid, attrid, false)
|
||||
.into_coll_result()
|
||||
// Safe to unwrap: we never retrieve structure.
|
||||
.map(|v| v.into_iter().map(|x| x.val().unwrap()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ use std::collections::{
|
|||
use mentat_core::{
|
||||
Entid,
|
||||
NamespacedKeyword,
|
||||
Binding,
|
||||
TypedValue,
|
||||
ValueType,
|
||||
};
|
||||
|
@ -86,22 +87,22 @@ impl<'a> QueryBuilder<'a> {
|
|||
read.q_once(&self.sql, query_inputs)
|
||||
}
|
||||
|
||||
pub fn execute_scalar(&mut self) -> Result<Option<TypedValue>> {
|
||||
pub fn execute_scalar(&mut self) -> Result<Option<Binding>> {
|
||||
let results = self.execute()?;
|
||||
results.into_scalar().map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn execute_coll(&mut self) -> Result<Vec<TypedValue>> {
|
||||
pub fn execute_coll(&mut self) -> Result<Vec<Binding>> {
|
||||
let results = self.execute()?;
|
||||
results.into_coll().map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn execute_tuple(&mut self) -> Result<Option<Vec<TypedValue>>> {
|
||||
pub fn execute_tuple(&mut self) -> Result<Option<Vec<Binding>>> {
|
||||
let results = self.execute()?;
|
||||
results.into_tuple().map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn execute_rel(&mut self) -> Result<RelResult> {
|
||||
pub fn execute_rel(&mut self) -> Result<RelResult<Binding>> {
|
||||
let results = self.execute()?;
|
||||
results.into_rel().map_err(|e| e.into())
|
||||
}
|
||||
|
@ -286,15 +287,15 @@ mod test {
|
|||
|
||||
let n_yes = report.tempids.get("n").expect("found it").clone();
|
||||
|
||||
let results: Vec<TypedValue> = QueryBuilder::new(&mut store, r#"[:find [?x, ?i]
|
||||
:in ?v ?i
|
||||
:where [?x :foo/boolean ?v]
|
||||
[?x :foo/long ?i]]"#)
|
||||
let results: Vec<_> = QueryBuilder::new(&mut store, r#"[:find [?x, ?i]
|
||||
:in ?v ?i
|
||||
:where [?x :foo/boolean ?v]
|
||||
[?x :foo/long ?i]]"#)
|
||||
.bind_value("?v", true)
|
||||
.bind_long("?i", 27)
|
||||
.execute_tuple().expect("TupleResult").unwrap_or(vec![]);
|
||||
let entid = TypedValue::Ref(n_yes.clone());
|
||||
let long_val = TypedValue::Long(27);
|
||||
let entid = TypedValue::Ref(n_yes.clone()).into();
|
||||
let long_val = TypedValue::Long(27).into();
|
||||
|
||||
assert_eq!(results, vec![entid, long_val]);
|
||||
}
|
||||
|
|
|
@ -110,6 +110,7 @@ use ::{
|
|||
HasSchema,
|
||||
IntoResult,
|
||||
NamespacedKeyword,
|
||||
Binding,
|
||||
TypedValue,
|
||||
ValueType,
|
||||
};
|
||||
|
@ -172,6 +173,7 @@ pub struct Definition {
|
|||
/// IntoResult,
|
||||
/// Queryable,
|
||||
/// Store,
|
||||
/// TypedValue,
|
||||
/// ValueType,
|
||||
/// };
|
||||
///
|
||||
|
@ -225,7 +227,7 @@ pub struct Definition {
|
|||
/// for row in results.into_iter() {
|
||||
/// let mut r = row.into_iter();
|
||||
/// let e = r.next().and_then(|e| e.into_known_entid()).expect("entity");
|
||||
/// let obsolete = r.next().expect("value");
|
||||
/// let obsolete = r.next().expect("value").val().expect("typed value");
|
||||
/// builder.retract(e, link_title, obsolete)?;
|
||||
/// }
|
||||
/// ip.transact_builder(builder)?;
|
||||
|
@ -882,7 +884,8 @@ impl<T> HasVocabularies for T where T: HasSchema + Queryable {
|
|||
.into_iter()
|
||||
.filter_map(|v|
|
||||
match (&v[0], &v[1]) {
|
||||
(&TypedValue::Ref(vocab), &TypedValue::Long(version))
|
||||
(&Binding::Scalar(TypedValue::Ref(vocab)),
|
||||
&Binding::Scalar(TypedValue::Long(version)))
|
||||
if version > 0 && (version < u32::max_value() as i64) => Some((vocab, version as u32)),
|
||||
(_, _) => None,
|
||||
})
|
||||
|
@ -895,7 +898,8 @@ impl<T> HasVocabularies for T where T: HasSchema + Queryable {
|
|||
.into_iter()
|
||||
.filter_map(|v| {
|
||||
match (&v[0], &v[1]) {
|
||||
(&TypedValue::Ref(vocab), &TypedValue::Ref(attr)) => Some((vocab, attr)),
|
||||
(&Binding::Scalar(TypedValue::Ref(vocab)),
|
||||
&Binding::Scalar(TypedValue::Ref(attr))) => Some((vocab, attr)),
|
||||
(_, _) => None,
|
||||
}
|
||||
});
|
||||
|
|
|
@ -27,6 +27,7 @@ use mentat::{
|
|||
Queryable,
|
||||
Schema,
|
||||
Store,
|
||||
Binding,
|
||||
TypedValue,
|
||||
};
|
||||
|
||||
|
@ -187,7 +188,7 @@ fn test_fetch_attribute_value_for_entid() {
|
|||
|
||||
let entities = store.q_once(r#"[:find ?e . :where [?e :foo/bar 100]]"#, None).expect("Expected query to work").into_scalar().expect("expected scalar results");
|
||||
let entid = match entities {
|
||||
Some(TypedValue::Ref(entid)) => entid,
|
||||
Some(Binding::Scalar(TypedValue::Ref(entid))) => entid,
|
||||
x => panic!("expected Some(Ref), got {:?}", x),
|
||||
};
|
||||
|
||||
|
@ -208,7 +209,7 @@ fn test_fetch_attribute_values_for_entid() {
|
|||
|
||||
let entities = store.q_once(r#"[:find ?e . :where [?e :foo/bar 100]]"#, None).expect("Expected query to work").into_scalar().expect("expected scalar results");
|
||||
let entid = match entities {
|
||||
Some(TypedValue::Ref(entid)) => entid,
|
||||
Some(Binding::Scalar(TypedValue::Ref(entid))) => entid,
|
||||
x => panic!("expected Some(Ref), got {:?}", x),
|
||||
};
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ use mentat_core::{
|
|||
Entid,
|
||||
HasSchema,
|
||||
KnownEntid,
|
||||
TypedValue,
|
||||
Utc,
|
||||
Uuid,
|
||||
ValueType,
|
||||
|
@ -50,7 +49,9 @@ use mentat::{
|
|||
QueryResults,
|
||||
RelResult,
|
||||
Store,
|
||||
Binding,
|
||||
TxReport,
|
||||
TypedValue,
|
||||
Variable,
|
||||
new_connection,
|
||||
};
|
||||
|
@ -132,7 +133,7 @@ fn test_scalar() {
|
|||
|
||||
assert_eq!(1, results.len());
|
||||
|
||||
if let QueryResults::Scalar(Some(TypedValue::Keyword(ref rc))) = results {
|
||||
if let QueryResults::Scalar(Some(Binding::Scalar(TypedValue::Keyword(ref rc)))) = results {
|
||||
// Should be '24'.
|
||||
assert_eq!(&NamespacedKeyword::new("db.type", "keyword"), rc.as_ref());
|
||||
assert_eq!(KnownEntid(24),
|
||||
|
@ -166,7 +167,7 @@ fn test_tuple() {
|
|||
if let QueryResults::Tuple(Some(ref tuple)) = results {
|
||||
let cardinality_one = NamespacedKeyword::new("db.cardinality", "one");
|
||||
assert_eq!(tuple.len(), 2);
|
||||
assert_eq!(tuple[0], TypedValue::Boolean(true));
|
||||
assert_eq!(tuple[0], TypedValue::Boolean(true).into());
|
||||
assert_eq!(tuple[1], db.schema.get_entid(&cardinality_one).expect("c1").into());
|
||||
} else {
|
||||
panic!("Expected tuple.");
|
||||
|
@ -214,7 +215,7 @@ fn test_inputs() {
|
|||
.expect("query to succeed")
|
||||
.results;
|
||||
|
||||
if let QueryResults::Scalar(Some(TypedValue::Keyword(value))) = results {
|
||||
if let QueryResults::Scalar(Some(Binding::Scalar(TypedValue::Keyword(value)))) = results {
|
||||
assert_eq!(value.as_ref(), &NamespacedKeyword::new("db.install", "valueType"));
|
||||
} else {
|
||||
panic!("Expected scalar.");
|
||||
|
@ -267,9 +268,9 @@ fn test_instants_and_uuids() {
|
|||
QueryResults::Tuple(Some(vals)) => {
|
||||
let mut vals = vals.into_iter();
|
||||
match (vals.next(), vals.next(), vals.next(), vals.next()) {
|
||||
(Some(TypedValue::Ref(e)),
|
||||
Some(TypedValue::Uuid(u)),
|
||||
Some(TypedValue::Instant(t)),
|
||||
(Some(Binding::Scalar(TypedValue::Ref(e))),
|
||||
Some(Binding::Scalar(TypedValue::Uuid(u))),
|
||||
Some(Binding::Scalar(TypedValue::Instant(t))),
|
||||
None) => {
|
||||
assert!(e > 40); // There are at least this many entities in the store.
|
||||
assert_eq!(Ok(u), Uuid::from_str("cf62d552-6569-4d1b-b667-04703041dfc4"));
|
||||
|
@ -384,9 +385,9 @@ fn test_fulltext() {
|
|||
QueryResults::Tuple(Some(vals)) => {
|
||||
let mut vals = vals.into_iter();
|
||||
match (vals.next(), vals.next(), vals.next(), vals.next()) {
|
||||
(Some(TypedValue::Ref(x)),
|
||||
Some(TypedValue::String(text)),
|
||||
Some(TypedValue::Double(score)),
|
||||
(Some(Binding::Scalar(TypedValue::Ref(x))),
|
||||
Some(Binding::Scalar(TypedValue::String(text))),
|
||||
Some(Binding::Scalar(TypedValue::Double(score))),
|
||||
None) => {
|
||||
assert_eq!(x, v);
|
||||
assert_eq!(text.as_str(), "hello darkness my old friend");
|
||||
|
@ -447,11 +448,12 @@ fn test_fulltext() {
|
|||
.into();
|
||||
match r {
|
||||
QueryResults::Rel(rels) => {
|
||||
assert_eq!(rels, vec![
|
||||
vec![TypedValue::Ref(v),
|
||||
TypedValue::String("I've come to talk with you again".to_string().into()),
|
||||
let values: Vec<Vec<Binding>> = rels.into_iter().collect();
|
||||
assert_eq!(values, vec![
|
||||
vec![Binding::Scalar(TypedValue::Ref(v)),
|
||||
"I've come to talk with you again".into(),
|
||||
]
|
||||
].into());
|
||||
]);
|
||||
},
|
||||
_ => panic!("Expected query to work."),
|
||||
}
|
||||
|
@ -486,8 +488,8 @@ fn test_instant_range_query() {
|
|||
match r {
|
||||
QueryResults::Coll(vals) => {
|
||||
assert_eq!(vals,
|
||||
vec![TypedValue::Ref(*ids.get("b").unwrap()),
|
||||
TypedValue::Ref(*ids.get("c").unwrap())]);
|
||||
vec![Binding::Scalar(TypedValue::Ref(*ids.get("b").unwrap())),
|
||||
Binding::Scalar(TypedValue::Ref(*ids.get("c").unwrap()))]);
|
||||
},
|
||||
_ => panic!("Expected query to work."),
|
||||
}
|
||||
|
@ -628,7 +630,7 @@ fn test_aggregates_type_handling() {
|
|||
.unwrap();
|
||||
|
||||
// Our two transactions, the bootstrap transaction, plus the three values.
|
||||
assert_eq!(TypedValue::Long(6), r);
|
||||
assert_eq!(Binding::Scalar(TypedValue::Long(6)), r);
|
||||
|
||||
// And you can min them, which returns an instant.
|
||||
let r = store.q_once(r#"[:find (min ?v) .
|
||||
|
@ -639,7 +641,7 @@ fn test_aggregates_type_handling() {
|
|||
.unwrap();
|
||||
|
||||
let earliest = DateTime::parse_from_rfc3339("2017-01-01T11:00:00.000Z").unwrap().with_timezone(&Utc);
|
||||
assert_eq!(TypedValue::Instant(earliest), r);
|
||||
assert_eq!(Binding::Scalar(TypedValue::Instant(earliest)), r);
|
||||
|
||||
let r = store.q_once(r#"[:find (sum ?v) .
|
||||
:where [_ _ ?v] [(long ?v)]]"#,
|
||||
|
@ -650,7 +652,7 @@ fn test_aggregates_type_handling() {
|
|||
|
||||
// Yes, the current version is in the store as a Long!
|
||||
let total = 30i64 + 20i64 + 10i64 + ::mentat_db::db::CURRENT_VERSION as i64;
|
||||
assert_eq!(TypedValue::Long(total), r);
|
||||
assert_eq!(Binding::Scalar(TypedValue::Long(total)), r);
|
||||
|
||||
let r = store.q_once(r#"[:find (avg ?v) .
|
||||
:where [_ _ ?v] [(double ?v)]]"#,
|
||||
|
@ -660,7 +662,7 @@ fn test_aggregates_type_handling() {
|
|||
.unwrap();
|
||||
|
||||
let avg = (6.4f64 / 3f64) + (4.4f64 / 3f64) + (2.4f64 / 3f64);
|
||||
assert_eq!(TypedValue::Double(avg.into()), r);
|
||||
assert_eq!(Binding::Scalar(TypedValue::Double(avg.into())), r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -700,7 +702,7 @@ fn test_type_reqs() {
|
|||
assert_eq!(res.width, 1);
|
||||
let entid =
|
||||
match res.into_iter().next().unwrap().into_iter().next().unwrap() {
|
||||
TypedValue::Ref(eid) => {
|
||||
Binding::Scalar(TypedValue::Ref(eid)) => {
|
||||
eid
|
||||
},
|
||||
unexpected => {
|
||||
|
@ -753,7 +755,7 @@ fn test_type_reqs() {
|
|||
.into();
|
||||
match res {
|
||||
QueryResults::Coll(vals) => {
|
||||
assert_eq!(vals, vec![TypedValue::Long(5), TypedValue::Long(33)])
|
||||
assert_eq!(vals, vec![Binding::Scalar(TypedValue::Long(5)), Binding::Scalar(TypedValue::Long(33))])
|
||||
},
|
||||
v => {
|
||||
panic!("Query returned unexpected type: {:?}", v);
|
||||
|
@ -800,7 +802,7 @@ fn test_monster_head_aggregates() {
|
|||
.expect("results")
|
||||
.into();
|
||||
match res {
|
||||
QueryResults::Scalar(Some(TypedValue::Long(count))) => {
|
||||
QueryResults::Scalar(Some(Binding::Scalar(TypedValue::Long(count)))) => {
|
||||
assert_eq!(count, 4);
|
||||
},
|
||||
r => panic!("Unexpected result {:?}", r),
|
||||
|
@ -811,7 +813,7 @@ fn test_monster_head_aggregates() {
|
|||
.expect("results")
|
||||
.into();
|
||||
match res {
|
||||
QueryResults::Scalar(Some(TypedValue::Long(count))) => {
|
||||
QueryResults::Scalar(Some(Binding::Scalar(TypedValue::Long(count)))) => {
|
||||
assert_eq!(count, 6);
|
||||
},
|
||||
r => panic!("Unexpected result {:?}", r),
|
||||
|
@ -921,7 +923,7 @@ fn test_basic_aggregates() {
|
|||
.into();
|
||||
match r {
|
||||
QueryResults::Scalar(Some(val)) => {
|
||||
assert_eq!(val, TypedValue::Long(2));
|
||||
assert_eq!(val, Binding::Scalar(TypedValue::Long(2)));
|
||||
},
|
||||
_ => panic!("Expected scalar."),
|
||||
}
|
||||
|
@ -935,8 +937,8 @@ fn test_basic_aggregates() {
|
|||
match r {
|
||||
QueryResults::Tuple(Some(vals)) => {
|
||||