Add 'Binding', a structured value type to return from queries. (#657) r=nalexander
Bump to 0.7: breaking change.
This commit is contained in:
parent
1818e0b98e
commit
a2e13f624c
16 changed files with 396 additions and 166 deletions
|
@ -12,7 +12,7 @@ authors = [
|
||||||
"Thom Chiovoloni <tchiovoloni@mozilla.com>",
|
"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]
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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:
|
||||||
|
|
19
src/conn.rs
19
src/conn.rs
|
@ -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 {
|
||||||
|
|
|
@ -41,6 +41,7 @@ pub use mentat_core::{
|
||||||
KnownEntid,
|
KnownEntid,
|
||||||
NamespacedKeyword,
|
NamespacedKeyword,
|
||||||
Schema,
|
Schema,
|
||||||
|
Binding,
|
||||||
TypedValue,
|
TypedValue,
|
||||||
Uuid,
|
Uuid,
|
||||||
Utc,
|
Utc,
|
||||||
|
|
27
src/query.rs
27
src/query.rs
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
@ -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),
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue