Add 'Binding', a structured value type to return from queries. (#657) r=nalexander

Bump to 0.7: breaking change.
This commit is contained in:
Richard Newman 2018-04-24 15:08:38 -07:00 committed by GitHub
parent 1818e0b98e
commit a2e13f624c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 396 additions and 166 deletions

View file

@ -12,7 +12,7 @@ authors = [
"Thom Chiovoloni <tchiovoloni@mozilla.com>", "Thom Chiovoloni <tchiovoloni@mozilla.com>",
] ]
name = "mentat" name = "mentat"
version = "0.6.2" version = "0.7.0"
build = "build/version.rs" build = "build/version.rs"
[features] [features]

View file

@ -6,6 +6,7 @@ workspace = ".."
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
enum-set = { git = "https://github.com/rnewman/enum-set" } enum-set = { git = "https://github.com/rnewman/enum-set" }
indexmap = "1"
lazy_static = "0.2" lazy_static = "0.2"
num = "0.1" num = "0.1"
ordered-float = { version = "0.5", features = ["serde"] } ordered-float = { version = "0.5", features = ["serde"] }

View file

@ -10,6 +10,7 @@
extern crate chrono; extern crate chrono;
extern crate enum_set; extern crate enum_set;
extern crate indexmap;
extern crate ordered_float; extern crate ordered_float;
extern crate uuid; extern crate uuid;
extern crate serde; extern crate serde;
@ -57,6 +58,7 @@ pub use types::{
Entid, Entid,
KnownEntid, KnownEntid,
TypedValue, TypedValue,
Binding,
ValueType, ValueType,
ValueTypeTag, ValueTypeTag,
now, now,

View file

@ -22,6 +22,10 @@ use ::chrono::{
Timelike, // For truncation. Timelike, // For truncation.
}; };
use ::indexmap::{
IndexMap,
};
use ::edn::{ use ::edn::{
self, self,
FromMicros, 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 { impl From<KnownEntid> for TypedValue {
fn from(k: KnownEntid) -> TypedValue { fn from(k: KnownEntid) -> TypedValue {
TypedValue::Ref(k.0) 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. 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 { impl TypedValue {
/// Returns true if the provided type is `Some` and matches this value's type, or if the /// Returns true if the provided type is `Some` and matches this value's type, or if the
/// provided type is `None`. /// 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] #[test]
fn test_typed_value() { fn test_typed_value() {
assert!(TypedValue::Boolean(false).is_congruent_with(None)); assert!(TypedValue::Boolean(false).is_congruent_with(None));

View file

@ -34,6 +34,7 @@ use rusqlite::{
}; };
use mentat_core::{ use mentat_core::{
Binding,
TypedValue, TypedValue,
ValueType, ValueType,
ValueTypeTag, ValueTypeTag,
@ -84,6 +85,7 @@ pub use project::{
pub use relresult::{ pub use relresult::{
RelResult, RelResult,
StructuredRelResult,
}; };
use errors::{ use errors::{
@ -99,10 +101,10 @@ pub struct QueryOutput {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum QueryResults { pub enum QueryResults {
Scalar(Option<TypedValue>), Scalar(Option<Binding>),
Tuple(Option<Vec<TypedValue>>), Tuple(Option<Vec<Binding>>),
Coll(Vec<TypedValue>), Coll(Vec<Binding>),
Rel(RelResult), Rel(RelResult<Binding>),
} }
impl From<QueryOutput> for QueryResults { impl From<QueryOutput> for QueryResults {
@ -153,7 +155,9 @@ impl QueryOutput {
match &**spec { match &**spec {
&FindScalar(Element::Variable(ref var)) | &FindScalar(Element::Variable(ref var)) |
&FindScalar(Element::Corresponding(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) QueryResults::Scalar(val)
}, },
&FindScalar(Element::Aggregate(ref _agg)) => { &FindScalar(Element::Aggregate(ref _agg)) => {
@ -165,7 +169,10 @@ impl QueryOutput {
.map(|e| match e { .map(|e| match e {
&Element::Variable(ref var) | &Element::Variable(ref var) |
&Element::Corresponding(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) => { &Element::Aggregate(ref _agg) => {
// TODO: static computation of aggregates, then // TODO: static computation of aggregates, then
@ -178,7 +185,10 @@ impl QueryOutput {
}, },
&FindColl(Element::Variable(ref var)) | &FindColl(Element::Variable(ref var)) |
&FindColl(Element::Corresponding(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]) QueryResults::Coll(vec![val])
}, },
&FindColl(Element::Aggregate(ref _agg)) => { &FindColl(Element::Aggregate(ref _agg)) => {
@ -193,7 +203,10 @@ impl QueryOutput {
let values = elements.iter().map(|e| match e { let values = elements.iter().map(|e| match e {
&Element::Variable(ref var) | &Element::Variable(ref var) |
&Element::Corresponding(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) => { &Element::Aggregate(ref _agg) => {
// TODO: static computation of aggregates, then // 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() self.results.into_scalar()
} }
pub fn into_coll(self) -> Result<Vec<TypedValue>> { pub fn into_coll(self) -> Result<Vec<Binding>> {
self.results.into_coll() 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() self.results.into_tuple()
} }
pub fn into_rel(self) -> Result<RelResult> { pub fn into_rel(self) -> Result<RelResult<Binding>> {
self.results.into_rel() 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 { match self {
QueryResults::Scalar(o) => Ok(o), QueryResults::Scalar(o) => Ok(o),
QueryResults::Coll(_) => bail!(ErrorKind::UnexpectedResultsType("coll", "scalar")), 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 { match self {
QueryResults::Scalar(_) => bail!(ErrorKind::UnexpectedResultsType("scalar", "coll")), QueryResults::Scalar(_) => bail!(ErrorKind::UnexpectedResultsType("scalar", "coll")),
QueryResults::Coll(c) => Ok(c), 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 { match self {
QueryResults::Scalar(_) => bail!(ErrorKind::UnexpectedResultsType("scalar", "tuple")), QueryResults::Scalar(_) => bail!(ErrorKind::UnexpectedResultsType("scalar", "tuple")),
QueryResults::Coll(_) => bail!(ErrorKind::UnexpectedResultsType("coll", "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 { match self {
QueryResults::Scalar(_) => bail!(ErrorKind::UnexpectedResultsType("scalar", "rel")), QueryResults::Scalar(_) => bail!(ErrorKind::UnexpectedResultsType("scalar", "rel")),
QueryResults::Coll(_) => bail!(ErrorKind::UnexpectedResultsType("coll", "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 /// This function will return a runtime error if the type tag is unknown, or the value is
/// otherwise not convertible by the DB layer. /// 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::*; use TypedIndex::*;
match self { match self {
&Known(value_index, value_type) => { &Known(value_index, value_type) => {
let v: rusqlite::types::Value = row.get(value_index); 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) => { &Unknown(value_index, type_index) => {
let v: rusqlite::types::Value = row.get(value_index); let v: rusqlite::types::Value = row.get(value_index);
let value_type_tag: i32 = row.get(type_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. // 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. // There will be at least as many SQL columns as Datalog columns.
// gte 'cos we might be querying extra columns for ordering. // gte 'cos we might be querying extra columns for ordering.
// The templates will take care of ignoring columns. // The templates will take care of ignoring columns.
@ -430,7 +447,7 @@ impl TupleProjector {
self.templates self.templates
.iter() .iter()
.map(|ti| ti.lookup(&row)) .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> { 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. // There will be at least as many SQL columns as Datalog columns.
// gte 'cos we might be querying extra columns for ordering. // gte 'cos we might be querying extra columns for ordering.
// The templates will take care of ignoring columns. // The templates will take care of ignoring columns.
@ -580,7 +597,7 @@ impl CollProjector {
impl Projector for CollProjector { impl Projector for CollProjector {
fn project<'stmt>(&self, mut rows: Rows<'stmt>) -> Result<QueryOutput> { 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() { while let Some(r) = rows.next() {
let row = r?; let row = r?;
let binding = self.template.lookup(&row)?; let binding = self.template.lookup(&row)?;
@ -664,7 +681,7 @@ pub fn query_projection(query: &AlgebraicQuery) -> Result<Either<ConstantProject
// TODO: error handling // TODO: error handling
let results = QueryOutput::from_constants(&spec, query.cc.value_bindings(&variables)); 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))) Ok(Either::Left(ConstantProjector::new(spec, f)))
} else if query.is_known_empty() { } else if query.is_known_empty() {

View file

@ -9,6 +9,7 @@
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use mentat_core::{ use mentat_core::{
Binding,
TypedValue, TypedValue,
}; };
@ -31,13 +32,15 @@ use mentat_core::{
/// - By consuming the results using `into_iter`. This allocates short-lived vectors, /// - By consuming the results using `into_iter`. This allocates short-lived vectors,
/// but gives you ownership of the enclosed `TypedValue`s. /// but gives you ownership of the enclosed `TypedValue`s.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct RelResult { pub struct RelResult<T> {
pub width: usize, pub width: usize,
pub values: Vec<TypedValue>, pub values: Vec<T>,
} }
impl RelResult { pub type StructuredRelResult = RelResult<Binding>;
pub fn empty(width: usize) -> RelResult {
impl<T> RelResult<T> {
pub fn empty(width: usize) -> RelResult<T> {
RelResult { RelResult {
width: width, width: width,
values: Vec::new(), values: Vec::new(),
@ -52,12 +55,12 @@ impl RelResult {
self.values.len() / self.width 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. // TODO: Nightly-only API `exact_chunks`. #47115.
self.values.chunks(self.width) 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); let end = self.width * (index + 1);
if end > self.values.len() { if end > self.values.len() {
None None
@ -70,15 +73,15 @@ impl RelResult {
#[test] #[test]
fn test_rel_result() { fn test_rel_result() {
let empty = RelResult::empty(3); let empty = StructuredRelResult::empty(3);
let unit = RelResult { let unit = StructuredRelResult {
width: 1, 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, width: 2,
values: vec![TypedValue::Long(5), TypedValue::Boolean(true), values: vec![TypedValue::Long(5).into(), TypedValue::Boolean(true).into(),
TypedValue::Long(-2), TypedValue::Boolean(false)], TypedValue::Long(-2).into(), TypedValue::Boolean(false).into()],
}; };
assert!(empty.is_empty()); assert!(empty.is_empty());
@ -93,18 +96,18 @@ fn test_rel_result() {
assert_eq!(unit.row(1), None); assert_eq!(unit.row(1), None);
assert_eq!(two_by_two.row(2), None); assert_eq!(two_by_two.row(2), None);
assert_eq!(unit.row(0), Some(vec![TypedValue::Long(5)].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), TypedValue::Boolean(true)].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), TypedValue::Boolean(false)].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(); 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(5).into(), TypedValue::Boolean(true).into()].as_slice()));
assert_eq!(rr.next(), Some(vec![TypedValue::Long(-2), TypedValue::Boolean(false)].as_slice())); assert_eq!(rr.next(), Some(vec![TypedValue::Long(-2).into(), TypedValue::Boolean(false).into()].as_slice()));
assert_eq!(rr.next(), None); assert_eq!(rr.next(), None);
} }
// Primarily for testing. // 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 { fn from(src: Vec<Vec<TypedValue>>) -> Self {
if src.is_empty() { if src.is_empty() {
RelResult::empty(0) 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); let width = src.get(0).map(|r| r.len()).unwrap_or(0);
RelResult { RelResult {
width: width, 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, 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 // TODO: this is a good opportunity to use `SmallVec` instead: most queries
// return a handful of columns. // return a handful of columns.
type Item = Vec<TypedValue>; type Item = Vec<T>;
fn next(&mut self) -> Option<Vec<TypedValue>> { fn next(&mut self) -> Option<Self::Item> {
let result: Vec<TypedValue> = (&mut self.values).take(self.width).collect(); let result: Vec<_> = (&mut self.values).take(self.width).collect();
if result.is_empty() { if result.is_empty() {
None None
} else { } else {
@ -137,9 +140,9 @@ impl Iterator for SubvecIntoIterator {
} }
} }
impl IntoIterator for RelResult { impl<T> IntoIterator for RelResult<T> {
type Item = Vec<TypedValue>; type Item = Vec<T>;
type IntoIter = SubvecIntoIterator; type IntoIter = SubvecIntoIterator<T>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
SubvecIntoIterator { SubvecIntoIterator {

View file

@ -735,7 +735,7 @@ fn test_ground_scalar() {
let constant = translate_to_constant(&schema, query); let constant = translate_to_constant(&schema, query);
assert_eq!(constant.project_without_rows().unwrap() assert_eq!(constant.project_without_rows().unwrap()
.into_scalar().unwrap(), .into_scalar().unwrap(),
Some(TypedValue::typed_string("yyy"))); Some(TypedValue::typed_string("yyy").into()));
// Verify that we accept bound input constants. // Verify that we accept bound input constants.
let query = r#"[:find ?x . :in ?v :where [(ground ?v) ?x]]"#; 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); let constant = translate_with_inputs_to_constant(&schema, query, inputs);
assert_eq!(constant.project_without_rows().unwrap() assert_eq!(constant.project_without_rows().unwrap()
.into_scalar().unwrap(), .into_scalar().unwrap(),
Some(TypedValue::typed_string("aaa"))); Some(TypedValue::typed_string("aaa").into()));
} }
#[test] #[test]
@ -765,7 +765,7 @@ fn test_ground_tuple() {
let constant = translate_with_inputs_to_constant(&schema, query, inputs); let constant = translate_with_inputs_to_constant(&schema, query, inputs);
assert_eq!(constant.project_without_rows().unwrap() assert_eq!(constant.project_without_rows().unwrap()
.into_tuple().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. // 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: // In that case the query wouldn't be constant, and would look more like:

View file

@ -923,6 +923,7 @@ mod tests {
use mentat_core::{ use mentat_core::{
CachedAttributes, CachedAttributes,
Binding,
TypedValue, TypedValue,
}; };
@ -1038,7 +1039,7 @@ mod tests {
let during = in_progress.q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None) let during = in_progress.q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None)
.expect("query succeeded"); .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"); let report = in_progress.transact(t2).expect("t2 succeeded");
in_progress.commit().expect("commit succeeded"); in_progress.commit().expect("commit succeeded");
@ -1083,10 +1084,10 @@ mod tests {
values).expect("prepare succeeded"); values).expect("prepare succeeded");
let yeses = prepared.run(None).expect("result"); 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"); 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] #[test]
@ -1119,7 +1120,7 @@ mod tests {
let during = in_progress.q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None) let during = in_progress.q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None)
.expect("query succeeded"); .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. // And we can do direct lookup, too.
let kw = in_progress.lookup_value_for_attribute(one, &edn::NamespacedKeyword::new("db", "ident")) 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 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 first = entities.expect("expected a result");
let entid = match first { let entid = match first {
TypedValue::Ref(entid) => entid, Binding::Scalar(TypedValue::Ref(entid)) => entid,
x => panic!("expected Some(Ref), got {:?}", x), x => panic!("expected Some(Ref), got {:?}", x),
}; };
@ -1279,7 +1280,7 @@ mod tests {
let mut ip = conn.begin_transaction(&mut sqlite).expect("began"); let mut ip = conn.begin_transaction(&mut sqlite).expect("began");
let ident = ip.q_once(query.as_str(), None).into_scalar_result().expect("query"); 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(); let start = time::PreciseTime::now();
ip.q_once(query.as_str(), None).into_scalar_result().expect("query"); 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)); assert!(ip.cache.is_attribute_cached_forward(db_ident));
let ident = ip.q_once(query.as_str(), None).into_scalar_result().expect("query"); 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(); let start = time::PreciseTime::now();
ip.q_once(query.as_str(), None).into_scalar_result().expect("query"); 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 mut ip = conn.begin_transaction(&mut sqlite).expect("began");
let ident = ip.q_once(query.as_str(), None).into_scalar_result().expect("query"); 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/ident), CacheDirection::Forward, CacheAction::Register).expect("registered");
ip.cache(&kw!(:db/valueType), 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] [?neighborhood :neighborhood/district ?d]
[?d :district/name ?district]]"#; [?d :district/name ?district]]"#;
let hood = "Beacon Hill"; 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) let mut prepared = in_progress.q_prepare(query, inputs)
.expect("prepared"); .expect("prepared");
match &prepared { match &prepared {

View file

@ -41,6 +41,7 @@ pub use mentat_core::{
KnownEntid, KnownEntid,
NamespacedKeyword, NamespacedKeyword,
Schema, Schema,
Binding,
TypedValue, TypedValue,
Uuid, Uuid,
Utc, Utc,

View file

@ -18,6 +18,7 @@ use mentat_core::{
HasSchema, HasSchema,
KnownEntid, KnownEntid,
Schema, Schema,
Binding,
TypedValue, TypedValue,
}; };
@ -117,26 +118,26 @@ impl<'sqlite> PreparedQuery<'sqlite> {
} }
pub trait IntoResult { pub trait IntoResult {
fn into_scalar_result(self) -> Result<Option<TypedValue>>; fn into_scalar_result(self) -> Result<Option<Binding>>;
fn into_coll_result(self) -> Result<Vec<TypedValue>>; fn into_coll_result(self) -> Result<Vec<Binding>>;
fn into_tuple_result(self) -> Result<Option<Vec<TypedValue>>>; fn into_tuple_result(self) -> Result<Option<Vec<Binding>>>;
fn into_rel_result(self) -> Result<RelResult>; fn into_rel_result(self) -> Result<RelResult<Binding>>;
} }
impl IntoResult for QueryExecutionResult { 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()) 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()) 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()) 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()) 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) { if known.is_attribute_cached_forward(attrid) {
Ok(known.get_value_for_entid(known.schema, attrid, entid).cloned()) Ok(known.get_value_for_entid(known.schema, attrid, entid).cloned())
} else { } 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() .cloned()
.unwrap_or_else(|| vec![])) .unwrap_or_else(|| vec![]))
} else { } 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())
} }
} }

View file

@ -16,6 +16,7 @@ use std::collections::{
use mentat_core::{ use mentat_core::{
Entid, Entid,
NamespacedKeyword, NamespacedKeyword,
Binding,
TypedValue, TypedValue,
ValueType, ValueType,
}; };
@ -86,22 +87,22 @@ impl<'a> QueryBuilder<'a> {
read.q_once(&self.sql, query_inputs) 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()?; let results = self.execute()?;
results.into_scalar().map_err(|e| e.into()) 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()?; let results = self.execute()?;
results.into_coll().map_err(|e| e.into()) 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()?; let results = self.execute()?;
results.into_tuple().map_err(|e| e.into()) 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()?; let results = self.execute()?;
results.into_rel().map_err(|e| e.into()) 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 n_yes = report.tempids.get("n").expect("found it").clone();
let results: Vec<TypedValue> = QueryBuilder::new(&mut store, r#"[:find [?x, ?i] let results: Vec<_> = QueryBuilder::new(&mut store, r#"[:find [?x, ?i]
:in ?v ?i :in ?v ?i
:where [?x :foo/boolean ?v] :where [?x :foo/boolean ?v]
[?x :foo/long ?i]]"#) [?x :foo/long ?i]]"#)
.bind_value("?v", true) .bind_value("?v", true)
.bind_long("?i", 27) .bind_long("?i", 27)
.execute_tuple().expect("TupleResult").unwrap_or(vec![]); .execute_tuple().expect("TupleResult").unwrap_or(vec![]);
let entid = TypedValue::Ref(n_yes.clone()); let entid = TypedValue::Ref(n_yes.clone()).into();
let long_val = TypedValue::Long(27); let long_val = TypedValue::Long(27).into();
assert_eq!(results, vec![entid, long_val]); assert_eq!(results, vec![entid, long_val]);
} }

View file

@ -110,6 +110,7 @@ use ::{
HasSchema, HasSchema,
IntoResult, IntoResult,
NamespacedKeyword, NamespacedKeyword,
Binding,
TypedValue, TypedValue,
ValueType, ValueType,
}; };
@ -172,6 +173,7 @@ pub struct Definition {
/// IntoResult, /// IntoResult,
/// Queryable, /// Queryable,
/// Store, /// Store,
/// TypedValue,
/// ValueType, /// ValueType,
/// }; /// };
/// ///
@ -225,7 +227,7 @@ pub struct Definition {
/// for row in results.into_iter() { /// for row in results.into_iter() {
/// let mut r = row.into_iter(); /// let mut r = row.into_iter();
/// let e = r.next().and_then(|e| e.into_known_entid()).expect("entity"); /// 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)?; /// builder.retract(e, link_title, obsolete)?;
/// } /// }
/// ip.transact_builder(builder)?; /// ip.transact_builder(builder)?;
@ -882,7 +884,8 @@ impl<T> HasVocabularies for T where T: HasSchema + Queryable {
.into_iter() .into_iter()
.filter_map(|v| .filter_map(|v|
match (&v[0], &v[1]) { 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)), if version > 0 && (version < u32::max_value() as i64) => Some((vocab, version as u32)),
(_, _) => None, (_, _) => None,
}) })
@ -895,7 +898,8 @@ impl<T> HasVocabularies for T where T: HasSchema + Queryable {
.into_iter() .into_iter()
.filter_map(|v| { .filter_map(|v| {
match (&v[0], &v[1]) { 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, (_, _) => None,
} }
}); });

View file

@ -27,6 +27,7 @@ use mentat::{
Queryable, Queryable,
Schema, Schema,
Store, Store,
Binding,
TypedValue, 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 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 { let entid = match entities {
Some(TypedValue::Ref(entid)) => entid, Some(Binding::Scalar(TypedValue::Ref(entid))) => entid,
x => panic!("expected Some(Ref), got {:?}", x), 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 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 { let entid = match entities {
Some(TypedValue::Ref(entid)) => entid, Some(Binding::Scalar(TypedValue::Ref(entid))) => entid,
x => panic!("expected Some(Ref), got {:?}", x), x => panic!("expected Some(Ref), got {:?}", x),
}; };

View file

@ -30,7 +30,6 @@ use mentat_core::{
Entid, Entid,
HasSchema, HasSchema,
KnownEntid, KnownEntid,
TypedValue,
Utc, Utc,
Uuid, Uuid,
ValueType, ValueType,
@ -50,7 +49,9 @@ use mentat::{
QueryResults, QueryResults,
RelResult, RelResult,
Store, Store,
Binding,
TxReport, TxReport,
TypedValue,
Variable, Variable,
new_connection, new_connection,
}; };
@ -132,7 +133,7 @@ fn test_scalar() {
assert_eq!(1, results.len()); 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'. // Should be '24'.
assert_eq!(&NamespacedKeyword::new("db.type", "keyword"), rc.as_ref()); assert_eq!(&NamespacedKeyword::new("db.type", "keyword"), rc.as_ref());
assert_eq!(KnownEntid(24), assert_eq!(KnownEntid(24),
@ -166,7 +167,7 @@ fn test_tuple() {
if let QueryResults::Tuple(Some(ref tuple)) = results { if let QueryResults::Tuple(Some(ref tuple)) = results {
let cardinality_one = NamespacedKeyword::new("db.cardinality", "one"); let cardinality_one = NamespacedKeyword::new("db.cardinality", "one");
assert_eq!(tuple.len(), 2); 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()); assert_eq!(tuple[1], db.schema.get_entid(&cardinality_one).expect("c1").into());
} else { } else {
panic!("Expected tuple."); panic!("Expected tuple.");
@ -214,7 +215,7 @@ fn test_inputs() {
.expect("query to succeed") .expect("query to succeed")
.results; .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")); assert_eq!(value.as_ref(), &NamespacedKeyword::new("db.install", "valueType"));
} else { } else {
panic!("Expected scalar."); panic!("Expected scalar.");
@ -267,9 +268,9 @@ fn test_instants_and_uuids() {
QueryResults::Tuple(Some(vals)) => { QueryResults::Tuple(Some(vals)) => {
let mut vals = vals.into_iter(); let mut vals = vals.into_iter();
match (vals.next(), vals.next(), vals.next(), vals.next()) { match (vals.next(), vals.next(), vals.next(), vals.next()) {
(Some(TypedValue::Ref(e)), (Some(Binding::Scalar(TypedValue::Ref(e))),
Some(TypedValue::Uuid(u)), Some(Binding::Scalar(TypedValue::Uuid(u))),
Some(TypedValue::Instant(t)), Some(Binding::Scalar(TypedValue::Instant(t))),
None) => { None) => {
assert!(e > 40); // There are at least this many entities in the store. 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")); assert_eq!(Ok(u), Uuid::from_str("cf62d552-6569-4d1b-b667-04703041dfc4"));
@ -384,9 +385,9 @@ fn test_fulltext() {
QueryResults::Tuple(Some(vals)) => { QueryResults::Tuple(Some(vals)) => {
let mut vals = vals.into_iter(); let mut vals = vals.into_iter();
match (vals.next(), vals.next(), vals.next(), vals.next()) { match (vals.next(), vals.next(), vals.next(), vals.next()) {
(Some(TypedValue::Ref(x)), (Some(Binding::Scalar(TypedValue::Ref(x))),
Some(TypedValue::String(text)), Some(Binding::Scalar(TypedValue::String(text))),
Some(TypedValue::Double(score)), Some(Binding::Scalar(TypedValue::Double(score))),
None) => { None) => {
assert_eq!(x, v); assert_eq!(x, v);
assert_eq!(text.as_str(), "hello darkness my old friend"); assert_eq!(text.as_str(), "hello darkness my old friend");
@ -447,11 +448,12 @@ fn test_fulltext() {
.into(); .into();
match r { match r {
QueryResults::Rel(rels) => { QueryResults::Rel(rels) => {
assert_eq!(rels, vec![ let values: Vec<Vec<Binding>> = rels.into_iter().collect();
vec![TypedValue::Ref(v), assert_eq!(values, vec![
TypedValue::String("I've come to talk with you again".to_string().into()), vec![Binding::Scalar(TypedValue::Ref(v)),
"I've come to talk with you again".into(),
] ]
].into()); ]);
}, },
_ => panic!("Expected query to work."), _ => panic!("Expected query to work."),
} }
@ -486,8 +488,8 @@ fn test_instant_range_query() {
match r { match r {
QueryResults::Coll(vals) => { QueryResults::Coll(vals) => {
assert_eq!(vals, assert_eq!(vals,
vec![TypedValue::Ref(*ids.get("b").unwrap()), vec![Binding::Scalar(TypedValue::Ref(*ids.get("b").unwrap())),
TypedValue::Ref(*ids.get("c").unwrap())]); Binding::Scalar(TypedValue::Ref(*ids.get("c").unwrap()))]);
}, },
_ => panic!("Expected query to work."), _ => panic!("Expected query to work."),
} }
@ -628,7 +630,7 @@ fn test_aggregates_type_handling() {
.unwrap(); .unwrap();
// Our two transactions, the bootstrap transaction, plus the three values. // 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. // And you can min them, which returns an instant.
let r = store.q_once(r#"[:find (min ?v) . let r = store.q_once(r#"[:find (min ?v) .
@ -639,7 +641,7 @@ fn test_aggregates_type_handling() {
.unwrap(); .unwrap();
let earliest = DateTime::parse_from_rfc3339("2017-01-01T11:00:00.000Z").unwrap().with_timezone(&Utc); 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) . let r = store.q_once(r#"[:find (sum ?v) .
:where [_ _ ?v] [(long ?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! // Yes, the current version is in the store as a Long!
let total = 30i64 + 20i64 + 10i64 + ::mentat_db::db::CURRENT_VERSION as i64; 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) . let r = store.q_once(r#"[:find (avg ?v) .
:where [_ _ ?v] [(double ?v)]]"#, :where [_ _ ?v] [(double ?v)]]"#,
@ -660,7 +662,7 @@ fn test_aggregates_type_handling() {
.unwrap(); .unwrap();
let avg = (6.4f64 / 3f64) + (4.4f64 / 3f64) + (2.4f64 / 3f64); 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] #[test]
@ -700,7 +702,7 @@ fn test_type_reqs() {
assert_eq!(res.width, 1); assert_eq!(res.width, 1);
let entid = let entid =
match res.into_iter().next().unwrap().into_iter().next().unwrap() { match res.into_iter().next().unwrap().into_iter().next().unwrap() {
TypedValue::Ref(eid) => { Binding::Scalar(TypedValue::Ref(eid)) => {
eid eid
}, },
unexpected => { unexpected => {
@ -753,7 +755,7 @@ fn test_type_reqs() {
.into(); .into();
match res { match res {
QueryResults::Coll(vals) => { 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 => { v => {
panic!("Query returned unexpected type: {:?}", v); panic!("Query returned unexpected type: {:?}", v);
@ -800,7 +802,7 @@ fn test_monster_head_aggregates() {
.expect("results") .expect("results")
.into(); .into();
match res { match res {
QueryResults::Scalar(Some(TypedValue::Long(count))) => { QueryResults::Scalar(Some(Binding::Scalar(TypedValue::Long(count)))) => {
assert_eq!(count, 4); assert_eq!(count, 4);
}, },
r => panic!("Unexpected result {:?}", r), r => panic!("Unexpected result {:?}", r),
@ -811,7 +813,7 @@ fn test_monster_head_aggregates() {
.expect("results") .expect("results")
.into(); .into();
match res { match res {
QueryResults::Scalar(Some(TypedValue::Long(count))) => { QueryResults::Scalar(Some(Binding::Scalar(TypedValue::Long(count)))) => {
assert_eq!(count, 6); assert_eq!(count, 6);
}, },
r => panic!("Unexpected result {:?}", r), r => panic!("Unexpected result {:?}", r),
@ -921,7 +923,7 @@ fn test_basic_aggregates() {
.into(); .into();
match r { match r {
QueryResults::Scalar(Some(val)) => { QueryResults::Scalar(Some(val)) => {
assert_eq!(val, TypedValue::Long(2)); assert_eq!(val, Binding::Scalar(TypedValue::Long(2)));
}, },
_ => panic!("Expected scalar."), _ => panic!("Expected scalar."),
} }
@ -935,8 +937,8 @@ fn test_basic_aggregates() {
match r { match r {
QueryResults::Tuple(Some(vals)) => { QueryResults::Tuple(Some(vals)) => {
assert_eq!(vals, assert_eq!(vals,
vec![TypedValue::Long(14), vec![Binding::Scalar(TypedValue::Long(14)),
TypedValue::Long(42)]); Binding::Scalar(TypedValue::Long(42))]);
}, },
_ => panic!("Expected tuple."), _ => panic!("Expected tuple."),
} }
@ -952,8 +954,8 @@ fn test_basic_aggregates() {
match r { match r {
QueryResults::Tuple(Some(vals)) => { QueryResults::Tuple(Some(vals)) => {
assert_eq!(vals, assert_eq!(vals,
vec![TypedValue::String("Alice".to_string().into()), vec!["Alice".into(),
TypedValue::Long(14)]); Binding::Scalar(TypedValue::Long(14))]);
}, },
r => panic!("Unexpected results {:?}", r), r => panic!("Unexpected results {:?}", r),
} }
@ -969,8 +971,8 @@ fn test_basic_aggregates() {
match r { match r {
QueryResults::Tuple(Some(vals)) => { QueryResults::Tuple(Some(vals)) => {
assert_eq!(vals, assert_eq!(vals,
vec![TypedValue::String("Carlos".to_string().into()), vec!["Carlos".into(),
TypedValue::Long(42)]); Binding::Scalar(TypedValue::Long(42))]);
}, },
_ => panic!("Expected tuple."), _ => panic!("Expected tuple."),
} }
@ -1057,7 +1059,7 @@ fn test_combinatorial() {
// How many different pairings of dancers were there? // How many different pairings of dancers were there?
// If we just use `!=` (or `differ`), the number is doubled because of symmetry! // If we just use `!=` (or `differ`), the number is doubled because of symmetry!
assert_eq!(TypedValue::Long(6), assert_eq!(Binding::Scalar(TypedValue::Long(6)),
store.q_once(r#"[:find (count ?right) . store.q_once(r#"[:find (count ?right) .
:with ?left :with ?left
:where :where
@ -1074,7 +1076,7 @@ fn test_combinatorial() {
// will come to rely on it. Instead we expose a specific operator: `unpermute`. // will come to rely on it. Instead we expose a specific operator: `unpermute`.
// When used in a query that generates permuted pairs of references, this // When used in a query that generates permuted pairs of references, this
// ensures that only one permutation is returned for a given pair. // ensures that only one permutation is returned for a given pair.
assert_eq!(TypedValue::Long(3), assert_eq!(Binding::Scalar(TypedValue::Long(3)),
store.q_once(r#"[:find (count ?right) . store.q_once(r#"[:find (count ?right) .
:with ?left :with ?left
:where :where
@ -1240,7 +1242,7 @@ fn test_aggregation_implicit_grouping() {
]"#).unwrap().tempids; ]"#).unwrap().tempids;
// How many different scores were there? // How many different scores were there?
assert_eq!(TypedValue::Long(7), assert_eq!(Binding::Scalar(TypedValue::Long(7)),
store.q_once(r#"[:find (count ?score) . store.q_once(r#"[:find (count ?score) .
:where :where
[?game :foo/score ?score]]"#, None) [?game :foo/score ?score]]"#, None)
@ -1249,7 +1251,7 @@ fn test_aggregation_implicit_grouping() {
// How many different games resulted in scores? // How many different games resulted in scores?
// '14' appears twice. // '14' appears twice.
assert_eq!(TypedValue::Long(8), assert_eq!(Binding::Scalar(TypedValue::Long(8)),
store.q_once(r#"[:find (count ?score) . store.q_once(r#"[:find (count ?score) .
:with ?game :with ?game
:where :where
@ -1258,7 +1260,7 @@ fn test_aggregation_implicit_grouping() {
.expect("scalar results").unwrap()); .expect("scalar results").unwrap());
// Who's the highest-scoring vegetarian? // Who's the highest-scoring vegetarian?
assert_eq!(vec!["Alice".into(), TypedValue::Long(99)], assert_eq!(vec!["Alice".into(), Binding::Scalar(TypedValue::Long(99))],
store.q_once(r#"[:find [(the ?name) (max ?score)] store.q_once(r#"[:find [(the ?name) (max ?score)]
:where :where
[?game :foo/score ?score] [?game :foo/score ?score]
@ -1270,11 +1272,11 @@ fn test_aggregation_implicit_grouping() {
// We can't run an ambiguous correspondence. // We can't run an ambiguous correspondence.
let res = store.q_once(r#"[:find [(the ?name) (min ?score) (max ?score)] let res = store.q_once(r#"[:find [(the ?name) (min ?score) (max ?score)]
:where :where
[?game :foo/score ?score] [?game :foo/score ?score]
[?person :foo/play ?game] [?person :foo/play ?game]
[?person :foo/is-vegetarian true] [?person :foo/is-vegetarian true]
[?person :foo/name ?name]]"#, None); [?person :foo/name ?name]]"#, None);
match res { match res {
Result::Err( Result::Err(
Error( Error(
@ -1292,7 +1294,7 @@ fn test_aggregation_implicit_grouping() {
} }
// Max scores for vegetarians. // Max scores for vegetarians.
let expected: RelResult = let expected: RelResult<Binding> =
vec![vec!["Alice".into(), TypedValue::Long(99)], vec![vec!["Alice".into(), TypedValue::Long(99)],
vec!["Beli".into(), TypedValue::Long(22)]].into(); vec!["Beli".into(), TypedValue::Long(22)]].into();
assert_eq!(expected, assert_eq!(expected,
@ -1382,6 +1384,7 @@ fn test_tx_ids() {
.into(); .into();
match r { match r {
QueryResults::Coll(txs) => { QueryResults::Coll(txs) => {
let expected: Vec<Binding> = expected.into_iter().map(|tv| tv.into()).collect();
assert_eq!(txs, expected); assert_eq!(txs, expected);
}, },
x => panic!("Got unexpected results {:?}", x), x => panic!("Got unexpected results {:?}", x),

View file

@ -49,6 +49,7 @@ use mentat::{
Queryable, Queryable,
RelResult, RelResult,
Store, Store,
Binding,
TypedValue, TypedValue,
ValueType, ValueType,
}; };
@ -212,8 +213,8 @@ fn test_add_vocab() {
.into_tuple_result() .into_tuple_result()
.expect("query returns") .expect("query returns")
.expect("a result"); .expect("a result");
assert_eq!(ver_attr[0], TypedValue::Long(1)); assert_eq!(ver_attr[0], TypedValue::Long(1).into());
assert_eq!(ver_attr[1], TypedValue::typed_ns_keyword("foo", "bar")); assert_eq!(ver_attr[1], TypedValue::typed_ns_keyword("foo", "bar").into());
// If we commit, it'll stick around. // If we commit, it'll stick around.
in_progress.commit().expect("commit succeeded"); in_progress.commit().expect("commit succeeded");
@ -227,8 +228,8 @@ fn test_add_vocab() {
.into_tuple_result() .into_tuple_result()
.expect("query returns") .expect("query returns")
.expect("a result"); .expect("a result");
assert_eq!(ver_attr[0], TypedValue::Long(1)); assert_eq!(ver_attr[0], TypedValue::Long(1).into());
assert_eq!(ver_attr[1], TypedValue::typed_ns_keyword("foo", "bar")); assert_eq!(ver_attr[1], TypedValue::typed_ns_keyword("foo", "bar").into());
// Scoped borrow of `conn`. // Scoped borrow of `conn`.
{ {
@ -264,8 +265,8 @@ fn test_add_vocab() {
.expect("query returns"); .expect("query returns");
assert_eq!(actual_attributes, assert_eq!(actual_attributes,
vec![ vec![
TypedValue::typed_ns_keyword("foo", "bar"), TypedValue::typed_ns_keyword("foo", "bar").into(),
TypedValue::typed_ns_keyword("foo", "baz"), TypedValue::typed_ns_keyword("foo", "baz").into(),
]); ]);
// Now let's modify our vocabulary without bumping the version. This is invalid and will result // Now let's modify our vocabulary without bumping the version. This is invalid and will result
@ -336,10 +337,10 @@ fn test_add_vocab() {
} }
/// A helper to turn rows from `[:find ?e ?a :where [?e ?a ?v]]` into a tuple. /// A helper to turn rows from `[:find ?e ?a :where [?e ?a ?v]]` into a tuple.
fn ea(row: Vec<TypedValue>) -> (KnownEntid, KnownEntid) { fn ea(row: Vec<Binding>) -> (KnownEntid, KnownEntid) {
let mut row = row.into_iter(); let mut row = row.into_iter();
match (row.next(), row.next()) { match (row.next(), row.next()) {
(Some(TypedValue::Ref(e)), Some(TypedValue::Ref(a))) => { (Some(Binding::Scalar(TypedValue::Ref(e))), Some(Binding::Scalar(TypedValue::Ref(a)))) => {
(KnownEntid(e), KnownEntid(a)) (KnownEntid(e), KnownEntid(a))
}, },
_ => panic!("Incorrect query shape for 'ea' helper."), _ => panic!("Incorrect query shape for 'ea' helper."),
@ -347,18 +348,20 @@ fn ea(row: Vec<TypedValue>) -> (KnownEntid, KnownEntid) {
} }
/// A helper to turn rows from `[:find ?a ?v :where [?e ?a ?v]]` into a tuple. /// A helper to turn rows from `[:find ?a ?v :where [?e ?a ?v]]` into a tuple.
fn av(row: Vec<TypedValue>) -> (KnownEntid, TypedValue) { /// Panics if any of the values are maps or vecs.
fn av(row: Vec<Binding>) -> (KnownEntid, TypedValue) {
let mut row = row.into_iter(); let mut row = row.into_iter();
match (row.next(), row.next()) { match (row.next(), row.next()) {
(Some(TypedValue::Ref(a)), Some(v)) => { (Some(Binding::Scalar(TypedValue::Ref(a))), Some(v)) => {
(KnownEntid(a), v) (KnownEntid(a), v.val().unwrap())
}, },
_ => panic!("Incorrect query shape for 'av' helper."), _ => panic!("Incorrect query shape for 'av' helper."),
} }
} }
/// A helper to turn rows from `[:find ?e ?v :where [?e ?a ?v]]` into a tuple. /// A helper to turn rows from `[:find ?e ?v :where [?e ?a ?v]]` into a tuple.
fn ev(row: Vec<TypedValue>) -> (KnownEntid, TypedValue) { /// Panics if any of the values are maps or vecs.
fn ev(row: Vec<Binding>) -> (KnownEntid, TypedValue) {
// This happens to be the same as `av`. // This happens to be the same as `av`.
av(row) av(row)
} }
@ -382,7 +385,7 @@ fn height_of_person(in_progress: &InProgress, name: &str) -> Option<i64> {
.into_scalar_result() .into_scalar_result()
.expect("result"); .expect("result");
match h { match h {
Some(TypedValue::Long(v)) => Some(v), Some(Binding::Scalar(TypedValue::Long(v))) => Some(v),
_ => None, _ => None,
} }
} }
@ -585,7 +588,7 @@ fn test_upgrade_with_functions() {
:order (asc ?year)]"#, None) :order (asc ?year)]"#, None)
.into_coll_result() .into_coll_result()
.expect("coll"); .expect("coll");
assert_eq!(years, vec![TypedValue::Long(1984), TypedValue::Long(2019)]); assert_eq!(years, vec![Binding::Scalar(TypedValue::Long(1984)), Binding::Scalar(TypedValue::Long(2019))]);
in_progress.commit().expect("commit succeeded"); in_progress.commit().expect("commit succeeded");
} }
@ -605,7 +608,7 @@ fn test_upgrade_with_functions() {
.into_iter() { .into_iter() {
let mut row = row.into_iter(); let mut row = row.into_iter();
match (row.next(), row.next()) { match (row.next(), row.next()) {
(Some(TypedValue::Ref(person)), Some(TypedValue::Long(height))) => { (Some(Binding::Scalar(TypedValue::Ref(person))), Some(Binding::Scalar(TypedValue::Long(height)))) => {
// No need to explicitly retract: cardinality-one. // No need to explicitly retract: cardinality-one.
builder.add(KnownEntid(person), person_height, TypedValue::Long(inches_to_cm(height)))?; builder.add(KnownEntid(person), person_height, TypedValue::Long(inches_to_cm(height)))?;
}, },
@ -674,7 +677,7 @@ fn test_upgrade_with_functions() {
.into_iter() { .into_iter() {
let mut row = row.into_iter(); let mut row = row.into_iter();
match (row.next(), row.next()) { match (row.next(), row.next()) {
(Some(TypedValue::Ref(food)), Some(TypedValue::String(name))) => { (Some(Binding::Scalar(TypedValue::Ref(food))), Some(Binding::Scalar(TypedValue::String(name)))) => {
if name.chars().any(|c| !c.is_lowercase()) { if name.chars().any(|c| !c.is_lowercase()) {
let lowercased = name.to_lowercase(); let lowercased = name.to_lowercase();
println!("Need to rename {} from '{}' to '{}'", food, name, lowercased); println!("Need to rename {} from '{}' to '{}'", food, name, lowercased);
@ -707,7 +710,7 @@ fn test_upgrade_with_functions() {
.into_iter() { .into_iter() {
let mut row = row.into_iter(); let mut row = row.into_iter();
match (row.next(), row.next()) { match (row.next(), row.next()) {
(Some(TypedValue::Ref(left)), Some(TypedValue::Ref(right))) => { (Some(Binding::Scalar(TypedValue::Ref(left))), Some(Binding::Scalar(TypedValue::Ref(right)))) => {
let keep = KnownEntid(left); let keep = KnownEntid(left);
let replace = KnownEntid(right); let replace = KnownEntid(right);
@ -831,7 +834,7 @@ fn test_upgrade_with_functions() {
[?f :food/name ?food]]"#, None) [?f :food/name ?food]]"#, None)
.into_coll_result() .into_coll_result()
.expect("success"), .expect("success"),
vec![TypedValue::typed_string("spice")]); vec![TypedValue::typed_string("spice").into()]);
// //
// Migration 4: multi-definition migration. // Migration 4: multi-definition migration.
@ -1032,7 +1035,7 @@ fn test_upgrade_with_functions() {
[?p :person/height ?height] [?p :person/height ?height]
[?p :person/name ?name]]"#; [?p :person/name ?name]]"#;
let r = store.q_once(q, None).into_rel_result().unwrap(); let r = store.q_once(q, None).into_rel_result().unwrap();
let expected: RelResult = let expected: RelResult<Binding> =
vec![vec![TypedValue::typed_string("Sam"), TypedValue::Long(162)], vec![vec![TypedValue::typed_string("Sam"), TypedValue::Long(162)],
vec![TypedValue::typed_string("Beth"), TypedValue::Long(172)]].into(); vec![TypedValue::typed_string("Beth"), TypedValue::Long(172)]].into();
assert_eq!(expected, r); assert_eq!(expected, r);
@ -1045,6 +1048,8 @@ fn test_upgrade_with_functions() {
[?p :food/likes ?f] [?p :food/likes ?f]
[?f :food/name ?food]]"#; [?f :food/name ?food]]"#;
let r = store.q_once(q, None).into_coll_result().unwrap(); let r = store.q_once(q, None).into_coll_result().unwrap();
assert_eq!(vec![TypedValue::typed_string("spice"), TypedValue::typed_string("weird blue worms")], let expected: Vec<Binding> =
r); vec![TypedValue::typed_string("spice").into(),
TypedValue::typed_string("weird blue worms").into()];
assert_eq!(expected, r);
} }

View file

@ -31,6 +31,7 @@ use mentat::{
QueryOutput, QueryOutput,
QueryResults, QueryResults,
Store, Store,
Binding,
Syncable, Syncable,
TxReport, TxReport,
TypedValue, TypedValue,
@ -422,14 +423,14 @@ impl Repl {
match query_output.results { match query_output.results {
QueryResults::Scalar(v) => { QueryResults::Scalar(v) => {
if let Some(val) = v { if let Some(val) = v {
writeln!(output, "| {}\t |", &self.typed_value_as_string(val))?; writeln!(output, "| {}\t |", &self.binding_as_string(val))?;
} }
}, },
QueryResults::Tuple(vv) => { QueryResults::Tuple(vv) => {
if let Some(vals) = vv { if let Some(vals) = vv {
for val in vals { for val in vals {
write!(output, "| {}\t", self.typed_value_as_string(val))?; write!(output, "| {}\t", self.binding_as_string(val))?;
} }
writeln!(output, "|")?; writeln!(output, "|")?;
} }
@ -437,14 +438,14 @@ impl Repl {
QueryResults::Coll(vv) => { QueryResults::Coll(vv) => {
for val in vv { for val in vv {
writeln!(output, "| {}\t|", self.typed_value_as_string(val))?; writeln!(output, "| {}\t|", self.binding_as_string(val))?;
} }
}, },
QueryResults::Rel(vvv) => { QueryResults::Rel(vvv) => {
for vv in vvv { for vv in vvv {
for v in vv { for v in vv {
write!(output, "| {}\t", self.typed_value_as_string(v))?; write!(output, "| {}\t", self.binding_as_string(v))?;
} }
writeln!(output, "|")?; writeln!(output, "|")?;
} }
@ -511,16 +512,26 @@ impl Repl {
Ok(report) Ok(report)
} }
fn typed_value_as_string(&self, value: TypedValue) -> String { fn binding_as_string(&self, value: Binding) -> String {
use self::Binding::*;
match value { match value {
TypedValue::Boolean(b) => if b { "true".to_string() } else { "false".to_string() }, Scalar(v) => self.value_as_string(v),
TypedValue::Double(d) => format!("{}", d), Map(_) => format!("TODO"),
TypedValue::Instant(i) => format!("{}", i), Vec(_) => format!("TODO"),
TypedValue::Keyword(k) => format!("{}", k), }
TypedValue::Long(l) => format!("{}", l), }
TypedValue::Ref(r) => format!("{}", r),
TypedValue::String(s) => format!("{:?}", s.to_string()), fn value_as_string(&self, value: TypedValue) -> String {
TypedValue::Uuid(u) => format!("{}", u), use self::TypedValue::*;
match value {
Boolean(b) => if b { "true".to_string() } else { "false".to_string() },
Double(d) => format!("{}", d),
Instant(i) => format!("{}", i),
Keyword(k) => format!("{}", k),
Long(l) => format!("{}", l),
Ref(r) => format!("{}", r),
String(s) => format!("{:?}", s.to_string()),
Uuid(u) => format!("{}", u),
} }
} }
} }