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>",
]
name = "mentat"
version = "0.6.2"
version = "0.7.0"
build = "build/version.rs"
[features]

View file

@ -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"] }

View file

@ -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,

View file

@ -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));

View file

@ -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() {

View file

@ -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 {

View file

@ -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:

View file

@ -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 {

View file

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

View file

@ -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())
}
}

View file

@ -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]);
}

View file

@ -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,
}
});

View file

@ -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),
};

View file

@ -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)) => {