Breathe life back into this project. #1

Merged
gburd merged 22 commits from gburd/2018edition-fmt-fix-deps into master 2020-01-16 16:27:21 +00:00
145 changed files with 17738 additions and 12352 deletions

1
.ignore Normal file
View file

@ -0,0 +1 @@
docs/

View file

@ -1,4 +1,5 @@
[package] [package]
edition = "2018"
authors = [ authors = [
"Richard Newman <rnewman@twinql.com>", "Richard Newman <rnewman@twinql.com>",
"Nicholas Alexander <nalexander@mozilla.com>", "Nicholas Alexander <nalexander@mozilla.com>",
@ -12,7 +13,7 @@ authors = [
"Thom Chiovoloni <tchiovoloni@mozilla.com>", "Thom Chiovoloni <tchiovoloni@mozilla.com>",
] ]
name = "mentat" name = "mentat"
version = "0.11.1" version = "0.11.2"
build = "build/version.rs" build = "build/version.rs"
[features] [features]
@ -29,14 +30,15 @@ rustc_version = "0.2"
[dependencies] [dependencies]
chrono = "0.4" chrono = "0.4"
failure = "0.1.1" failure = "0.1.6"
lazy_static = "0.2" lazy_static = "1.4.0"
time = "0.1" time = "0.2"
log = "0.4" log = "0.4"
uuid = { version = "0.5", features = ["v4", "serde"] } uuid = { version = "0.8", features = ["v4", "serde"] }
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.13" version = "0.21.0"
# System sqlite might be very old. # System sqlite might be very old.
features = ["limits"] features = ["limits"]

View file

@ -1,4 +1,4 @@
# UNMAINTAINED Project Mentat # Project Mentat
[![Build Status](https://travis-ci.org/mozilla/mentat.svg?branch=master)](https://travis-ci.org/mozilla/mentat) [![Build Status](https://travis-ci.org/mozilla/mentat.svg?branch=master)](https://travis-ci.org/mozilla/mentat)
**Project Mentat is [no longer being developed or actively maintained by Mozilla](https://mail.mozilla.org/pipermail/firefox-dev/2018-September/006780.html).** This repository will be marked read-only in the near future. You are, of course, welcome to fork the repository and use the existing code. **Project Mentat is [no longer being developed or actively maintained by Mozilla](https://mail.mozilla.org/pipermail/firefox-dev/2018-September/006780.html).** This repository will be marked read-only in the near future. You are, of course, welcome to fork the repository and use the existing code.
@ -17,7 +17,7 @@ The Rust implementation gives us a smaller compiled output, better performance,
## Motivation ## Motivation
Mentat is intended to be a flexible relational (not key-value, not document-oriented) store that makes it easy to describe, grow, and reuse your domain schema. Mentat is a flexible relational (not key-value, not document-oriented) store that makes it easy to describe, grow, and reuse your domain schema.
By abstracting away the storage schema, and by exposing change listeners outside the database (not via triggers), we hope to make domain schemas stable, and allow both the data store itself and embedding applications to use better architectures, meeting performance goals in a way that allows future evolution. By abstracting away the storage schema, and by exposing change listeners outside the database (not via triggers), we hope to make domain schemas stable, and allow both the data store itself and embedding applications to use better architectures, meeting performance goals in a way that allows future evolution.

View file

@ -8,24 +8,25 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate rustc_version; use rustc_version::{version, Version};
use std::io::{self, Write}; use std::io::{self, Write};
use std::process::exit; use std::process::exit;
use rustc_version::{
Version,
version,
};
/// MIN_VERSION should be changed when there's a new minimum version of rustc required /// MIN_VERSION should be changed when there's a new minimum version of rustc required
/// to build the project. /// to build the project.
static MIN_VERSION: &'static str = "1.25.0"; static MIN_VERSION: &'static str = "1.41.0";
fn main() { fn main() {
let ver = version().unwrap(); let ver = version().unwrap();
let min = Version::parse(MIN_VERSION).unwrap(); let min = Version::parse(MIN_VERSION).unwrap();
if ver < min { if ver < min {
writeln!(&mut io::stderr(), "Mentat requires rustc {} or higher.", MIN_VERSION).unwrap(); writeln!(
&mut io::stderr(),
"Mentat requires rustc {} or higher, you were using version {}.",
MIN_VERSION,
ver
)
.unwrap();
exit(1); exit(1);
} }
} }

View file

@ -9,11 +9,11 @@ path = "lib.rs"
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
enum-set = "0.0.7" enum-set = "0.0.8"
lazy_static = "0.2" lazy_static = "1.4.0"
indexmap = "1" indexmap = "1"
ordered-float = { version = "0.5", features = ["serde"] } ordered-float = { version = "1.0.2", features = ["serde"] }
uuid = { version = "0.5", features = ["v4", "serde"] } uuid = { version = "0.8", features = ["v4", "serde"] }
serde = { version = "1.0", features = ["rc"] } serde = { version = "1.0", features = ["rc"] }
serde_derive = "1.0" serde_derive = "1.0"

View file

@ -8,84 +8,57 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate enum_set;
extern crate ordered_float;
extern crate chrono; extern crate chrono;
extern crate enum_set;
extern crate indexmap; extern crate indexmap;
#[macro_use] extern crate serde_derive; extern crate ordered_float;
extern crate uuid; #[macro_use]
extern crate serde_derive;
extern crate edn; extern crate edn;
extern crate uuid;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
use std::fmt; use std::fmt;
use std::ffi::{ use std::ffi::CString;
CString,
};
use std::ops::{ use std::ops::Deref;
Deref,
};
use std::os::raw::{ use std::os::raw::c_char;
c_char,
};
use std::rc::{ use std::rc::Rc;
Rc,
};
use std::sync::{ use std::sync::Arc;
Arc,
};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use indexmap::{ use indexmap::IndexMap;
IndexMap,
};
use enum_set::EnumSet; use enum_set::EnumSet;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use chrono::{ use chrono::{DateTime, Timelike};
DateTime,
Timelike,
};
use uuid::Uuid; use uuid::Uuid;
use edn::{ use edn::{Cloned, FromMicros, FromRc, Keyword, Utc, ValueRc};
Cloned,
ValueRc,
Utc,
Keyword,
FromMicros,
FromRc,
};
use edn::entities::{ use edn::entities::{
AttributePlace, AttributePlace, EntidOrIdent, EntityPlace, TransactableValueMarker, ValuePlace,
EntityPlace,
EntidOrIdent,
ValuePlace,
TransactableValueMarker,
}; };
pub mod values;
mod value_type_set; mod value_type_set;
pub mod values;
pub use value_type_set::{ pub use value_type_set::ValueTypeSet;
ValueTypeSet,
};
#[macro_export] #[macro_export]
macro_rules! bail { macro_rules! bail {
($e:expr) => ( ($e:expr) => {
return Err($e.into()); return Err($e.into());
) };
} }
/// Represents one entid in the entid space. /// Represents one entid in the entid space.
@ -129,16 +102,14 @@ impl<V: TransactableValueMarker> Into<ValuePlace<V>> for KnownEntid {
/// When moving to a more concrete table, such as `datoms`, they are expanded out /// When moving to a more concrete table, such as `datoms`, they are expanded out
/// via these flags and put into their own column rather than a bit field. /// via these flags and put into their own column rather than a bit field.
pub enum AttributeBitFlags { pub enum AttributeBitFlags {
IndexAVET = 1 << 0, IndexAVET = 1 << 0,
IndexVAET = 1 << 1, IndexVAET = 1 << 1,
IndexFulltext = 1 << 2, IndexFulltext = 1 << 2,
UniqueValue = 1 << 3, UniqueValue = 1 << 3,
} }
pub mod attribute { pub mod attribute {
use ::{ use TypedValue;
TypedValue,
};
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum Unique { pub enum Unique {
@ -161,7 +132,7 @@ pub mod attribute {
/// with the attribute are interpreted. /// with the attribute are interpreted.
/// ///
/// TODO: consider packing this into a bitfield or similar. /// TODO: consider packing this into a bitfield or similar.
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct Attribute { pub struct Attribute {
/// The associated value type, i.e., `:db/valueType`? /// The associated value type, i.e., `:db/valueType`?
pub value_type: ValueType, pub value_type: ValueType,
@ -234,13 +205,30 @@ impl Attribute {
attribute_map.insert(values::DB_IDENT.clone(), edn::Value::Keyword(ident)); attribute_map.insert(values::DB_IDENT.clone(), edn::Value::Keyword(ident));
} }
attribute_map.insert(values::DB_VALUE_TYPE.clone(), self.value_type.into_edn_value()); attribute_map.insert(
values::DB_VALUE_TYPE.clone(),
self.value_type.into_edn_value(),
);
attribute_map.insert(values::DB_CARDINALITY.clone(), if self.multival { values::DB_CARDINALITY_MANY.clone() } else { values::DB_CARDINALITY_ONE.clone() }); attribute_map.insert(
values::DB_CARDINALITY.clone(),
if self.multival {
values::DB_CARDINALITY_MANY.clone()
} else {
values::DB_CARDINALITY_ONE.clone()
},
);
match self.unique { match self.unique {
Some(attribute::Unique::Value) => { attribute_map.insert(values::DB_UNIQUE.clone(), values::DB_UNIQUE_VALUE.clone()); }, Some(attribute::Unique::Value) => {
Some(attribute::Unique::Identity) => { attribute_map.insert(values::DB_UNIQUE.clone(), values::DB_UNIQUE_IDENTITY.clone()); }, attribute_map.insert(values::DB_UNIQUE.clone(), values::DB_UNIQUE_VALUE.clone());
}
Some(attribute::Unique::Identity) => {
attribute_map.insert(
values::DB_UNIQUE.clone(),
values::DB_UNIQUE_IDENTITY.clone(),
);
}
None => (), None => (),
} }
@ -310,7 +298,6 @@ impl ValueType {
} }
} }
impl ::enum_set::CLike for ValueType { impl ::enum_set::CLike for ValueType {
fn to_u32(&self) -> u32 { fn to_u32(&self) -> u32 {
*self as u32 *self as u32
@ -323,16 +310,19 @@ impl ::enum_set::CLike for ValueType {
impl ValueType { impl ValueType {
pub fn into_keyword(self) -> Keyword { pub fn into_keyword(self) -> Keyword {
Keyword::namespaced("db.type", match self { Keyword::namespaced(
ValueType::Ref => "ref", "db.type",
ValueType::Boolean => "boolean", match self {
ValueType::Instant => "instant", ValueType::Ref => "ref",
ValueType::Long => "long", ValueType::Boolean => "boolean",
ValueType::Double => "double", ValueType::Instant => "instant",
ValueType::String => "string", ValueType::Long => "long",
ValueType::Keyword => "keyword", ValueType::Double => "double",
ValueType::Uuid => "uuid", ValueType::String => "string",
}) ValueType::Keyword => "keyword",
ValueType::Uuid => "uuid",
},
)
} }
pub fn from_keyword(keyword: &Keyword) -> Option<Self> { pub fn from_keyword(keyword: &Keyword) -> Option<Self> {
@ -350,20 +340,23 @@ impl ValueType {
"keyword" => Some(ValueType::Keyword), "keyword" => Some(ValueType::Keyword),
"uuid" => Some(ValueType::Uuid), "uuid" => Some(ValueType::Uuid),
_ => None, _ => None,
} };
} }
pub fn into_typed_value(self) -> TypedValue { pub fn into_typed_value(self) -> TypedValue {
TypedValue::typed_ns_keyword("db.type", match self { TypedValue::typed_ns_keyword(
ValueType::Ref => "ref", "db.type",
ValueType::Boolean => "boolean", match self {
ValueType::Instant => "instant", ValueType::Ref => "ref",
ValueType::Long => "long", ValueType::Boolean => "boolean",
ValueType::Double => "double", ValueType::Instant => "instant",
ValueType::String => "string", ValueType::Long => "long",
ValueType::Keyword => "keyword", ValueType::Double => "double",
ValueType::Uuid => "uuid", ValueType::String => "string",
}) ValueType::Keyword => "keyword",
ValueType::Uuid => "uuid",
},
)
} }
pub fn into_edn_value(self) -> edn::Value { pub fn into_edn_value(self) -> edn::Value {
@ -382,23 +375,27 @@ impl ValueType {
pub fn is_numeric(&self) -> bool { pub fn is_numeric(&self) -> bool {
match self { match self {
&ValueType::Long | &ValueType::Double => true, &ValueType::Long | &ValueType::Double => true,
_ => false _ => false,
} }
} }
} }
impl fmt::Display for ValueType { impl fmt::Display for ValueType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match *self { write!(
ValueType::Ref => ":db.type/ref", f,
ValueType::Boolean => ":db.type/boolean", "{}",
ValueType::Instant => ":db.type/instant", match *self {
ValueType::Long => ":db.type/long", ValueType::Ref => ":db.type/ref",
ValueType::Double => ":db.type/double", ValueType::Boolean => ":db.type/boolean",
ValueType::String => ":db.type/string", ValueType::Instant => ":db.type/instant",
ValueType::Keyword => ":db.type/keyword", ValueType::Long => ":db.type/long",
ValueType::Uuid => ":db.type/uuid", ValueType::Double => ":db.type/double",
}) ValueType::String => ":db.type/string",
ValueType::Keyword => ":db.type/keyword",
ValueType::Uuid => ":db.type/uuid",
}
)
} }
} }
@ -415,11 +412,11 @@ pub enum TypedValue {
Boolean(bool), Boolean(bool),
Long(i64), Long(i64),
Double(OrderedFloat<f64>), Double(OrderedFloat<f64>),
Instant(DateTime<Utc>), // Use `into()` to ensure truncation. Instant(DateTime<Utc>), // Use `into()` to ensure truncation.
// TODO: &str throughout? // TODO: &str throughout?
String(ValueRc<String>), String(ValueRc<String>),
Keyword(ValueRc<Keyword>), Keyword(ValueRc<Keyword>),
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.
} }
impl From<KnownEntid> for TypedValue { impl From<KnownEntid> for TypedValue {
@ -552,7 +549,7 @@ impl TypedValue {
// Return a C-owned pointer. // Return a C-owned pointer.
Some(c.into_raw()) Some(c.into_raw())
}, }
_ => None, _ => None,
} }
} }
@ -568,7 +565,7 @@ impl TypedValue {
// Return a C-owned pointer. // Return a C-owned pointer.
Some(c.into_raw()) Some(c.into_raw())
}, }
_ => None, _ => None,
} }
} }
@ -577,14 +574,14 @@ impl TypedValue {
match self { match self {
TypedValue::Uuid(v) => { TypedValue::Uuid(v) => {
// Get an independent copy of the string. // Get an independent copy of the string.
let s: String = v.hyphenated().to_string(); let s: String = v.to_hyphenated().to_string();
// Make a CString out of the new bytes. // Make a CString out of the new bytes.
let c: CString = CString::new(s).expect("String conversion failed!"); let c: CString = CString::new(s).expect("String conversion failed!");
// Return a C-owned pointer. // Return a C-owned pointer.
Some(c.into_raw()) Some(c.into_raw())
}, }
_ => None, _ => None,
} }
} }
@ -598,7 +595,7 @@ impl TypedValue {
pub fn into_uuid_string(self) -> Option<String> { pub fn into_uuid_string(self) -> Option<String> {
match self { match self {
TypedValue::Uuid(v) => Some(v.hyphenated().to_string()), TypedValue::Uuid(v) => Some(v.to_hyphenated().to_string()),
_ => None, _ => None,
} }
} }
@ -709,7 +706,6 @@ impl MicrosecondPrecision for DateTime<Utc> {
} }
} }
/// The values bound in a query specification can be: /// The values bound in a query specification can be:
/// ///
/// * Vecs of structured values, for multi-valued component attributes or nested expressions. /// * Vecs of structured values, for multi-valued component attributes or nested expressions.
@ -729,7 +725,10 @@ pub enum Binding {
Map(ValueRc<StructuredMap>), Map(ValueRc<StructuredMap>),
} }
impl<T> From<T> for Binding where T: Into<TypedValue> { impl<T> From<T> for Binding
where
T: Into<TypedValue>,
{
fn from(value: T) -> Self { fn from(value: T) -> Self {
Binding::Scalar(value.into()) Binding::Scalar(value.into())
} }
@ -813,7 +812,11 @@ impl Deref for StructuredMap {
} }
impl StructuredMap { impl StructuredMap {
pub fn insert<N, B>(&mut self, name: N, value: B) where N: Into<ValueRc<Keyword>>, B: Into<Binding> { pub fn insert<N, B>(&mut self, name: N, value: B)
where
N: Into<ValueRc<Keyword>>,
B: Into<Binding>,
{
self.0.insert(name.into(), value.into()); self.0.insert(name.into(), value.into());
} }
} }
@ -825,7 +828,10 @@ impl From<IndexMap<ValueRc<Keyword>, Binding>> for StructuredMap {
} }
// Mostly for testing. // Mostly for testing.
impl<T> From<Vec<(Keyword, T)>> for StructuredMap where T: Into<Binding> { impl<T> From<Vec<(Keyword, T)>> for StructuredMap
where
T: Into<Binding>,
{
fn from(value: Vec<(Keyword, T)>) -> Self { fn from(value: Vec<(Keyword, T)>) -> Self {
let mut sm = StructuredMap::default(); let mut sm = StructuredMap::default();
for (k, v) in value.into_iter() { for (k, v) in value.into_iter() {
@ -936,7 +942,7 @@ impl Binding {
pub fn into_uuid_string(self) -> Option<String> { pub fn into_uuid_string(self) -> Option<String> {
match self { match self {
Binding::Scalar(TypedValue::Uuid(v)) => Some(v.hyphenated().to_string()), Binding::Scalar(TypedValue::Uuid(v)) => Some(v.to_hyphenated().to_string()),
_ => None, _ => None,
} }
} }

View file

@ -8,13 +8,9 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use enum_set::{ use enum_set::EnumSet;
EnumSet,
};
use ::{ use ValueType;
ValueType,
};
trait EnumSetExtensions<T: ::enum_set::CLike + Clone> { trait EnumSetExtensions<T: ::enum_set::CLike + Clone> {
/// Return a set containing both `x` and `y`. /// Return a set containing both `x` and `y`.
@ -41,7 +37,6 @@ impl<T: ::enum_set::CLike + Clone> EnumSetExtensions<T> for EnumSet<T> {
} }
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ValueTypeSet(pub EnumSet<ValueType>); pub struct ValueTypeSet(pub EnumSet<ValueType>);

View file

@ -10,12 +10,11 @@
#![allow(dead_code)] #![allow(dead_code)]
use edn::symbols;
/// Literal `Value` instances in the the "db" namespace. /// Literal `Value` instances in the the "db" namespace.
/// ///
/// Used through-out the transactor to match core DB constructs. /// Used through-out the transactor to match core DB constructs.
use edn::types::Value; use edn::types::Value;
use edn::symbols;
/// Declare a lazy static `ident` of type `Value::Keyword` with the given `namespace` and /// Declare a lazy static `ident` of type `Value::Keyword` with the given `namespace` and
/// `name`. /// `name`.

View file

@ -5,11 +5,11 @@ workspace = ".."
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
enum-set = "0.0.7" enum-set = "0.0.8"
failure = "0.1.1" failure = "0.1.1"
indexmap = "1" indexmap = "1"
ordered-float = { version = "0.5", features = ["serde"] } ordered-float = { version = "1.0.2", features = ["serde"] }
uuid = { version = "0.5", features = ["v4", "serde"] } uuid = { version = "0.8", features = ["v4", "serde"] }
[dependencies.core_traits] [dependencies.core_traits]
path = "../core-traits" path = "../core-traits"

View file

@ -9,34 +9,41 @@
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
/// Cache traits. /// Cache traits.
use std::collections::BTreeSet;
use std::collections::{ use core_traits::{Entid, TypedValue};
BTreeSet,
};
use core_traits::{ use Schema;
Entid,
TypedValue,
};
use ::{
Schema,
};
pub trait CachedAttributes { pub trait CachedAttributes {
fn is_attribute_cached_reverse(&self, entid: Entid) -> bool; fn is_attribute_cached_reverse(&self, entid: Entid) -> bool;
fn is_attribute_cached_forward(&self, entid: Entid) -> bool; fn is_attribute_cached_forward(&self, entid: Entid) -> bool;
fn has_cached_attributes(&self) -> bool; fn has_cached_attributes(&self) -> bool;
fn get_values_for_entid(&self, schema: &Schema, attribute: Entid, entid: Entid) -> Option<&Vec<TypedValue>>; fn get_values_for_entid(
fn get_value_for_entid(&self, schema: &Schema, attribute: Entid, entid: Entid) -> Option<&TypedValue>; &self,
schema: &Schema,
attribute: Entid,
entid: Entid,
) -> Option<&Vec<TypedValue>>;
fn get_value_for_entid(
&self,
schema: &Schema,
attribute: Entid,
entid: Entid,
) -> Option<&TypedValue>;
/// Reverse lookup. /// Reverse lookup.
fn get_entid_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<Entid>; fn get_entid_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<Entid>;
fn get_entids_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<&BTreeSet<Entid>>; fn get_entids_for_value(
&self,
attribute: Entid,
value: &TypedValue,
) -> Option<&BTreeSet<Entid>>;
} }
pub trait UpdateableCache<E> { pub trait UpdateableCache<E> {
fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), E> fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), E>
where I: Iterator<Item=(Entid, Entid, TypedValue)>; where
I: Iterator<Item = (Entid, Entid, TypedValue)>;
} }

View file

@ -19,11 +19,15 @@ pub struct RcCounter {
/// A simple shared counter. /// A simple shared counter.
impl RcCounter { impl RcCounter {
pub fn with_initial(value: usize) -> Self { pub fn with_initial(value: usize) -> Self {
RcCounter { c: Rc::new(Cell::new(value)) } RcCounter {
c: Rc::new(Cell::new(value)),
}
} }
pub fn new() -> Self { pub fn new() -> Self {
RcCounter { c: Rc::new(Cell::new(0)) } RcCounter {
c: Rc::new(Cell::new(0)),
}
} }
/// Return the next value in the sequence. /// Return the next value in the sequence.

View file

@ -19,63 +19,35 @@ extern crate core_traits;
extern crate edn; extern crate edn;
use core_traits::{ use core_traits::{Attribute, Entid, KnownEntid, ValueType};
Attribute,
Entid,
KnownEntid,
ValueType,
};
mod cache; mod cache;
use std::collections::{ use std::collections::BTreeMap;
BTreeMap,
};
pub use uuid::Uuid; pub use uuid::Uuid;
pub use chrono::{ pub use chrono::{
DateTime, DateTime,
Timelike, // For truncation. Timelike, // For truncation.
}; };
pub use edn::{ pub use edn::{Cloned, FromMicros, FromRc, Keyword, ToMicros, Utc, ValueRc};
Cloned,
FromMicros,
FromRc,
Keyword,
ToMicros,
Utc,
ValueRc,
};
pub use edn::parse::{ pub use edn::parse::parse_query;
parse_query,
};
pub use cache::{ pub use cache::{CachedAttributes, UpdateableCache};
CachedAttributes,
UpdateableCache,
};
mod sql_types;
mod tx_report;
/// Core types defining a Mentat knowledge base. /// Core types defining a Mentat knowledge base.
mod types; mod types;
mod tx_report;
mod sql_types;
pub use tx_report::{ pub use tx_report::TxReport;
TxReport,
};
pub use types::{ pub use types::ValueTypeTag;
ValueTypeTag,
};
pub use sql_types::{ pub use sql_types::{SQLTypeAffinity, SQLValueType, SQLValueTypeSet};
SQLTypeAffinity,
SQLValueType,
SQLValueTypeSet,
};
/// Map `Keyword` idents (`:db/ident`) to positive integer entids (`1`). /// Map `Keyword` idents (`:db/ident`) to positive integer entids (`1`).
pub type IdentMap = BTreeMap<Keyword, Entid>; pub type IdentMap = BTreeMap<Keyword, Entid>;
@ -119,15 +91,21 @@ pub struct Schema {
pub trait HasSchema { pub trait HasSchema {
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid>; fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid>;
fn get_ident<T>(&self, x: T) -> Option<&Keyword> where T: Into<Entid>; fn get_ident<T>(&self, x: T) -> Option<&Keyword>
where
T: Into<Entid>;
fn get_entid(&self, x: &Keyword) -> Option<KnownEntid>; fn get_entid(&self, x: &Keyword) -> Option<KnownEntid>;
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute> where T: Into<Entid>; fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute>
where
T: Into<Entid>;
// Returns the attribute and the entid named by the provided ident. // Returns the attribute and the entid named by the provided ident.
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)>; fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)>;
/// Return true if the provided entid identifies an attribute in this schema. /// Return true if the provided entid identifies an attribute in this schema.
fn is_attribute<T>(&self, x: T) -> bool where T: Into<Entid>; fn is_attribute<T>(&self, x: T) -> bool
where
T: Into<Entid>;
/// Return true if the provided ident identifies an attribute in this schema. /// Return true if the provided ident identifies an attribute in this schema.
fn identifies_attribute(&self, x: &Keyword) -> bool; fn identifies_attribute(&self, x: &Keyword) -> bool;
@ -137,17 +115,24 @@ pub trait HasSchema {
impl Schema { impl Schema {
pub fn new(ident_map: IdentMap, entid_map: EntidMap, attribute_map: AttributeMap) -> Schema { pub fn new(ident_map: IdentMap, entid_map: EntidMap, attribute_map: AttributeMap) -> Schema {
let mut s = Schema { ident_map, entid_map, attribute_map, component_attributes: Vec::new() }; let mut s = Schema {
ident_map,
entid_map,
attribute_map,
component_attributes: Vec::new(),
};
s.update_component_attributes(); s.update_component_attributes();
s s
} }
/// Returns an symbolic representation of the schema suitable for applying across Mentat stores. /// Returns an symbolic representation of the schema suitable for applying across Mentat stores.
pub fn to_edn_value(&self) -> edn::Value { pub fn to_edn_value(&self) -> edn::Value {
edn::Value::Vector((&self.attribute_map).iter() edn::Value::Vector(
.map(|(entid, attribute)| (&self.attribute_map)
attribute.to_edn_value(self.get_ident(*entid).cloned())) .iter()
.collect()) .map(|(entid, attribute)| attribute.to_edn_value(self.get_ident(*entid).cloned()))
.collect(),
)
} }
fn get_raw_entid(&self, x: &Keyword) -> Option<Entid> { fn get_raw_entid(&self, x: &Keyword) -> Option<Entid> {
@ -156,10 +141,11 @@ impl Schema {
pub fn update_component_attributes(&mut self) { pub fn update_component_attributes(&mut self) {
let mut components: Vec<Entid>; let mut components: Vec<Entid>;
components = self.attribute_map components = self
.iter() .attribute_map
.filter_map(|(k, v)| if v.component { Some(*k) } else { None }) .iter()
.collect(); .filter_map(|(k, v)| if v.component { Some(*k) } else { None })
.collect();
components.sort_unstable(); components.sort_unstable();
self.component_attributes = components; self.component_attributes = components;
} }
@ -171,7 +157,10 @@ impl HasSchema for Schema {
self.get_entid(&t.into_keyword()) self.get_entid(&t.into_keyword())
} }
fn get_ident<T>(&self, x: T) -> Option<&Keyword> where T: Into<Entid> { fn get_ident<T>(&self, x: T) -> Option<&Keyword>
where
T: Into<Entid>,
{
self.entid_map.get(&x.into()) self.entid_map.get(&x.into())
} }
@ -179,25 +168,33 @@ impl HasSchema for Schema {
self.get_raw_entid(x).map(KnownEntid) self.get_raw_entid(x).map(KnownEntid)
} }
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute> where T: Into<Entid> { fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute>
where
T: Into<Entid>,
{
self.attribute_map.get(&x.into()) self.attribute_map.get(&x.into())
} }
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)> { fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)> {
self.get_raw_entid(&ident) self.get_raw_entid(&ident).and_then(|entid| {
.and_then(|entid| { self.attribute_for_entid(entid)
self.attribute_for_entid(entid).map(|a| (a, KnownEntid(entid))) .map(|a| (a, KnownEntid(entid)))
}) })
} }
/// Return true if the provided entid identifies an attribute in this schema. /// Return true if the provided entid identifies an attribute in this schema.
fn is_attribute<T>(&self, x: T) -> bool where T: Into<Entid> { fn is_attribute<T>(&self, x: T) -> bool
where
T: Into<Entid>,
{
self.attribute_map.contains_key(&x.into()) self.attribute_map.contains_key(&x.into())
} }
/// Return true if the provided ident identifies an attribute in this schema. /// Return true if the provided ident identifies an attribute in this schema.
fn identifies_attribute(&self, x: &Keyword) -> bool { fn identifies_attribute(&self, x: &Keyword) -> bool {
self.get_raw_entid(x).map(|e| self.is_attribute(e)).unwrap_or(false) self.get_raw_entid(x)
.map(|e| self.is_attribute(e))
.unwrap_or(false)
} }
fn component_attributes(&self) -> &[Entid] { fn component_attributes(&self) -> &[Entid] {
@ -228,7 +225,7 @@ pub mod util;
macro_rules! interpose { macro_rules! interpose {
( $name: pat, $across: expr, $body: block, $inter: block ) => { ( $name: pat, $across: expr, $body: block, $inter: block ) => {
interpose_iter!($name, $across.iter(), $body, $inter) interpose_iter!($name, $across.iter(), $body, $inter)
} };
} }
/// A helper to bind `name` to values in `across`, running `body` for each value, /// A helper to bind `name` to values in `across`, running `body` for each value,
@ -244,7 +241,7 @@ macro_rules! interpose_iter {
$body; $body;
} }
} }
} };
} }
#[cfg(test)] #[cfg(test)]
@ -253,10 +250,7 @@ mod test {
use std::str::FromStr; use std::str::FromStr;
use core_traits::{ use core_traits::{attribute, TypedValue};
attribute,
TypedValue,
};
fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) { fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) {
schema.entid_map.insert(e, i.clone()); schema.entid_map.insert(e, i.clone());
@ -269,8 +263,10 @@ mod test {
#[test] #[test]
fn test_datetime_truncation() { fn test_datetime_truncation() {
let dt: DateTime<Utc> = DateTime::from_str("2018-01-11T00:34:09.273457004Z").expect("parsed"); let dt: DateTime<Utc> =
let expected: DateTime<Utc> = DateTime::from_str("2018-01-11T00:34:09.273457Z").expect("parsed"); DateTime::from_str("2018-01-11T00:34:09.273457004Z").expect("parsed");
let expected: DateTime<Utc> =
DateTime::from_str("2018-01-11T00:34:09.273457Z").expect("parsed");
let tv: TypedValue = dt.into(); let tv: TypedValue = dt.into();
if let TypedValue::Instant(roundtripped) = tv { if let TypedValue::Instant(roundtripped) = tv {
@ -338,7 +334,9 @@ mod test {
:db/cardinality :db.cardinality/one :db/cardinality :db.cardinality/one
:db/unique :db.unique/identity :db/unique :db.unique/identity
:db/isComponent true }, ]"#; :db/isComponent true }, ]"#;
let expected_value = edn::parse::value(&expected_output).expect("to be able to parse").without_spans(); let expected_value = edn::parse::value(&expected_output)
.expect("to be able to parse")
.without_spans();
assert_eq!(expected_value, value); assert_eq!(expected_value, value);
// let's compare the whole thing again, just to make sure we are not changing anything when we convert to edn. // let's compare the whole thing again, just to make sure we are not changing anything when we convert to edn.

View file

@ -8,18 +8,11 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use std::collections::{ use std::collections::BTreeSet;
BTreeSet,
};
use core_traits::{ use core_traits::{ValueType, ValueTypeSet};
ValueType,
ValueTypeSet,
};
use types::{ use types::ValueTypeTag;
ValueTypeTag,
};
/// Type safe representation of the possible return values from SQLite's `typeof` /// Type safe representation of the possible return values from SQLite's `typeof`
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
@ -48,15 +41,15 @@ pub trait SQLValueType {
impl SQLValueType for ValueType { impl SQLValueType for ValueType {
fn sql_representation(&self) -> (ValueTypeTag, Option<SQLTypeAffinity>) { fn sql_representation(&self) -> (ValueTypeTag, Option<SQLTypeAffinity>) {
match *self { match *self {
ValueType::Ref => (0, None), ValueType::Ref => (0, None),
ValueType::Boolean => (1, None), ValueType::Boolean => (1, None),
ValueType::Instant => (4, None), ValueType::Instant => (4, None),
// SQLite distinguishes integral from decimal types, allowing long and double to share a tag. // SQLite distinguishes integral from decimal types, allowing long and double to share a tag.
ValueType::Long => (5, Some(SQLTypeAffinity::Integer)), ValueType::Long => (5, Some(SQLTypeAffinity::Integer)),
ValueType::Double => (5, Some(SQLTypeAffinity::Real)), ValueType::Double => (5, Some(SQLTypeAffinity::Real)),
ValueType::String => (10, None), ValueType::String => (10, None),
ValueType::Uuid => (11, None), ValueType::Uuid => (11, None),
ValueType::Keyword => (13, None), ValueType::Keyword => (13, None),
} }
} }
@ -71,13 +64,13 @@ impl SQLValueType for ValueType {
fn accommodates_integer(&self, int: i64) -> bool { fn accommodates_integer(&self, int: i64) -> bool {
use ValueType::*; use ValueType::*;
match *self { match *self {
Instant => false, // Always use #inst. Instant => false, // Always use #inst.
Long | Double => true, Long | Double => true,
Ref => int >= 0, Ref => int >= 0,
Boolean => (int == 0) || (int == 1), Boolean => (int == 0) || (int == 1),
ValueType::String => false, ValueType::String => false,
Keyword => false, Keyword => false,
Uuid => false, Uuid => false,
} }
} }
} }
@ -130,12 +123,8 @@ impl SQLValueTypeSet for ValueTypeSet {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use core_traits::{ use core_traits::ValueType;
ValueType, use sql_types::SQLValueType;
};
use sql_types::{
SQLValueType,
};
#[test] #[test]
fn test_accommodates_integer() { fn test_accommodates_integer() {

View file

@ -10,18 +10,11 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::collections::{ use std::collections::BTreeMap;
BTreeMap,
};
use core_traits::{ use core_traits::Entid;
Entid,
};
use ::{ use {DateTime, Utc};
DateTime,
Utc,
};
/// A transaction report summarizes an applied transaction. /// A transaction report summarizes an applied transaction.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]

View file

@ -67,7 +67,8 @@ pub enum Either<L, R> {
// Cribbed from https://github.com/bluss/either/blob/f793721f3fdeb694f009e731b23a2858286bc0d6/src/lib.rs#L219-L259. // Cribbed from https://github.com/bluss/either/blob/f793721f3fdeb694f009e731b23a2858286bc0d6/src/lib.rs#L219-L259.
impl<L, R> Either<L, R> { impl<L, R> Either<L, R> {
pub fn map_left<F, M>(self, f: F) -> Either<M, R> pub fn map_left<F, M>(self, f: F) -> Either<M, R>
where F: FnOnce(L) -> M where
F: FnOnce(L) -> M,
{ {
use self::Either::*; use self::Either::*;
match self { match self {
@ -77,7 +78,8 @@ impl<L, R> Either<L, R> {
} }
pub fn map_right<F, S>(self, f: F) -> Either<L, S> pub fn map_right<F, S>(self, f: F) -> Either<L, S>
where F: FnOnce(R) -> S where
F: FnOnce(R) -> S,
{ {
use self::Either::*; use self::Either::*;
match self { match self {

View file

@ -21,5 +21,5 @@ path = "../edn"
path = "../core-traits" path = "../core-traits"
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.13" version = "0.21"
features = ["limits"] features = ["limits"]

View file

@ -10,29 +10,15 @@
#![allow(dead_code)] #![allow(dead_code)]
use failure::{ use failure::{Backtrace, Context, Fail};
Backtrace,
Context,
Fail,
};
use std::collections::{ use std::collections::{BTreeMap, BTreeSet};
BTreeMap,
BTreeSet,
};
use rusqlite; use rusqlite;
use edn::entities::{ use edn::entities::TempId;
TempId,
};
use core_traits::{ use core_traits::{Entid, KnownEntid, TypedValue, ValueType};
Entid,
KnownEntid,
TypedValue,
ValueType,
};
pub type Result<T> = ::std::result::Result<T, DbError>; pub type Result<T> = ::std::result::Result<T, DbError>;
@ -72,40 +58,46 @@ pub enum SchemaConstraintViolation {
/// A transaction tried to assert a datom or datoms with the wrong value `v` type(s). /// A transaction tried to assert a datom or datoms with the wrong value `v` type(s).
TypeDisagreements { TypeDisagreements {
/// The key (`[e a v]`) has an invalid value `v`: it is not of the expected value type. /// The key (`[e a v]`) has an invalid value `v`: it is not of the expected value type.
conflicting_datoms: BTreeMap<(Entid, Entid, TypedValue), ValueType> conflicting_datoms: BTreeMap<(Entid, Entid, TypedValue), ValueType>,
}, },
/// A transaction tried to assert datoms that don't observe the schema's cardinality constraints. /// A transaction tried to assert datoms that don't observe the schema's cardinality constraints.
CardinalityConflicts { CardinalityConflicts { conflicts: Vec<CardinalityConflict> },
conflicts: Vec<CardinalityConflict>,
},
} }
impl ::std::fmt::Display for SchemaConstraintViolation { impl ::std::fmt::Display for SchemaConstraintViolation {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
use self::SchemaConstraintViolation::*; use self::SchemaConstraintViolation::*;
match self { match self {
&ConflictingUpserts { ref conflicting_upserts } => { &ConflictingUpserts {
ref conflicting_upserts,
} => {
writeln!(f, "conflicting upserts:")?; writeln!(f, "conflicting upserts:")?;
for (tempid, entids) in conflicting_upserts { for (tempid, entids) in conflicting_upserts {
writeln!(f, " tempid {:?} upserts to {:?}", tempid, entids)?; writeln!(f, " tempid {:?} upserts to {:?}", tempid, entids)?;
} }
Ok(()) Ok(())
}, }
&TypeDisagreements { ref conflicting_datoms } => { &TypeDisagreements {
ref conflicting_datoms,
} => {
writeln!(f, "type disagreements:")?; writeln!(f, "type disagreements:")?;
for (ref datom, expected_type) in conflicting_datoms { for (ref datom, expected_type) in conflicting_datoms {
writeln!(f, " expected value of type {} but got datom [{} {} {:?}]", expected_type, datom.0, datom.1, datom.2)?; writeln!(
f,
" expected value of type {} but got datom [{} {} {:?}]",
expected_type, datom.0, datom.1, datom.2
)?;
} }
Ok(()) Ok(())
}, }
&CardinalityConflicts { ref conflicts } => { &CardinalityConflicts { ref conflicts } => {
writeln!(f, "cardinality conflicts:")?; writeln!(f, "cardinality conflicts:")?;
for ref conflict in conflicts { for ref conflict in conflicts {
writeln!(f, " {:?}", conflict)?; writeln!(f, " {:?}", conflict)?;
} }
Ok(()) Ok(())
}, }
} }
} }
} }
@ -146,7 +138,7 @@ impl ::std::fmt::Display for DbError {
} }
impl Fail for DbError { impl Fail for DbError {
fn cause(&self) -> Option<&Fail> { fn cause(&self) -> Option<&dyn Fail> {
self.inner.cause() self.inner.cause()
} }
@ -162,20 +154,24 @@ impl DbError {
} }
impl From<DbErrorKind> for DbError { impl From<DbErrorKind> for DbError {
fn from(kind: DbErrorKind) -> DbError { fn from(kind: DbErrorKind) -> Self {
DbError { inner: Context::new(kind) } DbError {
inner: Context::new(kind),
}
} }
} }
impl From<Context<DbErrorKind>> for DbError { impl From<Context<DbErrorKind>> for DbError {
fn from(inner: Context<DbErrorKind>) -> DbError { fn from(inner: Context<DbErrorKind>) -> Self {
DbError { inner: inner } DbError { inner: inner }
} }
} }
impl From<rusqlite::Error> for DbError { impl From<rusqlite::Error> for DbError {
fn from(error: rusqlite::Error) -> DbError { fn from(error: rusqlite::Error) -> Self {
DbError { inner: Context::new(DbErrorKind::RusqliteError(error.to_string())) } DbError {
inner: Context::new(DbErrorKind::RusqliteError(error.to_string())),
}
} }
} }
@ -187,7 +183,10 @@ pub enum DbErrorKind {
NotYetImplemented(String), NotYetImplemented(String),
/// We've been given a value that isn't the correct Mentat type. /// We've been given a value that isn't the correct Mentat type.
#[fail(display = "value '{}' is not the expected Mentat value type {:?}", _0, _1)] #[fail(
display = "value '{}' is not the expected Mentat value type {:?}",
_0, _1
)]
BadValuePair(String, ValueType), BadValuePair(String, ValueType),
/// We've got corrupt data in the SQL store: a value and value_type_tag don't line up. /// We've got corrupt data in the SQL store: a value and value_type_tag don't line up.
@ -195,11 +194,10 @@ pub enum DbErrorKind {
#[fail(display = "bad SQL (value_type_tag, value) pair: ({:?}, {:?})", _0, _1)] #[fail(display = "bad SQL (value_type_tag, value) pair: ({:?}, {:?})", _0, _1)]
BadSQLValuePair(rusqlite::types::Value, i32), BadSQLValuePair(rusqlite::types::Value, i32),
// /// The SQLite store user_version isn't recognized. This could be an old version of Mentat /// The SQLite store user_version isn't recognized. This could be an old version of Mentat
// /// trying to open a newer version SQLite store; or it could be a corrupt file; or ... /// trying to open a newer version SQLite store; or it could be a corrupt file; or ...
// #[fail(display = "bad SQL store user_version: {}", _0)] /// #[fail(display = "bad SQL store user_version: {}", _0)]
// BadSQLiteStoreVersion(i32), /// BadSQLiteStoreVersion(i32),
/// A bootstrap definition couldn't be parsed or installed. This is a programmer error, not /// A bootstrap definition couldn't be parsed or installed. This is a programmer error, not
/// a runtime error. /// a runtime error.
#[fail(display = "bad bootstrap definition: {}", _0)] #[fail(display = "bad bootstrap definition: {}", _0)]
@ -239,7 +237,9 @@ pub enum DbErrorKind {
#[fail(display = "transaction input error: {}", _0)] #[fail(display = "transaction input error: {}", _0)]
InputError(InputError), InputError(InputError),
#[fail(display = "Cannot transact a fulltext assertion with a typed value that is not :db/valueType :db.type/string")] #[fail(
display = "Cannot transact a fulltext assertion with a typed value that is not :db/valueType :db.type/string"
)]
WrongTypeValueForFtsAssertion, WrongTypeValueForFtsAssertion,
// SQL errors. // SQL errors.

View file

@ -8,13 +8,11 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate failure; extern crate failure;
#[macro_use]
extern crate failure_derive; extern crate failure_derive;
extern crate rusqlite; extern crate rusqlite;
extern crate edn;
extern crate core_traits; extern crate core_traits;
extern crate edn;
pub mod errors; pub mod errors;

View file

@ -9,20 +9,20 @@ sqlcipher = ["rusqlite/sqlcipher"]
syncable = ["serde", "serde_json", "serde_derive"] syncable = ["serde", "serde_json", "serde_derive"]
[dependencies] [dependencies]
failure = "0.1.1" failure = "0.1.6"
indexmap = "1" indexmap = "1"
itertools = "0.7" itertools = "0.8"
lazy_static = "0.2" lazy_static = "1.4.0"
log = "0.4" log = "0.4"
ordered-float = "0.5" ordered-float = "1.0.2"
time = "0.1" time = "0.2"
petgraph = "0.4.12" petgraph = "0.5"
serde = { version = "1.0", optional = true } serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true }
serde_derive = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true }
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.13" version = "0.21"
features = ["limits"] features = ["limits"]
[dependencies.edn] [dependencies.edn]
@ -42,7 +42,7 @@ path = "../sql"
# Should be dev-dependencies. # Should be dev-dependencies.
[dependencies.tabwriter] [dependencies.tabwriter]
version = "1.0.3" version = "1.2.1"
[dev-dependencies] [dev-dependencies]
env_logger = "0.5" env_logger = "0.7"

View file

@ -24,7 +24,10 @@ pub struct AddRetractAlterSet<K, V> {
pub altered: BTreeMap<K, (V, V)>, pub altered: BTreeMap<K, (V, V)>,
} }
impl<K, V> Default for AddRetractAlterSet<K, V> where K: Ord { impl<K, V> Default for AddRetractAlterSet<K, V>
where
K: Ord,
{
fn default() -> AddRetractAlterSet<K, V> { fn default() -> AddRetractAlterSet<K, V> {
AddRetractAlterSet { AddRetractAlterSet {
asserted: BTreeMap::default(), asserted: BTreeMap::default(),
@ -34,7 +37,10 @@ impl<K, V> Default for AddRetractAlterSet<K, V> where K: Ord {
} }
} }
impl<K, V> AddRetractAlterSet<K, V> where K: Ord { impl<K, V> AddRetractAlterSet<K, V>
where
K: Ord,
{
pub fn witness(&mut self, key: K, value: V, added: bool) { pub fn witness(&mut self, key: K, value: V, added: bool) {
if added { if added {
if let Some(retracted_value) = self.retracted.remove(&key) { if let Some(retracted_value) = self.retracted.remove(&key) {

View file

@ -10,26 +10,17 @@
#![allow(dead_code)] #![allow(dead_code)]
use edn;
use db_traits::errors::{
DbErrorKind,
Result,
};
use edn::types::Value;
use edn::symbols;
use entids;
use db::TypedSQLValue; use db::TypedSQLValue;
use db_traits::errors::{DbErrorKind, Result};
use edn;
use edn::entities::Entity; use edn::entities::Entity;
use edn::symbols;
use edn::types::Value;
use entids;
use core_traits::{ use core_traits::{values, TypedValue};
TypedValue,
values,
};
use mentat_core::{ use mentat_core::{IdentMap, Schema};
IdentMap,
Schema,
};
use schema::SchemaBuilding; use schema::SchemaBuilding;
use types::{Partition, PartitionMap}; use types::{Partition, PartitionMap};
@ -46,76 +37,118 @@ pub const CORE_SCHEMA_VERSION: u32 = 1;
lazy_static! { lazy_static! {
static ref V1_IDENTS: [(symbols::Keyword, i64); 40] = { static ref V1_IDENTS: [(symbols::Keyword, i64); 40] = {
[(ns_keyword!("db", "ident"), entids::DB_IDENT), [
(ns_keyword!("db.part", "db"), entids::DB_PART_DB), (ns_keyword!("db", "ident"), entids::DB_IDENT),
(ns_keyword!("db", "txInstant"), entids::DB_TX_INSTANT), (ns_keyword!("db.part", "db"), entids::DB_PART_DB),
(ns_keyword!("db.install", "partition"), entids::DB_INSTALL_PARTITION), (ns_keyword!("db", "txInstant"), entids::DB_TX_INSTANT),
(ns_keyword!("db.install", "valueType"), entids::DB_INSTALL_VALUE_TYPE), (
(ns_keyword!("db.install", "attribute"), entids::DB_INSTALL_ATTRIBUTE), ns_keyword!("db.install", "partition"),
(ns_keyword!("db", "valueType"), entids::DB_VALUE_TYPE), entids::DB_INSTALL_PARTITION,
(ns_keyword!("db", "cardinality"), entids::DB_CARDINALITY), ),
(ns_keyword!("db", "unique"), entids::DB_UNIQUE), (
(ns_keyword!("db", "isComponent"), entids::DB_IS_COMPONENT), ns_keyword!("db.install", "valueType"),
(ns_keyword!("db", "index"), entids::DB_INDEX), entids::DB_INSTALL_VALUE_TYPE,
(ns_keyword!("db", "fulltext"), entids::DB_FULLTEXT), ),
(ns_keyword!("db", "noHistory"), entids::DB_NO_HISTORY), (
(ns_keyword!("db", "add"), entids::DB_ADD), ns_keyword!("db.install", "attribute"),
(ns_keyword!("db", "retract"), entids::DB_RETRACT), entids::DB_INSTALL_ATTRIBUTE,
(ns_keyword!("db.part", "user"), entids::DB_PART_USER), ),
(ns_keyword!("db.part", "tx"), entids::DB_PART_TX), (ns_keyword!("db", "valueType"), entids::DB_VALUE_TYPE),
(ns_keyword!("db", "excise"), entids::DB_EXCISE), (ns_keyword!("db", "cardinality"), entids::DB_CARDINALITY),
(ns_keyword!("db.excise", "attrs"), entids::DB_EXCISE_ATTRS), (ns_keyword!("db", "unique"), entids::DB_UNIQUE),
(ns_keyword!("db.excise", "beforeT"), entids::DB_EXCISE_BEFORE_T), (ns_keyword!("db", "isComponent"), entids::DB_IS_COMPONENT),
(ns_keyword!("db.excise", "before"), entids::DB_EXCISE_BEFORE), (ns_keyword!("db", "index"), entids::DB_INDEX),
(ns_keyword!("db.alter", "attribute"), entids::DB_ALTER_ATTRIBUTE), (ns_keyword!("db", "fulltext"), entids::DB_FULLTEXT),
(ns_keyword!("db.type", "ref"), entids::DB_TYPE_REF), (ns_keyword!("db", "noHistory"), entids::DB_NO_HISTORY),
(ns_keyword!("db.type", "keyword"), entids::DB_TYPE_KEYWORD), (ns_keyword!("db", "add"), entids::DB_ADD),
(ns_keyword!("db.type", "long"), entids::DB_TYPE_LONG), (ns_keyword!("db", "retract"), entids::DB_RETRACT),
(ns_keyword!("db.type", "double"), entids::DB_TYPE_DOUBLE), (ns_keyword!("db.part", "user"), entids::DB_PART_USER),
(ns_keyword!("db.type", "string"), entids::DB_TYPE_STRING), (ns_keyword!("db.part", "tx"), entids::DB_PART_TX),
(ns_keyword!("db.type", "uuid"), entids::DB_TYPE_UUID), (ns_keyword!("db", "excise"), entids::DB_EXCISE),
(ns_keyword!("db.type", "uri"), entids::DB_TYPE_URI), (ns_keyword!("db.excise", "attrs"), entids::DB_EXCISE_ATTRS),
(ns_keyword!("db.type", "boolean"), entids::DB_TYPE_BOOLEAN), (
(ns_keyword!("db.type", "instant"), entids::DB_TYPE_INSTANT), ns_keyword!("db.excise", "beforeT"),
(ns_keyword!("db.type", "bytes"), entids::DB_TYPE_BYTES), entids::DB_EXCISE_BEFORE_T,
(ns_keyword!("db.cardinality", "one"), entids::DB_CARDINALITY_ONE), ),
(ns_keyword!("db.cardinality", "many"), entids::DB_CARDINALITY_MANY), (ns_keyword!("db.excise", "before"), entids::DB_EXCISE_BEFORE),
(ns_keyword!("db.unique", "value"), entids::DB_UNIQUE_VALUE), (
(ns_keyword!("db.unique", "identity"), entids::DB_UNIQUE_IDENTITY), ns_keyword!("db.alter", "attribute"),
(ns_keyword!("db", "doc"), entids::DB_DOC), entids::DB_ALTER_ATTRIBUTE,
(ns_keyword!("db.schema", "version"), entids::DB_SCHEMA_VERSION), ),
(ns_keyword!("db.schema", "attribute"), entids::DB_SCHEMA_ATTRIBUTE), (ns_keyword!("db.type", "ref"), entids::DB_TYPE_REF),
(ns_keyword!("db.schema", "core"), entids::DB_SCHEMA_CORE), (ns_keyword!("db.type", "keyword"), entids::DB_TYPE_KEYWORD),
(ns_keyword!("db.type", "long"), entids::DB_TYPE_LONG),
(ns_keyword!("db.type", "double"), entids::DB_TYPE_DOUBLE),
(ns_keyword!("db.type", "string"), entids::DB_TYPE_STRING),
(ns_keyword!("db.type", "uuid"), entids::DB_TYPE_UUID),
(ns_keyword!("db.type", "uri"), entids::DB_TYPE_URI),
(ns_keyword!("db.type", "boolean"), entids::DB_TYPE_BOOLEAN),
(ns_keyword!("db.type", "instant"), entids::DB_TYPE_INSTANT),
(ns_keyword!("db.type", "bytes"), entids::DB_TYPE_BYTES),
(
ns_keyword!("db.cardinality", "one"),
entids::DB_CARDINALITY_ONE,
),
(
ns_keyword!("db.cardinality", "many"),
entids::DB_CARDINALITY_MANY,
),
(ns_keyword!("db.unique", "value"), entids::DB_UNIQUE_VALUE),
(
ns_keyword!("db.unique", "identity"),
entids::DB_UNIQUE_IDENTITY,
),
(ns_keyword!("db", "doc"), entids::DB_DOC),
(
ns_keyword!("db.schema", "version"),
entids::DB_SCHEMA_VERSION,
),
(
ns_keyword!("db.schema", "attribute"),
entids::DB_SCHEMA_ATTRIBUTE,
),
(ns_keyword!("db.schema", "core"), entids::DB_SCHEMA_CORE),
] ]
}; };
pub static ref V1_PARTS: [(symbols::Keyword, i64, i64, i64, bool); 3] = { pub static ref V1_PARTS: [(symbols::Keyword, i64, i64, i64, bool); 3] = {
[(ns_keyword!("db.part", "db"), 0, USER0 - 1, (1 + V1_IDENTS.len()) as i64, false), [
(ns_keyword!("db.part", "user"), USER0, TX0 - 1, USER0, true), (
(ns_keyword!("db.part", "tx"), TX0, i64::max_value(), TX0, false), ns_keyword!("db.part", "db"),
0,
USER0 - 1,
(1 + V1_IDENTS.len()) as i64,
false,
),
(ns_keyword!("db.part", "user"), USER0, TX0 - 1, USER0, true),
(
ns_keyword!("db.part", "tx"),
TX0,
i64::max_value(),
TX0,
false,
),
] ]
}; };
static ref V1_CORE_SCHEMA: [symbols::Keyword; 16] = {
static ref V1_CORE_SCHEMA: [(symbols::Keyword); 16] = { [
[(ns_keyword!("db", "ident")), (ns_keyword!("db", "ident")),
(ns_keyword!("db.install", "partition")), (ns_keyword!("db.install", "partition")),
(ns_keyword!("db.install", "valueType")), (ns_keyword!("db.install", "valueType")),
(ns_keyword!("db.install", "attribute")), (ns_keyword!("db.install", "attribute")),
(ns_keyword!("db", "txInstant")), (ns_keyword!("db", "txInstant")),
(ns_keyword!("db", "valueType")), (ns_keyword!("db", "valueType")),
(ns_keyword!("db", "cardinality")), (ns_keyword!("db", "cardinality")),
(ns_keyword!("db", "doc")), (ns_keyword!("db", "doc")),
(ns_keyword!("db", "unique")), (ns_keyword!("db", "unique")),
(ns_keyword!("db", "isComponent")), (ns_keyword!("db", "isComponent")),
(ns_keyword!("db", "index")), (ns_keyword!("db", "index")),
(ns_keyword!("db", "fulltext")), (ns_keyword!("db", "fulltext")),
(ns_keyword!("db", "noHistory")), (ns_keyword!("db", "noHistory")),
(ns_keyword!("db.alter", "attribute")), (ns_keyword!("db.alter", "attribute")),
(ns_keyword!("db.schema", "version")), (ns_keyword!("db.schema", "version")),
(ns_keyword!("db.schema", "attribute")), (ns_keyword!("db.schema", "attribute")),
] ]
}; };
static ref V1_SYMBOLIC_SCHEMA: Value = { static ref V1_SYMBOLIC_SCHEMA: Value = {
let s = r#" let s = r#"
{:db/ident {:db/valueType :db.type/keyword {:db/ident {:db/valueType :db.type/keyword
@ -163,7 +196,9 @@ lazy_static! {
:db/cardinality :db.cardinality/many}}"#; :db/cardinality :db.cardinality/many}}"#;
edn::parse::value(s) edn::parse::value(s)
.map(|v| v.without_spans()) .map(|v| v.without_spans())
.map_err(|_| DbErrorKind::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into())) .map_err(|_| {
DbErrorKind::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into())
})
.unwrap() .unwrap()
}; };
} }
@ -174,7 +209,12 @@ fn idents_to_assertions(idents: &[(symbols::Keyword, i64)]) -> Vec<Value> {
.into_iter() .into_iter()
.map(|&(ref ident, _)| { .map(|&(ref ident, _)| {
let value = Value::Keyword(ident.clone()); let value = Value::Keyword(ident.clone());
Value::Vector(vec![values::DB_ADD.clone(), value.clone(), values::DB_IDENT.clone(), value.clone()]) Value::Vector(vec![
values::DB_ADD.clone(),
value.clone(),
values::DB_IDENT.clone(),
value.clone(),
])
}) })
.collect() .collect()
} }
@ -188,15 +228,19 @@ fn schema_attrs_to_assertions(version: u32, idents: &[symbols::Keyword]) -> Vec<
.into_iter() .into_iter()
.map(|ident| { .map(|ident| {
let value = Value::Keyword(ident.clone()); let value = Value::Keyword(ident.clone());
Value::Vector(vec![values::DB_ADD.clone(), Value::Vector(vec![
schema_core.clone(), values::DB_ADD.clone(),
schema_attr.clone(), schema_core.clone(),
value]) schema_attr.clone(),
value,
])
}) })
.chain(::std::iter::once(Value::Vector(vec![values::DB_ADD.clone(), .chain(::std::iter::once(Value::Vector(vec![
schema_core.clone(), values::DB_ADD.clone(),
schema_version, schema_core.clone(),
Value::Integer(version as i64)]))) schema_version,
Value::Integer(version as i64),
])))
.collect() .collect()
} }
@ -205,7 +249,10 @@ fn schema_attrs_to_assertions(version: u32, idents: &[symbols::Keyword]) -> Vec<
/// ///
/// Such triples are closer to what the transactor will produce when processing attribute /// Such triples are closer to what the transactor will produce when processing attribute
/// assertions. /// assertions.
fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) -> Result<Vec<(symbols::Keyword, symbols::Keyword, TypedValue)>> { fn symbolic_schema_to_triples(
ident_map: &IdentMap,
symbolic_schema: &Value,
) -> Result<Vec<(symbols::Keyword, symbols::Keyword, TypedValue)>> {
// Failure here is a coding error, not a runtime error. // Failure here is a coding error, not a runtime error.
let mut triples: Vec<(symbols::Keyword, symbols::Keyword, TypedValue)> = vec![]; let mut triples: Vec<(symbols::Keyword, symbols::Keyword, TypedValue)> = vec![];
// TODO: Consider `flat_map` and `map` rather than loop. // TODO: Consider `flat_map` and `map` rather than loop.
@ -214,15 +261,21 @@ fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) ->
for (ident, mp) in m { for (ident, mp) in m {
let ident = match ident { let ident = match ident {
&Value::Keyword(ref ident) => ident, &Value::Keyword(ref ident) => ident,
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!("Expected namespaced keyword for ident but got '{:?}'", ident))), _ => bail!(DbErrorKind::BadBootstrapDefinition(format!(
"Expected namespaced keyword for ident but got '{:?}'",
ident
))),
}; };
match *mp { match *mp {
Value::Map(ref mpp) => { Value::Map(ref mpp) => {
for (attr, value) in mpp { for (attr, value) in mpp {
let attr = match attr { let attr = match attr {
&Value::Keyword(ref attr) => attr, &Value::Keyword(ref attr) => attr,
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!("Expected namespaced keyword for attr but got '{:?}'", attr))), _ => bail!(DbErrorKind::BadBootstrapDefinition(format!(
}; "Expected namespaced keyword for attr but got '{:?}'",
attr
))),
};
// We have symbolic idents but the transactor handles entids. Ad-hoc // We have symbolic idents but the transactor handles entids. Ad-hoc
// convert right here. This is a fundamental limitation on the // convert right here. This is a fundamental limitation on the
@ -233,23 +286,27 @@ fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) ->
// bootstrap symbolic schema, or by representing the initial bootstrap // bootstrap symbolic schema, or by representing the initial bootstrap
// schema directly as Rust data. // schema directly as Rust data.
let typed_value = match TypedValue::from_edn_value(value) { let typed_value = match TypedValue::from_edn_value(value) {
Some(TypedValue::Keyword(ref k)) => { Some(TypedValue::Keyword(ref k)) => ident_map
ident_map.get(k) .get(k)
.map(|entid| TypedValue::Ref(*entid)) .map(|entid| TypedValue::Ref(*entid))
.ok_or(DbErrorKind::UnrecognizedIdent(k.to_string()))? .ok_or(DbErrorKind::UnrecognizedIdent(k.to_string()))?,
},
Some(v) => v, Some(v) => v,
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!("Expected Mentat typed value for value but got '{:?}'", value))) _ => bail!(DbErrorKind::BadBootstrapDefinition(format!(
"Expected Mentat typed value for value but got '{:?}'",
value
))),
}; };
triples.push((ident.clone(), attr.clone(), typed_value)); triples.push((ident.clone(), attr.clone(), typed_value));
} }
}, }
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into())) _ => bail!(DbErrorKind::BadBootstrapDefinition(
"Expected {:db/ident {:db/attr value ...} ...}".into()
)),
} }
} }
}, }
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into())) _ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into())),
} }
Ok(triples) Ok(triples)
} }
@ -264,48 +321,64 @@ fn symbolic_schema_to_assertions(symbolic_schema: &Value) -> Result<Vec<Value>>
match *mp { match *mp {
Value::Map(ref mpp) => { Value::Map(ref mpp) => {
for (attr, value) in mpp { for (attr, value) in mpp {
assertions.push(Value::Vector(vec![values::DB_ADD.clone(), assertions.push(Value::Vector(vec![
ident.clone(), values::DB_ADD.clone(),
attr.clone(), ident.clone(),
value.clone()])); attr.clone(),
value.clone(),
]));
} }
}, }
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into())) _ => bail!(DbErrorKind::BadBootstrapDefinition(
"Expected {:db/ident {:db/attr value ...} ...}".into()
)),
} }
} }
}, }
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into())) _ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into())),
} }
Ok(assertions) Ok(assertions)
} }
pub(crate) fn bootstrap_partition_map() -> PartitionMap { pub(crate) fn bootstrap_partition_map() -> PartitionMap {
V1_PARTS.iter() V1_PARTS
.map(|&(ref part, start, end, index, allow_excision)| (part.to_string(), Partition::new(start, end, index, allow_excision))) .iter()
.collect() .map(|&(ref part, start, end, index, allow_excision)| {
(
part.to_string(),
Partition::new(start, end, index, allow_excision),
)
})
.collect()
} }
pub(crate) fn bootstrap_ident_map() -> IdentMap { pub(crate) fn bootstrap_ident_map() -> IdentMap {
V1_IDENTS.iter() V1_IDENTS
.map(|&(ref ident, entid)| (ident.clone(), entid)) .iter()
.collect() .map(|&(ref ident, entid)| (ident.clone(), entid))
.collect()
} }
pub(crate) fn bootstrap_schema() -> Schema { pub(crate) fn bootstrap_schema() -> Schema {
let ident_map = bootstrap_ident_map(); let ident_map = bootstrap_ident_map();
let bootstrap_triples = symbolic_schema_to_triples(&ident_map, &V1_SYMBOLIC_SCHEMA).expect("symbolic schema"); let bootstrap_triples =
symbolic_schema_to_triples(&ident_map, &V1_SYMBOLIC_SCHEMA).expect("symbolic schema");
Schema::from_ident_map_and_triples(ident_map, bootstrap_triples).unwrap() Schema::from_ident_map_and_triples(ident_map, bootstrap_triples).unwrap()
} }
pub(crate) fn bootstrap_entities() -> Vec<Entity<edn::ValueAndSpan>> { pub(crate) fn bootstrap_entities() -> Vec<Entity<edn::ValueAndSpan>> {
let bootstrap_assertions: Value = Value::Vector([ let bootstrap_assertions: Value = Value::Vector(
symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).expect("symbolic schema"), [
idents_to_assertions(&V1_IDENTS[..]), symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).expect("symbolic schema"),
schema_attrs_to_assertions(CORE_SCHEMA_VERSION, V1_CORE_SCHEMA.as_ref()), idents_to_assertions(&V1_IDENTS[..]),
].concat()); schema_attrs_to_assertions(CORE_SCHEMA_VERSION, V1_CORE_SCHEMA.as_ref()),
]
.concat(),
);
// Failure here is a coding error (since the inputs are fixed), not a runtime error. // Failure here is a coding error (since the inputs are fixed), not a runtime error.
// TODO: represent these bootstrap data errors rather than just panicing. // TODO: represent these bootstrap data errors rather than just panicing.
let bootstrap_entities: Vec<Entity<edn::ValueAndSpan>> = edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions"); let bootstrap_entities: Vec<Entity<edn::ValueAndSpan>> =
edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions");
return bootstrap_entities; return bootstrap_entities;
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -25,11 +25,13 @@ macro_rules! assert_matches {
.expect(format!("to be able to parse expected {}", $expected).as_str()) .expect(format!("to be able to parse expected {}", $expected).as_str())
.without_spans(); .without_spans();
let input_value = $input.to_edn(); let input_value = $input.to_edn();
assert!(input_value.matches(&pattern_value), assert!(
"Expected value:\n{}\nto match pattern:\n{}\n", input_value.matches(&pattern_value),
input_value.to_pretty(120).unwrap(), "Expected value:\n{}\nto match pattern:\n{}\n",
pattern_value.to_pretty(120).unwrap()); input_value.to_pretty(120).unwrap(),
}} pattern_value.to_pretty(120).unwrap()
);
}};
} }
// Transact $input against the given $conn, expecting success or a `Result<TxReport, String>`. // Transact $input against the given $conn, expecting success or a `Result<TxReport, String>`.
@ -45,61 +47,45 @@ macro_rules! assert_transact {
( $conn: expr, $input: expr ) => {{ ( $conn: expr, $input: expr ) => {{
trace!("assert_transact: {}", $input); trace!("assert_transact: {}", $input);
let result = $conn.transact($input); let result = $conn.transact($input);
assert!(result.is_ok(), "Expected Ok(_), got `{}`", result.unwrap_err()); assert!(
result.is_ok(),
"Expected Ok(_), got `{}`",
result.unwrap_err()
);
result.unwrap() result.unwrap()
}}; }};
} }
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::io::{Write}; use std::io::Write;
use itertools::Itertools; use itertools::Itertools;
use rusqlite; use rusqlite;
use rusqlite::{TransactionBehavior}; use rusqlite::types::ToSql;
use rusqlite::types::{ToSql}; use rusqlite::TransactionBehavior;
use tabwriter::TabWriter; use tabwriter::TabWriter;
use bootstrap; use bootstrap;
use db::*; use db::*;
use db::{read_attribute_map,read_ident_map}; use db::{read_attribute_map, read_ident_map};
use db_traits::errors::Result;
use edn; use edn;
use entids; use entids;
use db_traits::errors::Result;
use core_traits::{ use core_traits::{Entid, TypedValue, ValueType};
Entid,
TypedValue,
ValueType,
};
use mentat_core::{ use edn::entities::{EntidOrIdent, TempId};
HasSchema, use edn::InternSet;
SQLValueType, use internal_types::TermWithTempIds;
TxReport, use mentat_core::{HasSchema, SQLValueType, TxReport};
}; use schema::SchemaBuilding;
use edn::{ use tx::{transact, transact_terms};
InternSet,
};
use edn::entities::{
EntidOrIdent,
TempId,
};
use internal_types::{
TermWithTempIds,
};
use schema::{
SchemaBuilding,
};
use types::*; use types::*;
use tx::{
transact,
transact_terms,
};
use watcher::NullWatcher; use watcher::NullWatcher;
/// Represents a *datom* (assertion) in the store. /// Represents a *datom* (assertion) in the store.
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct Datom { pub struct Datom {
// TODO: generalize this. // TODO: generalize this.
pub e: EntidOrIdent, pub e: EntidOrIdent,
@ -160,19 +146,30 @@ impl Transactions {
impl FulltextValues { impl FulltextValues {
pub fn to_edn(&self) -> edn::Value { pub fn to_edn(&self) -> edn::Value {
edn::Value::Vector((&self.0).into_iter().map(|&(x, ref y)| edn::Value::Vector(vec![edn::Value::Integer(x), edn::Value::Text(y.clone())])).collect()) edn::Value::Vector(
(&self.0)
.into_iter()
.map(|&(x, ref y)| {
edn::Value::Vector(vec![edn::Value::Integer(x), edn::Value::Text(y.clone())])
})
.collect(),
)
} }
} }
/// Turn TypedValue::Ref into TypedValue::Keyword when it is possible. /// Turn TypedValue::Ref into TypedValue::Keyword when it is possible.
trait ToIdent { trait ToIdent {
fn map_ident(self, schema: &Schema) -> Self; fn map_ident(self, schema: &Schema) -> Self;
} }
impl ToIdent for TypedValue { impl ToIdent for TypedValue {
fn map_ident(self, schema: &Schema) -> Self { fn map_ident(self, schema: &Schema) -> Self {
if let TypedValue::Ref(e) = self { if let TypedValue::Ref(e) = self {
schema.get_ident(e).cloned().map(|i| i.into()).unwrap_or(TypedValue::Ref(e)) schema
.get_ident(e)
.cloned()
.map(|i| i.into())
.unwrap_or(TypedValue::Ref(e))
} else { } else {
self self
} }
@ -181,7 +178,11 @@ impl ToIdent for TypedValue {
/// Convert a numeric entid to an ident `Entid` if possible, otherwise a numeric `Entid`. /// Convert a numeric entid to an ident `Entid` if possible, otherwise a numeric `Entid`.
pub fn to_entid(schema: &Schema, entid: i64) -> EntidOrIdent { pub fn to_entid(schema: &Schema, entid: i64) -> EntidOrIdent {
schema.get_ident(entid).map_or(EntidOrIdent::Entid(entid), |ident| EntidOrIdent::Ident(ident.clone())) schema
.get_ident(entid)
.map_or(EntidOrIdent::Entid(entid), |ident| {
EntidOrIdent::Ident(ident.clone())
})
} }
// /// Convert a symbolic ident to an ident `Entid` if possible, otherwise a numeric `Entid`. // /// Convert a symbolic ident to an ident `Entid` if possible, otherwise a numeric `Entid`.
@ -199,38 +200,49 @@ pub fn datoms<S: Borrow<Schema>>(conn: &rusqlite::Connection, schema: &S) -> Res
/// ordered by (e, a, v, tx). /// ordered by (e, a, v, tx).
/// ///
/// The datom set returned does not include any datoms of the form [... :db/txInstant ...]. /// The datom set returned does not include any datoms of the form [... :db/txInstant ...].
pub fn datoms_after<S: Borrow<Schema>>(conn: &rusqlite::Connection, schema: &S, tx: i64) -> Result<Datoms> { pub fn datoms_after<S: Borrow<Schema>>(
conn: &rusqlite::Connection,
schema: &S,
tx: i64,
) -> Result<Datoms> {
let borrowed_schema = schema.borrow(); let borrowed_schema = schema.borrow();
let mut stmt: rusqlite::Statement = conn.prepare("SELECT e, a, v, value_type_tag, tx FROM datoms WHERE tx > ? ORDER BY e ASC, a ASC, value_type_tag ASC, v ASC, tx ASC")?; let mut stmt: rusqlite::Statement = conn.prepare("SELECT e, a, v, value_type_tag, tx FROM datoms WHERE tx > ? ORDER BY e ASC, a ASC, value_type_tag ASC, v ASC, tx ASC")?;
let r: Result<Vec<_>> = stmt.query_and_then(&[&tx], |row| { let r: Result<Vec<_>> = stmt
let e: i64 = row.get_checked(0)?; .query_and_then(&[&tx], |row| {
let a: i64 = row.get_checked(1)?; let e: i64 = row.get(0)?;
let a: i64 = row.get(1)?;
if a == entids::DB_TX_INSTANT { if a == entids::DB_TX_INSTANT {
return Ok(None); return Ok(None);
} }
let v: rusqlite::types::Value = row.get_checked(2)?; let v: rusqlite::types::Value = row.get(2)?;
let value_type_tag: i32 = row.get_checked(3)?; let value_type_tag: i32 = row.get(3)?;
let attribute = borrowed_schema.require_attribute_for_entid(a)?; let attribute = borrowed_schema.require_attribute_for_entid(a)?;
let value_type_tag = if !attribute.fulltext { value_type_tag } else { ValueType::Long.value_type_tag() }; let value_type_tag = if !attribute.fulltext {
value_type_tag
} else {
ValueType::Long.value_type_tag()
};
let typed_value = TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema); let typed_value =
let (value, _) = typed_value.to_edn_value_pair(); TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema);
let (value, _) = typed_value.to_edn_value_pair();
let tx: i64 = row.get_checked(4)?; let tx: i64 = row.get(4)?;
Ok(Some(Datom { Ok(Some(Datom {
e: EntidOrIdent::Entid(e), e: EntidOrIdent::Entid(e),
a: to_entid(borrowed_schema, a), a: to_entid(borrowed_schema, a),
v: value, v: value,
tx: tx, tx: tx,
added: None, added: None,
})) }))
})?.collect(); })?
.collect();
Ok(Datoms(r?.into_iter().filter_map(|x| x).collect())) Ok(Datoms(r?.into_iter().filter_map(|x| x).collect()))
} }
@ -239,50 +251,70 @@ pub fn datoms_after<S: Borrow<Schema>>(conn: &rusqlite::Connection, schema: &S,
/// given `tx`, ordered by (tx, e, a, v). /// given `tx`, ordered by (tx, e, a, v).
/// ///
/// Each transaction returned includes the [(transaction-tx) :db/txInstant ...] datom. /// Each transaction returned includes the [(transaction-tx) :db/txInstant ...] datom.
pub fn transactions_after<S: Borrow<Schema>>(conn: &rusqlite::Connection, schema: &S, tx: i64) -> Result<Transactions> { pub fn transactions_after<S: Borrow<Schema>>(
conn: &rusqlite::Connection,
schema: &S,
tx: i64,
) -> Result<Transactions> {
let borrowed_schema = schema.borrow(); let borrowed_schema = schema.borrow();
let mut stmt: rusqlite::Statement = conn.prepare("SELECT e, a, v, value_type_tag, tx, added FROM transactions WHERE tx > ? ORDER BY tx ASC, e ASC, a ASC, value_type_tag ASC, v ASC, added ASC")?; let mut stmt: rusqlite::Statement = conn.prepare("SELECT e, a, v, value_type_tag, tx, added FROM transactions WHERE tx > ? ORDER BY tx ASC, e ASC, a ASC, value_type_tag ASC, v ASC, added ASC")?;
let r: Result<Vec<_>> = stmt.query_and_then(&[&tx], |row| { let r: Result<Vec<_>> = stmt
let e: i64 = row.get_checked(0)?; .query_and_then(&[&tx], |row| {
let a: i64 = row.get_checked(1)?; let e: i64 = row.get(0)?;
let a: i64 = row.get(1)?;
let v: rusqlite::types::Value = row.get_checked(2)?; let v: rusqlite::types::Value = row.get(2)?;
let value_type_tag: i32 = row.get_checked(3)?; let value_type_tag: i32 = row.get(3)?;
let attribute = borrowed_schema.require_attribute_for_entid(a)?; let attribute = borrowed_schema.require_attribute_for_entid(a)?;
let value_type_tag = if !attribute.fulltext { value_type_tag } else { ValueType::Long.value_type_tag() }; let value_type_tag = if !attribute.fulltext {
value_type_tag
} else {
ValueType::Long.value_type_tag()
};
let typed_value = TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema); let typed_value =
let (value, _) = typed_value.to_edn_value_pair(); TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema);
let (value, _) = typed_value.to_edn_value_pair();
let tx: i64 = row.get_checked(4)?; let tx: i64 = row.get(4)?;
let added: bool = row.get_checked(5)?; let added: bool = row.get(5)?;
Ok(Datom { Ok(Datom {
e: EntidOrIdent::Entid(e), e: EntidOrIdent::Entid(e),
a: to_entid(borrowed_schema, a), a: to_entid(borrowed_schema, a),
v: value, v: value,
tx: tx, tx: tx,
added: Some(added), added: Some(added),
}) })
})?.collect(); })?
.collect();
// Group by tx. // Group by tx.
let r: Vec<Datoms> = r?.into_iter().group_by(|x| x.tx).into_iter().map(|(_key, group)| Datoms(group.collect())).collect(); let r: Vec<Datoms> = r?
.into_iter()
.group_by(|x| x.tx)
.into_iter()
.map(|(_key, group)| Datoms(group.collect()))
.collect();
Ok(Transactions(r)) Ok(Transactions(r))
} }
/// Return the set of fulltext values in the store, ordered by rowid. /// Return the set of fulltext values in the store, ordered by rowid.
pub fn fulltext_values(conn: &rusqlite::Connection) -> Result<FulltextValues> { pub fn fulltext_values(conn: &rusqlite::Connection) -> Result<FulltextValues> {
let mut stmt: rusqlite::Statement = conn.prepare("SELECT rowid, text FROM fulltext_values ORDER BY rowid")?; let mut stmt: rusqlite::Statement =
conn.prepare("SELECT rowid, text FROM fulltext_values ORDER BY rowid")?;
let params: &[i32; 0] = &[];
let r: Result<Vec<_>> = stmt.query_and_then(&[], |row| { let r: Result<Vec<_>> = stmt
let rowid: i64 = row.get_checked(0)?; .query_and_then(params, |row| {
let text: String = row.get_checked(1)?; let rowid: i64 = row.get(0)?;
Ok((rowid, text)) let text: String = row.get(1)?;
})?.collect(); Ok((rowid, text))
})?
.collect();
r.map(FulltextValues) r.map(FulltextValues)
} }
@ -292,7 +324,11 @@ pub fn fulltext_values(conn: &rusqlite::Connection) -> Result<FulltextValues> {
/// ///
/// The query is printed followed by a newline, then the returned columns followed by a newline, and /// The query is printed followed by a newline, then the returned columns followed by a newline, and
/// then the data rows and columns. All columns are aligned. /// then the data rows and columns. All columns are aligned.
pub fn dump_sql_query(conn: &rusqlite::Connection, sql: &str, params: &[&ToSql]) -> Result<String> { pub fn dump_sql_query(
conn: &rusqlite::Connection,
sql: &str,
params: &[&dyn ToSql],
) -> Result<String> {
let mut stmt: rusqlite::Statement = conn.prepare(sql)?; let mut stmt: rusqlite::Statement = conn.prepare(sql)?;
let mut tw = TabWriter::new(Vec::new()).padding(2); let mut tw = TabWriter::new(Vec::new()).padding(2);
@ -303,14 +339,16 @@ pub fn dump_sql_query(conn: &rusqlite::Connection, sql: &str, params: &[&ToSql])
} }
write!(&mut tw, "\n").unwrap(); write!(&mut tw, "\n").unwrap();
let r: Result<Vec<_>> = stmt.query_and_then(params, |row| { let r: Result<Vec<_>> = stmt
for i in 0..row.column_count() { .query_and_then(params, |row| {
let value: rusqlite::types::Value = row.get_checked(i)?; for i in 0..row.column_count() {
write!(&mut tw, "{:?}\t", value).unwrap(); let value: rusqlite::types::Value = row.get(i)?;
} write!(&mut tw, "{:?}\t", value).unwrap();
write!(&mut tw, "\n").unwrap(); }
Ok(()) write!(&mut tw, "\n").unwrap();
})?.collect(); Ok(())
})?
.collect();
r?; r?;
let dump = String::from_utf8(tw.into_inner().unwrap()).unwrap(); let dump = String::from_utf8(tw.into_inner().unwrap()).unwrap();
@ -330,20 +368,37 @@ impl TestConn {
let materialized_ident_map = read_ident_map(&self.sqlite).expect("ident map"); let materialized_ident_map = read_ident_map(&self.sqlite).expect("ident map");
let materialized_attribute_map = read_attribute_map(&self.sqlite).expect("schema map"); let materialized_attribute_map = read_attribute_map(&self.sqlite).expect("schema map");
let materialized_schema = Schema::from_ident_map_and_attribute_map(materialized_ident_map, materialized_attribute_map).expect("schema"); let materialized_schema = Schema::from_ident_map_and_attribute_map(
materialized_ident_map,
materialized_attribute_map,
)
.expect("schema");
assert_eq!(materialized_schema, self.schema); assert_eq!(materialized_schema, self.schema);
} }
pub fn transact<I>(&mut self, transaction: I) -> Result<TxReport> where I: Borrow<str> { pub fn transact<I>(&mut self, transaction: I) -> Result<TxReport>
where
I: Borrow<str>,
{
// Failure to parse the transaction is a coding error, so we unwrap. // Failure to parse the transaction is a coding error, so we unwrap.
let entities = edn::parse::entities(transaction.borrow()).expect(format!("to be able to parse {} into entities", transaction.borrow()).as_str()); let entities = edn::parse::entities(transaction.borrow())
.expect(format!("to be able to parse {} into entities", transaction.borrow()).as_str());
let details = { let details = {
// The block scopes the borrow of self.sqlite. // The block scopes the borrow of self.sqlite.
// We're about to write, so go straight ahead and get an IMMEDIATE transaction. // We're about to write, so go straight ahead and get an IMMEDIATE transaction.
let tx = self.sqlite.transaction_with_behavior(TransactionBehavior::Immediate)?; let tx = self
.sqlite
.transaction_with_behavior(TransactionBehavior::Immediate)?;
// Applying the transaction can fail, so we don't unwrap. // Applying the transaction can fail, so we don't unwrap.
let details = transact(&tx, self.partition_map.clone(), &self.schema, &self.schema, NullWatcher(), entities)?; let details = transact(
&tx,
self.partition_map.clone(),
&self.schema,
&self.schema,
NullWatcher(),
entities,
)?;
tx.commit()?; tx.commit()?;
details details
}; };
@ -360,13 +415,30 @@ impl TestConn {
Ok(report) Ok(report)
} }
pub fn transact_simple_terms<I>(&mut self, terms: I, tempid_set: InternSet<TempId>) -> Result<TxReport> where I: IntoIterator<Item=TermWithTempIds> { pub fn transact_simple_terms<I>(
&mut self,
terms: I,
tempid_set: InternSet<TempId>,
) -> Result<TxReport>
where
I: IntoIterator<Item = TermWithTempIds>,
{
let details = { let details = {
// The block scopes the borrow of self.sqlite. // The block scopes the borrow of self.sqlite.
// We're about to write, so go straight ahead and get an IMMEDIATE transaction. // We're about to write, so go straight ahead and get an IMMEDIATE transaction.
let tx = self.sqlite.transaction_with_behavior(TransactionBehavior::Immediate)?; let tx = self
.sqlite
.transaction_with_behavior(TransactionBehavior::Immediate)?;
// Applying the transaction can fail, so we don't unwrap. // Applying the transaction can fail, so we don't unwrap.
let details = transact_terms(&tx, self.partition_map.clone(), &self.schema, &self.schema, NullWatcher(), terms, tempid_set)?; let details = transact_terms(
&tx,
self.partition_map.clone(),
&self.schema,
&self.schema,
NullWatcher(),
terms,
tempid_set,
)?;
tx.commit()?; tx.commit()?;
details details
}; };
@ -384,11 +456,19 @@ impl TestConn {
} }
pub fn last_tx_id(&self) -> Entid { pub fn last_tx_id(&self) -> Entid {
self.partition_map.get(&":db.part/tx".to_string()).unwrap().next_entid() - 1 self.partition_map
.get(&":db.part/tx".to_string())
.unwrap()
.next_entid()
- 1
} }
pub fn last_transaction(&self) -> Datoms { pub fn last_transaction(&self) -> Datoms {
transactions_after(&self.sqlite, &self.schema, self.last_tx_id() - 1).expect("last_transaction").0.pop().unwrap() transactions_after(&self.sqlite, &self.schema, self.last_tx_id() - 1)
.expect("last_transaction")
.0
.pop()
.unwrap()
} }
pub fn transactions(&self) -> Transactions { pub fn transactions(&self) -> Transactions {

View file

@ -13,10 +13,7 @@
/// Literal `Entid` values in the the "db" namespace. /// Literal `Entid` values in the the "db" namespace.
/// ///
/// Used through-out the transactor to match core DB constructs. /// Used through-out the transactor to match core DB constructs.
use core_traits::Entid;
use core_traits::{
Entid,
};
// Added in SQL schema v1. // Added in SQL schema v1.
pub const DB_IDENT: Entid = 1; pub const DB_IDENT: Entid = 1;
@ -64,7 +61,7 @@ pub const DB_SCHEMA_CORE: Entid = 40;
/// partitions in the partition map. /// partitions in the partition map.
pub fn might_update_metadata(attribute: Entid) -> bool { pub fn might_update_metadata(attribute: Entid) -> bool {
if attribute >= DB_DOC { if attribute >= DB_DOC {
return false return false;
} }
match attribute { match attribute {
// Idents. // Idents.
@ -84,14 +81,8 @@ pub fn might_update_metadata(attribute: Entid) -> bool {
/// Return 'false' if the given attribute might be used to describe a schema attribute. /// Return 'false' if the given attribute might be used to describe a schema attribute.
pub fn is_a_schema_attribute(attribute: Entid) -> bool { pub fn is_a_schema_attribute(attribute: Entid) -> bool {
match attribute { match attribute {
DB_IDENT | DB_IDENT | DB_CARDINALITY | DB_FULLTEXT | DB_INDEX | DB_IS_COMPONENT | DB_UNIQUE
DB_CARDINALITY | | DB_VALUE_TYPE => true,
DB_FULLTEXT |
DB_INDEX |
DB_IS_COMPONENT |
DB_UNIQUE |
DB_VALUE_TYPE =>
true,
_ => false, _ => false,
} }
} }

View file

@ -12,50 +12,21 @@
//! Types used only within the transactor. These should not be exposed outside of this crate. //! Types used only within the transactor. These should not be exposed outside of this crate.
use std::collections::{ use std::collections::{BTreeMap, BTreeSet, HashMap};
BTreeMap,
BTreeSet,
HashMap,
};
use core_traits::{ use core_traits::{Attribute, Entid, KnownEntid, TypedValue, ValueType};
Attribute,
Entid,
KnownEntid,
TypedValue,
ValueType,
};
use mentat_core::util::Either; use mentat_core::util::Either;
use edn; use edn;
use edn::{
SpannedValue,
ValueAndSpan,
ValueRc,
};
use edn::entities; use edn::entities;
use edn::entities::{ use edn::entities::{EntityPlace, OpType, TempId, TxFunction};
EntityPlace, use edn::{SpannedValue, ValueAndSpan, ValueRc};
OpType,
TempId,
TxFunction,
};
use db_traits::errors as errors; use db_traits::errors;
use db_traits::errors::{ use db_traits::errors::{DbErrorKind, Result};
DbErrorKind, use schema::SchemaTypeChecking;
Result, use types::{AVMap, AVPair, Schema, TransactableValue};
};
use schema::{
SchemaTypeChecking,
};
use types::{
AVMap,
AVPair,
Schema,
TransactableValue,
};
impl TransactableValue for ValueAndSpan { impl TransactableValue for ValueAndSpan {
fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> Result<TypedValue> { fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> Result<TypedValue> {
@ -73,7 +44,7 @@ impl TransactableValue for ValueAndSpan {
// We only allow namespaced idents. // We only allow namespaced idents.
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)) bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
} }
}, }
Text(v) => Ok(EntityPlace::TempId(TempId::External(v).into())), Text(v) => Ok(EntityPlace::TempId(TempId::External(v).into())),
List(ls) => { List(ls) => {
let mut it = ls.iter(); let mut it = ls.iter();
@ -81,35 +52,41 @@ impl TransactableValue for ValueAndSpan {
// Like "(transaction-id)". // Like "(transaction-id)".
(Some(&PlainSymbol(ref op)), None, None, None) => { (Some(&PlainSymbol(ref op)), None, None, None) => {
Ok(EntityPlace::TxFunction(TxFunction { op: op.clone() })) Ok(EntityPlace::TxFunction(TxFunction { op: op.clone() }))
}, }
// Like "(lookup-ref)". // Like "(lookup-ref)".
(Some(&PlainSymbol(edn::PlainSymbol(ref s))), Some(a), Some(v), None) if s == "lookup-ref" => { (Some(&PlainSymbol(edn::PlainSymbol(ref s))), Some(a), Some(v), None)
if s == "lookup-ref" =>
{
match a.clone().into_entity_place()? { match a.clone().into_entity_place()? {
EntityPlace::Entid(a) => Ok(EntityPlace::LookupRef(entities::LookupRef { a: entities::AttributePlace::Entid(a), v: v.clone() })), EntityPlace::Entid(a) => {
EntityPlace::TempId(_) | Ok(EntityPlace::LookupRef(entities::LookupRef {
EntityPlace::TxFunction(_) | a: entities::AttributePlace::Entid(a),
EntityPlace::LookupRef(_) => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)), v: v.clone(),
}))
}
EntityPlace::TempId(_)
| EntityPlace::TxFunction(_)
| EntityPlace::LookupRef(_) => {
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
}
} }
}, }
_ => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)), _ => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)),
} }
}, }
Nil | Nil | Boolean(_) | Instant(_) | BigInteger(_) | Float(_) | Uuid(_) | PlainSymbol(_)
Boolean(_) | | NamespacedSymbol(_) | Vector(_) | Set(_) | Map(_) => {
Instant(_) | bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
BigInteger(_) | }
Float(_) |
Uuid(_) |
PlainSymbol(_) |
NamespacedSymbol(_) |
Vector(_) |
Set(_) |
Map(_) => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)),
} }
} }
fn as_tempid(&self) -> Option<TempId> { fn as_tempid(&self) -> Option<TempId> {
self.inner.as_text().cloned().map(TempId::External).map(|v| v.into()) self.inner
.as_text()
.cloned()
.map(TempId::External)
.map(|v| v.into())
} }
} }
@ -124,13 +101,17 @@ impl TransactableValue for TypedValue {
fn into_entity_place(self) -> Result<EntityPlace<Self>> { fn into_entity_place(self) -> Result<EntityPlace<Self>> {
match self { match self {
TypedValue::Ref(x) => Ok(EntityPlace::Entid(entities::EntidOrIdent::Entid(x))), TypedValue::Ref(x) => Ok(EntityPlace::Entid(entities::EntidOrIdent::Entid(x))),
TypedValue::Keyword(x) => Ok(EntityPlace::Entid(entities::EntidOrIdent::Ident((*x).clone()))), TypedValue::Keyword(x) => Ok(EntityPlace::Entid(entities::EntidOrIdent::Ident(
(*x).clone(),
))),
TypedValue::String(x) => Ok(EntityPlace::TempId(TempId::External((*x).clone()).into())), TypedValue::String(x) => Ok(EntityPlace::TempId(TempId::External((*x).clone()).into())),
TypedValue::Boolean(_) | TypedValue::Boolean(_)
TypedValue::Long(_) | | TypedValue::Long(_)
TypedValue::Double(_) | | TypedValue::Double(_)
TypedValue::Instant(_) | | TypedValue::Instant(_)
TypedValue::Uuid(_) => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)), | TypedValue::Uuid(_) => {
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
}
} }
} }
@ -160,13 +141,14 @@ pub type LookupRef = ValueRc<AVPair>;
/// Internal representation of an entid on its way to resolution. We either have the simple case (a /// Internal representation of an entid on its way to resolution. We either have the simple case (a
/// numeric entid), a lookup-ref that still needs to be resolved (an atomized [a v] pair), or a temp /// numeric entid), a lookup-ref that still needs to be resolved (an atomized [a v] pair), or a temp
/// ID that needs to be upserted or allocated (an atomized tempid). /// ID that needs to be upserted or allocated (an atomized tempid).
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum LookupRefOrTempId { pub enum LookupRefOrTempId {
LookupRef(LookupRef), LookupRef(LookupRef),
TempId(TempIdHandle) TempId(TempIdHandle),
} }
pub type TermWithTempIdsAndLookupRefs = Term<KnownEntidOr<LookupRefOrTempId>, TypedValueOr<LookupRefOrTempId>>; pub type TermWithTempIdsAndLookupRefs =
Term<KnownEntidOr<LookupRefOrTempId>, TypedValueOr<LookupRefOrTempId>>;
pub type TermWithTempIds = Term<KnownEntidOr<TempIdHandle>, TypedValueOr<TempIdHandle>>; pub type TermWithTempIds = Term<KnownEntidOr<TempIdHandle>, TypedValueOr<TempIdHandle>>;
pub type TermWithoutTempIds = Term<KnownEntid, TypedValue>; pub type TermWithoutTempIds = Term<KnownEntid, TypedValue>;
pub type Population = Vec<TermWithTempIds>; pub type Population = Vec<TermWithTempIds>;
@ -186,7 +168,7 @@ impl TermWithTempIds {
impl TermWithoutTempIds { impl TermWithoutTempIds {
pub(crate) fn rewrap<A, B>(self) -> Term<KnownEntidOr<A>, TypedValueOr<B>> { pub(crate) fn rewrap<A, B>(self) -> Term<KnownEntidOr<A>, TypedValueOr<B>> {
match self { match self {
Term::AddOrRetract(op, n, a, v) => Term::AddOrRetract(op, Left(n), a, Left(v)) Term::AddOrRetract(op, n, a, v) => Term::AddOrRetract(op, Left(n), a, Left(v)),
} }
} }
} }
@ -200,16 +182,31 @@ impl TermWithoutTempIds {
/// The reason for this awkward expression is that we're parameterizing over the _type constructor_ /// The reason for this awkward expression is that we're parameterizing over the _type constructor_
/// (`EntidOr` or `TypedValueOr`), which is not trivial to express in Rust. This only works because /// (`EntidOr` or `TypedValueOr`), which is not trivial to express in Rust. This only works because
/// they're both the same `Result<...>` type with different parameterizations. /// they're both the same `Result<...>` type with different parameterizations.
pub fn replace_lookup_ref<T, U>(lookup_map: &AVMap, desired_or: Either<T, LookupRefOrTempId>, lift: U) -> errors::Result<Either<T, TempIdHandle>> where U: FnOnce(Entid) -> T { pub fn replace_lookup_ref<T, U>(
lookup_map: &AVMap,
desired_or: Either<T, LookupRefOrTempId>,
lift: U,
) -> errors::Result<Either<T, TempIdHandle>>
where
U: FnOnce(Entid) -> T,
{
match desired_or { match desired_or {
Left(desired) => Ok(Left(desired)), // N.b., must unwrap here -- the ::Left types are different! Left(desired) => Ok(Left(desired)), // N.b., must unwrap here -- the ::Left types are different!
Right(other) => { Right(other) => {
match other { match other {
LookupRefOrTempId::TempId(t) => Ok(Right(t)), LookupRefOrTempId::TempId(t) => Ok(Right(t)),
LookupRefOrTempId::LookupRef(av) => lookup_map.get(&*av) LookupRefOrTempId::LookupRef(av) => lookup_map
.map(|x| lift(*x)).map(Left) .get(&*av)
.map(|x| lift(*x))
.map(Left)
// XXX TODO: fix this error kind! // XXX TODO: fix this error kind!
.ok_or_else(|| DbErrorKind::UnrecognizedIdent(format!("couldn't lookup [a v]: {:?}", (*av).clone())).into()), .ok_or_else(|| {
DbErrorKind::UnrecognizedIdent(format!(
"couldn't lookup [a v]: {:?}",
(*av).clone()
))
.into()
}),
} }
} }
} }
@ -223,4 +220,5 @@ pub(crate) struct AddAndRetract {
// A trie-like structure mapping a -> e -> v that prefix compresses and makes uniqueness constraint // A trie-like structure mapping a -> e -> v that prefix compresses and makes uniqueness constraint
// checking more efficient. BTree* for deterministic errors. // checking more efficient. BTree* for deterministic errors.
pub(crate) type AEVTrie<'schema> = BTreeMap<(Entid, &'schema Attribute), BTreeMap<Entid, AddAndRetract>>; pub(crate) type AEVTrie<'schema> =
BTreeMap<(Entid, &'schema Attribute), BTreeMap<Entid, AddAndRetract>>;

View file

@ -11,113 +11,87 @@
extern crate failure; extern crate failure;
extern crate indexmap; extern crate indexmap;
extern crate itertools; extern crate itertools;
#[macro_use] extern crate lazy_static; #[macro_use]
#[macro_use] extern crate log; extern crate lazy_static;
#[macro_use]
extern crate log;
#[cfg(feature = "syncable")] #[cfg(feature = "syncable")]
#[macro_use] extern crate serde_derive; #[macro_use]
extern crate serde_derive;
extern crate petgraph; extern crate petgraph;
extern crate rusqlite; extern crate rusqlite;
extern crate tabwriter; extern crate tabwriter;
extern crate time; extern crate time;
#[macro_use] extern crate edn; #[macro_use]
#[macro_use] extern crate mentat_core; extern crate edn;
#[macro_use]
extern crate mentat_core;
extern crate db_traits; extern crate db_traits;
#[macro_use] extern crate core_traits; #[macro_use]
extern crate core_traits;
extern crate mentat_sql; extern crate mentat_sql;
use std::iter::repeat; use std::iter::repeat;
use itertools::Itertools; use itertools::Itertools;
use db_traits::errors::{ use db_traits::errors::{DbErrorKind, Result};
DbErrorKind,
Result,
};
#[macro_use] pub mod debug; #[macro_use]
pub mod debug;
mod add_retract_alter_set; mod add_retract_alter_set;
mod bootstrap;
pub mod cache; pub mod cache;
pub mod db; pub mod db;
mod bootstrap;
pub mod entids; pub mod entids;
pub mod internal_types; // pub because we need them for building entities programmatically. pub mod internal_types; // pub because we need them for building entities programmatically.
mod metadata; mod metadata;
mod schema; mod schema;
pub mod tx_observer;
mod watcher;
pub mod timelines; pub mod timelines;
mod tx; mod tx;
mod tx_checking; mod tx_checking;
pub mod tx_observer;
pub mod types; pub mod types;
mod upsert_resolution; mod upsert_resolution;
mod watcher;
// Export these for reference from sync code and tests. // Export these for reference from sync code and tests.
pub use bootstrap::{ pub use bootstrap::{TX0, USER0, V1_PARTS};
TX0,
USER0,
V1_PARTS,
};
pub static TIMELINE_MAIN: i64 = 0; pub static TIMELINE_MAIN: i64 = 0;
pub use schema::{ pub use schema::{AttributeBuilder, AttributeValidation};
AttributeBuilder,
AttributeValidation,
};
pub use bootstrap::{ pub use bootstrap::CORE_SCHEMA_VERSION;
CORE_SCHEMA_VERSION,
};
use edn::symbols; use edn::symbols;
pub use entids::{ pub use entids::DB_SCHEMA_CORE;
DB_SCHEMA_CORE,
};
pub use db::{ pub use db::{new_connection, TypedSQLValue};
TypedSQLValue,
new_connection,
};
#[cfg(feature = "sqlcipher")] #[cfg(feature = "sqlcipher")]
pub use db::{ pub use db::{change_encryption_key, new_connection_with_key};
new_connection_with_key,
change_encryption_key,
};
pub use watcher::{ pub use watcher::TransactWatcher;
TransactWatcher,
};
pub use tx::{ pub use tx::{transact, transact_terms};
transact,
transact_terms,
};
pub use tx_observer::{ pub use tx_observer::{InProgressObserverTransactWatcher, TxObservationService, TxObserver};
InProgressObserverTransactWatcher,
TxObservationService,
TxObserver,
};
pub use types::{ pub use types::{AttributeSet, Partition, PartitionMap, TransactableValue, DB};
AttributeSet,
DB,
Partition,
PartitionMap,
TransactableValue,
};
pub fn to_namespaced_keyword(s: &str) -> Result<symbols::Keyword> { pub fn to_namespaced_keyword(s: &str) -> Result<symbols::Keyword> {
let splits = [':', '/']; let splits = [':', '/'];
let mut i = s.split(&splits[..]); let mut i = s.split(&splits[..]);
let nsk = match (i.next(), i.next(), i.next(), i.next()) { let nsk = match (i.next(), i.next(), i.next(), i.next()) {
(Some(""), Some(namespace), Some(name), None) => Some(symbols::Keyword::namespaced(namespace, name)), (Some(""), Some(namespace), Some(name), None) => {
Some(symbols::Keyword::namespaced(namespace, name))
}
_ => None, _ => None,
}; };

View file

@ -26,39 +26,21 @@
use failure::ResultExt; use failure::ResultExt;
use std::collections::{BTreeMap, BTreeSet};
use std::collections::btree_map::Entry; use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, BTreeSet};
use add_retract_alter_set::{ use add_retract_alter_set::AddRetractAlterSet;
AddRetractAlterSet, use db_traits::errors::{DbErrorKind, Result};
};
use edn::symbols; use edn::symbols;
use entids; use entids;
use db_traits::errors::{
DbErrorKind,
Result,
};
use core_traits::{ use core_traits::{attribute, Entid, TypedValue, ValueType};
attribute,
Entid,
TypedValue,
ValueType,
};
use mentat_core::{ use mentat_core::{AttributeMap, Schema};
Schema,
AttributeMap,
};
use schema::{ use schema::{AttributeBuilder, AttributeValidation};
AttributeBuilder,
AttributeValidation,
};
use types::{ use types::EAV;
EAV,
};
/// An alteration to an attribute. /// An alteration to an attribute.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
@ -100,8 +82,7 @@ pub struct MetadataReport {
impl MetadataReport { impl MetadataReport {
pub fn attributes_did_change(&self) -> bool { pub fn attributes_did_change(&self) -> bool {
!(self.attributes_installed.is_empty() && !(self.attributes_installed.is_empty() && self.attributes_altered.is_empty())
self.attributes_altered.is_empty())
} }
} }
@ -115,7 +96,11 @@ impl MetadataReport {
/// - we're allowing optional attributes to not be retracted and dangle afterwards /// - we're allowing optional attributes to not be retracted and dangle afterwards
/// ///
/// Returns a set of attribute retractions which do not involve schema-defining attributes. /// Returns a set of attribute retractions which do not involve schema-defining attributes.
fn update_attribute_map_from_schema_retractions(attribute_map: &mut AttributeMap, retractions: Vec<EAV>, ident_retractions: &BTreeMap<Entid, symbols::Keyword>) -> Result<Vec<EAV>> { fn update_attribute_map_from_schema_retractions(
attribute_map: &mut AttributeMap,
retractions: Vec<EAV>,
ident_retractions: &BTreeMap<Entid, symbols::Keyword>,
) -> Result<Vec<EAV>> {
// Process retractions of schema attributes first. It's allowed to retract a schema attribute // Process retractions of schema attributes first. It's allowed to retract a schema attribute
// if all of the schema-defining schema attributes are being retracted. // if all of the schema-defining schema attributes are being retracted.
// A defining set of attributes is :db/ident, :db/valueType, :db/cardinality. // A defining set of attributes is :db/ident, :db/valueType, :db/cardinality.
@ -152,7 +137,9 @@ fn update_attribute_map_from_schema_retractions(attribute_map: &mut AttributeMap
let attributes = eas.get(&e).unwrap(); let attributes = eas.get(&e).unwrap();
// Found a set of retractions which negate a schema. // Found a set of retractions which negate a schema.
if attributes.contains(&entids::DB_CARDINALITY) && attributes.contains(&entids::DB_VALUE_TYPE) { if attributes.contains(&entids::DB_CARDINALITY)
&& attributes.contains(&entids::DB_VALUE_TYPE)
{
// Ensure that corresponding :db/ident is also being retracted at the same time. // Ensure that corresponding :db/ident is also being retracted at the same time.
if ident_retractions.contains_key(&e) { if ident_retractions.contains_key(&e) {
// Remove attributes corresponding to retracted attribute. // Remove attributes corresponding to retracted attribute.
@ -174,11 +161,19 @@ fn update_attribute_map_from_schema_retractions(attribute_map: &mut AttributeMap
/// contain install and alter markers. /// contain install and alter markers.
/// ///
/// Returns a report summarizing the mutations that were applied. /// Returns a report summarizing the mutations that were applied.
pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap, assertions: Vec<EAV>, retractions: Vec<EAV>) -> Result<MetadataReport> { pub fn update_attribute_map_from_entid_triples(
fn attribute_builder_to_modify(attribute_id: Entid, existing: &AttributeMap) -> AttributeBuilder { attribute_map: &mut AttributeMap,
existing.get(&attribute_id) assertions: Vec<EAV>,
.map(AttributeBuilder::to_modify_attribute) retractions: Vec<EAV>,
.unwrap_or_else(AttributeBuilder::default) ) -> Result<MetadataReport> {
fn attribute_builder_to_modify(
attribute_id: Entid,
existing: &AttributeMap,
) -> AttributeBuilder {
existing
.get(&attribute_id)
.map(AttributeBuilder::to_modify_attribute)
.unwrap_or_else(AttributeBuilder::default)
} }
// Group mutations by impacted entid. // Group mutations by impacted entid.
@ -187,7 +182,9 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap,
// For retractions, we start with an attribute builder that's pre-populated with the existing // For retractions, we start with an attribute builder that's pre-populated with the existing
// attribute values. That allows us to check existing values and unset them. // attribute values. That allows us to check existing values and unset them.
for (entid, attr, ref value) in retractions { for (entid, attr, ref value) in retractions {
let builder = builders.entry(entid).or_insert_with(|| attribute_builder_to_modify(entid, attribute_map)); let builder = builders
.entry(entid)
.or_insert_with(|| attribute_builder_to_modify(entid, attribute_map));
match attr { match attr {
// You can only retract :db/unique, :db/isComponent; all others must be altered instead // You can only retract :db/unique, :db/isComponent; all others must be altered instead
// of retracted, or are not allowed to change. // of retracted, or are not allowed to change.
@ -303,7 +300,7 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap,
bail!(DbErrorKind::BadSchemaAssertion(format!("Do not recognize attribute {} for entid {}", attr, entid))) bail!(DbErrorKind::BadSchemaAssertion(format!("Do not recognize attribute {} for entid {}", attr, entid)))
} }
} }
}; }
let mut attributes_installed: BTreeSet<Entid> = BTreeSet::default(); let mut attributes_installed: BTreeSet<Entid> = BTreeSet::default();
let mut attributes_altered: BTreeMap<Entid, Vec<AttributeAlteration>> = BTreeMap::default(); let mut attributes_altered: BTreeMap<Entid, Vec<AttributeAlteration>> = BTreeMap::default();
@ -312,20 +309,30 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap,
match attribute_map.entry(entid) { match attribute_map.entry(entid) {
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
// Validate once… // Validate once…
builder.validate_install_attribute().context(DbErrorKind::BadSchemaAssertion(format!("Schema alteration for new attribute with entid {} is not valid", entid)))?; builder
.validate_install_attribute()
.context(DbErrorKind::BadSchemaAssertion(format!(
"Schema alteration for new attribute with entid {} is not valid",
entid
)))?;
// … and twice, now we have the Attribute. // … and twice, now we have the Attribute.
let a = builder.build(); let a = builder.build();
a.validate(|| entid.to_string())?; a.validate(|| entid.to_string())?;
entry.insert(a); entry.insert(a);
attributes_installed.insert(entid); attributes_installed.insert(entid);
}, }
Entry::Occupied(mut entry) => { Entry::Occupied(mut entry) => {
builder.validate_alter_attribute().context(DbErrorKind::BadSchemaAssertion(format!("Schema alteration for existing attribute with entid {} is not valid", entid)))?; builder
.validate_alter_attribute()
.context(DbErrorKind::BadSchemaAssertion(format!(
"Schema alteration for existing attribute with entid {} is not valid",
entid
)))?;
let mutations = builder.mutate(entry.get_mut()); let mutations = builder.mutate(entry.get_mut());
attributes_altered.insert(entid, mutations); attributes_altered.insert(entid, mutations);
}, }
} }
} }
@ -344,14 +351,19 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap,
/// This is suitable for mutating a `Schema` from an applied transaction. /// This is suitable for mutating a `Schema` from an applied transaction.
/// ///
/// Returns a report summarizing the mutations that were applied. /// Returns a report summarizing the mutations that were applied.
pub fn update_schema_from_entid_quadruples<U>(schema: &mut Schema, assertions: U) -> Result<MetadataReport> pub fn update_schema_from_entid_quadruples<U>(
where U: IntoIterator<Item=(Entid, Entid, TypedValue, bool)> { schema: &mut Schema,
assertions: U,
) -> Result<MetadataReport>
where
U: IntoIterator<Item = (Entid, Entid, TypedValue, bool)>,
{
// Group attribute assertions into asserted, retracted, and updated. We assume all our // Group attribute assertions into asserted, retracted, and updated. We assume all our
// attribute assertions are :db/cardinality :db.cardinality/one (so they'll only be added or // attribute assertions are :db/cardinality :db.cardinality/one (so they'll only be added or
// retracted at most once), which means all attribute alterations are simple changes from an old // retracted at most once), which means all attribute alterations are simple changes from an old
// value to a new value. // value to a new value.
let mut attribute_set: AddRetractAlterSet<(Entid, Entid), TypedValue> = AddRetractAlterSet::default(); let mut attribute_set: AddRetractAlterSet<(Entid, Entid), TypedValue> =
AddRetractAlterSet::default();
let mut ident_set: AddRetractAlterSet<Entid, symbols::Keyword> = AddRetractAlterSet::default(); let mut ident_set: AddRetractAlterSet<Entid, symbols::Keyword> = AddRetractAlterSet::default();
for (e, a, typed_value, added) in assertions.into_iter() { for (e, a, typed_value, added) in assertions.into_iter() {
@ -359,7 +371,7 @@ pub fn update_schema_from_entid_quadruples<U>(schema: &mut Schema, assertions: U
if a == entids::DB_IDENT { if a == entids::DB_IDENT {
if let TypedValue::Keyword(ref keyword) = typed_value { if let TypedValue::Keyword(ref keyword) = typed_value {
ident_set.witness(e, keyword.as_ref().clone(), added); ident_set.witness(e, keyword.as_ref().clone(), added);
continue continue;
} else { } else {
// Something is terribly wrong: the schema ensures we have a keyword. // Something is terribly wrong: the schema ensures we have a keyword.
unreachable!(); unreachable!();
@ -370,20 +382,33 @@ pub fn update_schema_from_entid_quadruples<U>(schema: &mut Schema, assertions: U
} }
// Collect triples. // Collect triples.
let retracted_triples = attribute_set.retracted.into_iter().map(|((e, a), typed_value)| (e, a, typed_value)); let retracted_triples = attribute_set
let asserted_triples = attribute_set.asserted.into_iter().map(|((e, a), typed_value)| (e, a, typed_value)); .retracted
let altered_triples = attribute_set.altered.into_iter().map(|((e, a), (_old_value, new_value))| (e, a, new_value)); .into_iter()
.map(|((e, a), typed_value)| (e, a, typed_value));
let asserted_triples = attribute_set
.asserted
.into_iter()
.map(|((e, a), typed_value)| (e, a, typed_value));
let altered_triples = attribute_set
.altered
.into_iter()
.map(|((e, a), (_old_value, new_value))| (e, a, new_value));
// First we process retractions which remove schema. // First we process retractions which remove schema.
// This operation consumes our current list of attribute retractions, producing a filtered one. // This operation consumes our current list of attribute retractions, producing a filtered one.
let non_schema_retractions = update_attribute_map_from_schema_retractions(&mut schema.attribute_map, let non_schema_retractions = update_attribute_map_from_schema_retractions(
retracted_triples.collect(), &mut schema.attribute_map,
&ident_set.retracted)?; retracted_triples.collect(),
&ident_set.retracted,
)?;
// Now we process all other retractions. // Now we process all other retractions.
let report = update_attribute_map_from_entid_triples(&mut schema.attribute_map, let report = update_attribute_map_from_entid_triples(
asserted_triples.chain(altered_triples).collect(), &mut schema.attribute_map,
non_schema_retractions)?; asserted_triples.chain(altered_triples).collect(),
non_schema_retractions,
)?;
let mut idents_altered: BTreeMap<Entid, IdentAlteration> = BTreeMap::new(); let mut idents_altered: BTreeMap<Entid, IdentAlteration> = BTreeMap::new();
@ -420,6 +445,6 @@ pub fn update_schema_from_entid_quadruples<U>(schema: &mut Schema, assertions: U
Ok(MetadataReport { Ok(MetadataReport {
idents_altered: idents_altered, idents_altered: idents_altered,
.. report ..report
}) })
} }

View file

@ -11,54 +11,56 @@
#![allow(dead_code)] #![allow(dead_code)]
use db::TypedSQLValue; use db::TypedSQLValue;
use db_traits::errors::{DbErrorKind, Result};
use edn; use edn;
use db_traits::errors::{
DbErrorKind,
Result,
};
use edn::symbols; use edn::symbols;
use core_traits::{ use core_traits::{attribute, Attribute, Entid, KnownEntid, TypedValue, ValueType};
attribute,
Attribute,
Entid,
KnownEntid,
TypedValue,
ValueType,
};
use mentat_core::{ use mentat_core::{AttributeMap, EntidMap, HasSchema, IdentMap, Schema};
EntidMap,
HasSchema,
IdentMap,
Schema,
AttributeMap,
};
use metadata; use metadata;
use metadata::{ use metadata::AttributeAlteration;
AttributeAlteration,
};
pub trait AttributeValidation { pub trait AttributeValidation {
fn validate<F>(&self, ident: F) -> Result<()> where F: Fn() -> String; fn validate<F>(&self, ident: F) -> Result<()>
where
F: Fn() -> String;
} }
impl AttributeValidation for Attribute { impl AttributeValidation for Attribute {
fn validate<F>(&self, ident: F) -> Result<()> where F: Fn() -> String { fn validate<F>(&self, ident: F) -> Result<()>
where
F: Fn() -> String,
{
if self.unique == Some(attribute::Unique::Value) && !self.index { if self.unique == Some(attribute::Unique::Value) && !self.index {
bail!(DbErrorKind::BadSchemaAssertion(format!(":db/unique :db/unique_value without :db/index true for entid: {}", ident()))) bail!(DbErrorKind::BadSchemaAssertion(format!(
":db/unique :db/unique_value without :db/index true for entid: {}",
ident()
)))
} }
if self.unique == Some(attribute::Unique::Identity) && !self.index { if self.unique == Some(attribute::Unique::Identity) && !self.index {
bail!(DbErrorKind::BadSchemaAssertion(format!(":db/unique :db/unique_identity without :db/index true for entid: {}", ident()))) bail!(DbErrorKind::BadSchemaAssertion(format!(
":db/unique :db/unique_identity without :db/index true for entid: {}",
ident()
)))
} }
if self.fulltext && self.value_type != ValueType::String { if self.fulltext && self.value_type != ValueType::String {
bail!(DbErrorKind::BadSchemaAssertion(format!(":db/fulltext true without :db/valueType :db.type/string for entid: {}", ident()))) bail!(DbErrorKind::BadSchemaAssertion(format!(
":db/fulltext true without :db/valueType :db.type/string for entid: {}",
ident()
)))
} }
if self.fulltext && !self.index { if self.fulltext && !self.index {
bail!(DbErrorKind::BadSchemaAssertion(format!(":db/fulltext true without :db/index true for entid: {}", ident()))) bail!(DbErrorKind::BadSchemaAssertion(format!(
":db/fulltext true without :db/index true for entid: {}",
ident()
)))
} }
if self.component && self.value_type != ValueType::Ref { if self.component && self.value_type != ValueType::Ref {
bail!(DbErrorKind::BadSchemaAssertion(format!(":db/isComponent true without :db/valueType :db.type/ref for entid: {}", ident()))) bail!(DbErrorKind::BadSchemaAssertion(format!(
":db/isComponent true without :db/valueType :db.type/ref for entid: {}",
ident()
)))
} }
// TODO: consider warning if we have :db/index true for :db/valueType :db.type/string, // TODO: consider warning if we have :db/index true for :db/valueType :db.type/string,
// since this may be inefficient. More generally, we should try to drive complex // since this may be inefficient. More generally, we should try to drive complex
@ -71,13 +73,18 @@ impl AttributeValidation for Attribute {
/// Return `Ok(())` if `attribute_map` defines a valid Mentat schema. /// Return `Ok(())` if `attribute_map` defines a valid Mentat schema.
fn validate_attribute_map(entid_map: &EntidMap, attribute_map: &AttributeMap) -> Result<()> { fn validate_attribute_map(entid_map: &EntidMap, attribute_map: &AttributeMap) -> Result<()> {
for (entid, attribute) in attribute_map { for (entid, attribute) in attribute_map {
let ident = || entid_map.get(entid).map(|ident| ident.to_string()).unwrap_or(entid.to_string()); let ident = || {
entid_map
.get(entid)
.map(|ident| ident.to_string())
.unwrap_or(entid.to_string())
};
attribute.validate(ident)?; attribute.validate(ident)?;
} }
Ok(()) Ok(())
} }
#[derive(Clone,Debug,Default,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct AttributeBuilder { pub struct AttributeBuilder {
helpful: bool, helpful: bool,
pub value_type: Option<ValueType>, pub value_type: Option<ValueType>,
@ -103,9 +110,9 @@ impl AttributeBuilder {
/// retraction. Only attributes that we allow to change are duplicated here. /// retraction. Only attributes that we allow to change are duplicated here.
pub fn to_modify_attribute(attribute: &Attribute) -> Self { pub fn to_modify_attribute(attribute: &Attribute) -> Self {
let mut ab = AttributeBuilder::default(); let mut ab = AttributeBuilder::default();
ab.multival = Some(attribute.multival); ab.multival = Some(attribute.multival);
ab.unique = Some(attribute.unique); ab.unique = Some(attribute.unique);
ab.component = Some(attribute.component); ab.component = Some(attribute.component);
ab ab
} }
@ -157,17 +164,23 @@ impl AttributeBuilder {
pub fn validate_install_attribute(&self) -> Result<()> { pub fn validate_install_attribute(&self) -> Result<()> {
if self.value_type.is_none() { if self.value_type.is_none() {
bail!(DbErrorKind::BadSchemaAssertion("Schema attribute for new attribute does not set :db/valueType".into())); bail!(DbErrorKind::BadSchemaAssertion(
"Schema attribute for new attribute does not set :db/valueType".into()
));
} }
Ok(()) Ok(())
} }
pub fn validate_alter_attribute(&self) -> Result<()> { pub fn validate_alter_attribute(&self) -> Result<()> {
if self.value_type.is_some() { if self.value_type.is_some() {
bail!(DbErrorKind::BadSchemaAssertion("Schema alteration must not set :db/valueType".into())); bail!(DbErrorKind::BadSchemaAssertion(
"Schema alteration must not set :db/valueType".into()
));
} }
if self.fulltext.is_some() { if self.fulltext.is_some() {
bail!(DbErrorKind::BadSchemaAssertion("Schema alteration must not set :db/fulltext".into())); bail!(DbErrorKind::BadSchemaAssertion(
"Schema alteration must not set :db/fulltext".into()
));
} }
Ok(()) Ok(())
} }
@ -247,27 +260,40 @@ pub trait SchemaBuilding {
fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword>; fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword>;
fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid>; fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid>;
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute>; fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute>;
fn from_ident_map_and_attribute_map(ident_map: IdentMap, attribute_map: AttributeMap) -> Result<Schema>; fn from_ident_map_and_attribute_map(
ident_map: IdentMap,
attribute_map: AttributeMap,
) -> Result<Schema>;
fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema> fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
where U: IntoIterator<Item=(symbols::Keyword, symbols::Keyword, TypedValue)>; where
U: IntoIterator<Item = (symbols::Keyword, symbols::Keyword, TypedValue)>;
} }
impl SchemaBuilding for Schema { impl SchemaBuilding for Schema {
fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword> { fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword> {
self.get_ident(entid).ok_or(DbErrorKind::UnrecognizedEntid(entid).into()) self.get_ident(entid)
.ok_or(DbErrorKind::UnrecognizedEntid(entid).into())
} }
fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid> { fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid> {
self.get_entid(&ident).ok_or(DbErrorKind::UnrecognizedIdent(ident.to_string()).into()) self.get_entid(&ident)
.ok_or(DbErrorKind::UnrecognizedIdent(ident.to_string()).into())
} }
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute> { fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute> {
self.attribute_for_entid(entid).ok_or(DbErrorKind::UnrecognizedEntid(entid).into()) self.attribute_for_entid(entid)
.ok_or(DbErrorKind::UnrecognizedEntid(entid).into())
} }
/// Create a valid `Schema` from the constituent maps. /// Create a valid `Schema` from the constituent maps.
fn from_ident_map_and_attribute_map(ident_map: IdentMap, attribute_map: AttributeMap) -> Result<Schema> { fn from_ident_map_and_attribute_map(
let entid_map: EntidMap = ident_map.iter().map(|(k, v)| (v.clone(), k.clone())).collect(); ident_map: IdentMap,
attribute_map: AttributeMap,
) -> Result<Schema> {
let entid_map: EntidMap = ident_map
.iter()
.map(|(k, v)| (v.clone(), k.clone()))
.collect();
validate_attribute_map(&entid_map, &attribute_map)?; validate_attribute_map(&entid_map, &attribute_map)?;
Ok(Schema::new(ident_map, entid_map, attribute_map)) Ok(Schema::new(ident_map, entid_map, attribute_map))
@ -275,19 +301,30 @@ impl SchemaBuilding for Schema {
/// Turn vec![(Keyword(:ident), Keyword(:key), TypedValue(:value)), ...] into a Mentat `Schema`. /// Turn vec![(Keyword(:ident), Keyword(:key), TypedValue(:value)), ...] into a Mentat `Schema`.
fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema> fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
where U: IntoIterator<Item=(symbols::Keyword, symbols::Keyword, TypedValue)>{ where
U: IntoIterator<Item = (symbols::Keyword, symbols::Keyword, TypedValue)>,
{
let entid_assertions: Result<Vec<(Entid, Entid, TypedValue)>> = assertions
.into_iter()
.map(|(symbolic_ident, symbolic_attr, value)| {
let ident: i64 = *ident_map
.get(&symbolic_ident)
.ok_or(DbErrorKind::UnrecognizedIdent(symbolic_ident.to_string()))?;
let attr: i64 = *ident_map
.get(&symbolic_attr)
.ok_or(DbErrorKind::UnrecognizedIdent(symbolic_attr.to_string()))?;
Ok((ident, attr, value))
})
.collect();
let entid_assertions: Result<Vec<(Entid, Entid, TypedValue)>> = assertions.into_iter().map(|(symbolic_ident, symbolic_attr, value)| { let mut schema =
let ident: i64 = *ident_map.get(&symbolic_ident).ok_or(DbErrorKind::UnrecognizedIdent(symbolic_ident.to_string()))?; Schema::from_ident_map_and_attribute_map(ident_map, AttributeMap::default())?;
let attr: i64 = *ident_map.get(&symbolic_attr).ok_or(DbErrorKind::UnrecognizedIdent(symbolic_attr.to_string()))?; let metadata_report = metadata::update_attribute_map_from_entid_triples(
Ok((ident, attr, value)) &mut schema.attribute_map,
}).collect(); entid_assertions?,
// No retractions.
let mut schema = Schema::from_ident_map_and_attribute_map(ident_map, AttributeMap::default())?; vec![],
let metadata_report = metadata::update_attribute_map_from_entid_triples(&mut schema.attribute_map, )?;
entid_assertions?,
// No retractions.
vec![])?;
// Rebuild the component attributes list if necessary. // Rebuild the component attributes list if necessary.
if metadata_report.attributes_did_change() { if metadata_report.attributes_did_change() {
@ -302,11 +339,19 @@ pub trait SchemaTypeChecking {
/// ///
/// Either assert that the given value is in the value type's value set, or (in limited cases) /// Either assert that the given value is in the value type's value set, or (in limited cases)
/// coerce the given value into the value type's value set. /// coerce the given value into the value type's value set.
fn to_typed_value(&self, value: &edn::ValueAndSpan, value_type: ValueType) -> Result<TypedValue>; fn to_typed_value(
&self,
value: &edn::ValueAndSpan,
value_type: ValueType,
) -> Result<TypedValue>;
} }
impl SchemaTypeChecking for Schema { impl SchemaTypeChecking for Schema {
fn to_typed_value(&self, value: &edn::ValueAndSpan, value_type: ValueType) -> Result<TypedValue> { fn to_typed_value(
&self,
value: &edn::ValueAndSpan,
value_type: ValueType,
) -> Result<TypedValue> {
// TODO: encapsulate entid-ident-attribute for better error messages, perhaps by including // TODO: encapsulate entid-ident-attribute for better error messages, perhaps by including
// the attribute (rather than just the attribute's value type) into this function or a // the attribute (rather than just the attribute's value type) into this function or a
// wrapper function. // wrapper function.
@ -324,38 +369,35 @@ impl SchemaTypeChecking for Schema {
(ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv), (ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv),
// Ref coerces a little: we interpret some things depending on the schema as a Ref. // Ref coerces a little: we interpret some things depending on the schema as a Ref.
(ValueType::Ref, TypedValue::Long(x)) => Ok(TypedValue::Ref(x)), (ValueType::Ref, TypedValue::Long(x)) => Ok(TypedValue::Ref(x)),
(ValueType::Ref, TypedValue::Keyword(ref x)) => self.require_entid(&x).map(|entid| entid.into()), (ValueType::Ref, TypedValue::Keyword(ref x)) => {
self.require_entid(&x).map(|entid| entid.into())
}
// Otherwise, we have a type mismatch. // Otherwise, we have a type mismatch.
// Enumerate all of the types here to allow the compiler to help us. // Enumerate all of the types here to allow the compiler to help us.
// We don't enumerate all `TypedValue` cases, though: that would multiply this // We don't enumerate all `TypedValue` cases, though: that would multiply this
// collection by 8! // collection by 8!
(vt @ ValueType::Boolean, _) | (vt @ ValueType::Boolean, _)
(vt @ ValueType::Long, _) | | (vt @ ValueType::Long, _)
(vt @ ValueType::Double, _) | | (vt @ ValueType::Double, _)
(vt @ ValueType::String, _) | | (vt @ ValueType::String, _)
(vt @ ValueType::Uuid, _) | | (vt @ ValueType::Uuid, _)
(vt @ ValueType::Instant, _) | | (vt @ ValueType::Instant, _)
(vt @ ValueType::Keyword, _) | | (vt @ ValueType::Keyword, _)
(vt @ ValueType::Ref, _) | (vt @ ValueType::Ref, _) => {
=> bail!(DbErrorKind::BadValuePair(format!("{}", value), vt)), bail!(DbErrorKind::BadValuePair(format!("{}", value), vt))
} }
},
} }
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*;
use self::edn::Keyword; use self::edn::Keyword;
use super::*;
fn add_attribute(schema: &mut Schema, fn add_attribute(schema: &mut Schema, ident: Keyword, entid: Entid, attribute: Attribute) {
ident: Keyword,
entid: Entid,
attribute: Attribute) {
schema.entid_map.insert(entid, ident.clone()); schema.entid_map.insert(entid, ident.clone());
schema.ident_map.insert(ident.clone(), entid); schema.ident_map.insert(ident.clone(), entid);
@ -370,55 +412,80 @@ mod test {
fn validate_attribute_map_success() { fn validate_attribute_map_success() {
let mut schema = Schema::default(); let mut schema = Schema::default();
// attribute that is not an index has no uniqueness // attribute that is not an index has no uniqueness
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 97, Attribute { add_attribute(
index: false, &mut schema,
value_type: ValueType::Boolean, Keyword::namespaced("foo", "bar"),
fulltext: false, 97,
unique: None, Attribute {
multival: false, index: false,
component: false, value_type: ValueType::Boolean,
no_history: false, fulltext: false,
}); unique: None,
multival: false,
component: false,
no_history: false,
},
);
// attribute is unique by value and an index // attribute is unique by value and an index
add_attribute(&mut schema, Keyword::namespaced("foo", "baz"), 98, Attribute { add_attribute(
index: true, &mut schema,
value_type: ValueType::Long, Keyword::namespaced("foo", "baz"),
fulltext: false, 98,
unique: Some(attribute::Unique::Value), Attribute {
multival: false, index: true,
component: false, value_type: ValueType::Long,
no_history: false, fulltext: false,
}); unique: Some(attribute::Unique::Value),
multival: false,
component: false,
no_history: false,
},
);
// attribue is unique by identity and an index // attribue is unique by identity and an index
add_attribute(&mut schema, Keyword::namespaced("foo", "bat"), 99, Attribute { add_attribute(
index: true, &mut schema,
value_type: ValueType::Ref, Keyword::namespaced("foo", "bat"),
fulltext: false, 99,
unique: Some(attribute::Unique::Identity), Attribute {
multival: false, index: true,
component: false, value_type: ValueType::Ref,
no_history: false, fulltext: false,
}); unique: Some(attribute::Unique::Identity),
multival: false,
component: false,
no_history: false,
},
);
// attribute is a components and a `Ref` // attribute is a components and a `Ref`
add_attribute(&mut schema, Keyword::namespaced("foo", "bak"), 100, Attribute { add_attribute(
index: false, &mut schema,
value_type: ValueType::Ref, Keyword::namespaced("foo", "bak"),
fulltext: false, 100,
unique: None, Attribute {
multival: false, index: false,
component: true, value_type: ValueType::Ref,
no_history: false, fulltext: false,
}); unique: None,
multival: false,
component: true,
no_history: false,
},
);
// fulltext attribute is a string and an index // fulltext attribute is a string and an index
add_attribute(&mut schema, Keyword::namespaced("foo", "bap"), 101, Attribute { add_attribute(
index: true, &mut schema,
value_type: ValueType::String, Keyword::namespaced("foo", "bap"),
fulltext: true, 101,
unique: None, Attribute {
multival: false, index: true,
component: false, value_type: ValueType::String,
no_history: false, fulltext: true,
}); unique: None,
multival: false,
component: false,
no_history: false,
},
);
assert!(validate_attribute_map(&schema.entid_map, &schema.attribute_map).is_ok()); assert!(validate_attribute_map(&schema.entid_map, &schema.attribute_map).is_ok());
} }
@ -428,88 +495,150 @@ mod test {
let mut schema = Schema::default(); let mut schema = Schema::default();
// attribute unique by value but not index // attribute unique by value but not index
let ident = Keyword::namespaced("foo", "bar"); let ident = Keyword::namespaced("foo", "bar");
add_attribute(&mut schema, ident , 99, Attribute { add_attribute(
index: false, &mut schema,
value_type: ValueType::Boolean, ident,
fulltext: false, 99,
unique: Some(attribute::Unique::Value), Attribute {
multival: false, index: false,
component: false, value_type: ValueType::Boolean,
no_history: false, fulltext: false,
}); unique: Some(attribute::Unique::Value),
multival: false,
component: false,
no_history: false,
},
);
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind()); let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/unique :db/unique_value without :db/index true for entid: :foo/bar".into()))); .err()
.map(|e| e.kind());
assert_eq!(
err,
Some(DbErrorKind::BadSchemaAssertion(
":db/unique :db/unique_value without :db/index true for entid: :foo/bar".into()
))
);
} }
#[test] #[test]
fn invalid_schema_unique_identity_not_index() { fn invalid_schema_unique_identity_not_index() {
let mut schema = Schema::default(); let mut schema = Schema::default();
// attribute is unique by identity but not index // attribute is unique by identity but not index
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute { add_attribute(
index: false, &mut schema,
value_type: ValueType::Long, Keyword::namespaced("foo", "bar"),
fulltext: false, 99,
unique: Some(attribute::Unique::Identity), Attribute {
multival: false, index: false,
component: false, value_type: ValueType::Long,
no_history: false, fulltext: false,
}); unique: Some(attribute::Unique::Identity),
multival: false,
component: false,
no_history: false,
},
);
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind()); let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/unique :db/unique_identity without :db/index true for entid: :foo/bar".into()))); .err()
.map(|e| e.kind());
assert_eq!(
err,
Some(DbErrorKind::BadSchemaAssertion(
":db/unique :db/unique_identity without :db/index true for entid: :foo/bar".into()
))
);
} }
#[test] #[test]
fn invalid_schema_component_not_ref() { fn invalid_schema_component_not_ref() {
let mut schema = Schema::default(); let mut schema = Schema::default();
// attribute that is a component is not a `Ref` // attribute that is a component is not a `Ref`
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute { add_attribute(
index: false, &mut schema,
value_type: ValueType::Boolean, Keyword::namespaced("foo", "bar"),
fulltext: false, 99,
unique: None, Attribute {
multival: false, index: false,
component: true, value_type: ValueType::Boolean,
no_history: false, fulltext: false,
}); unique: None,
multival: false,
component: true,
no_history: false,
},
);
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind()); let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/isComponent true without :db/valueType :db.type/ref for entid: :foo/bar".into()))); .err()
.map(|e| e.kind());
assert_eq!(
err,
Some(DbErrorKind::BadSchemaAssertion(
":db/isComponent true without :db/valueType :db.type/ref for entid: :foo/bar"
.into()
))
);
} }
#[test] #[test]
fn invalid_schema_fulltext_not_index() { fn invalid_schema_fulltext_not_index() {
let mut schema = Schema::default(); let mut schema = Schema::default();
// attribute that is fulltext is not an index // attribute that is fulltext is not an index
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute { add_attribute(
index: false, &mut schema,
value_type: ValueType::String, Keyword::namespaced("foo", "bar"),
fulltext: true, 99,
unique: None, Attribute {
multival: false, index: false,
component: false, value_type: ValueType::String,
no_history: false, fulltext: true,
}); unique: None,
multival: false,
component: false,
no_history: false,
},
);
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind()); let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/fulltext true without :db/index true for entid: :foo/bar".into()))); .err()
.map(|e| e.kind());
assert_eq!(
err,
Some(DbErrorKind::BadSchemaAssertion(
":db/fulltext true without :db/index true for entid: :foo/bar".into()
))
);
} }
fn invalid_schema_fulltext_index_not_string() { fn invalid_schema_fulltext_index_not_string() {
let mut schema = Schema::default(); let mut schema = Schema::default();
// attribute that is fulltext and not a `String` // attribute that is fulltext and not a `String`
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute { add_attribute(
index: true, &mut schema,
value_type: ValueType::Long, Keyword::namespaced("foo", "bar"),
fulltext: true, 99,
unique: None, Attribute {
multival: false, index: true,
component: false, value_type: ValueType::Long,
no_history: false, fulltext: true,
}); unique: None,
multival: false,
component: false,
no_history: false,
},
);
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind()); let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/fulltext true without :db/valueType :db.type/string for entid: :foo/bar".into()))); .err()
.map(|e| e.kind());
assert_eq!(
err,
Some(DbErrorKind::BadSchemaAssertion(
":db/fulltext true without :db/valueType :db.type/string for entid: :foo/bar"
.into()
))
);
} }
} }

View file

@ -12,57 +12,39 @@ use std::ops::RangeFrom;
use rusqlite; use rusqlite;
use db_traits::errors::{ use db_traits::errors::{DbErrorKind, Result};
DbErrorKind,
Result,
};
use core_traits::{ use core_traits::{Entid, KnownEntid, TypedValue};
Entid,
KnownEntid,
TypedValue,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::{ use edn::InternSet;
InternSet,
};
use edn::entities::OpType; use edn::entities::OpType;
use db; use db;
use db::{ use db::TypedSQLValue;
TypedSQLValue,
};
use tx::{ use tx::{transact_terms_with_action, TransactorAction};
transact_terms_with_action,
TransactorAction,
};
use types::{ use types::PartitionMap;
PartitionMap,
};
use internal_types::{ use internal_types::{Term, TermWithoutTempIds};
Term,
TermWithoutTempIds,
};
use watcher::{ use watcher::NullWatcher;
NullWatcher,
};
/// Collects a supplied tx range into an DESC ordered Vec of valid txs, /// Collects a supplied tx range into an DESC ordered Vec of valid txs,
/// ensuring they all belong to the same timeline. /// ensuring they all belong to the same timeline.
fn collect_ordered_txs_to_move(conn: &rusqlite::Connection, txs_from: RangeFrom<Entid>, timeline: Entid) -> Result<Vec<Entid>> { fn collect_ordered_txs_to_move(
conn: &rusqlite::Connection,
txs_from: RangeFrom<Entid>,
timeline: Entid,
) -> Result<Vec<Entid>> {
let mut stmt = conn.prepare("SELECT tx, timeline FROM timelined_transactions WHERE tx >= ? AND timeline = ? GROUP BY tx ORDER BY tx DESC")?; let mut stmt = conn.prepare("SELECT tx, timeline FROM timelined_transactions WHERE tx >= ? AND timeline = ? GROUP BY tx ORDER BY tx DESC")?;
let mut rows = stmt.query_and_then(&[&txs_from.start, &timeline], |row: &rusqlite::Row| -> Result<(Entid, Entid)>{ let mut rows = stmt.query_and_then(
Ok((row.get_checked(0)?, row.get_checked(1)?)) &[&txs_from.start, &timeline],
})?; |row: &rusqlite::Row| -> Result<(Entid, Entid)> { Ok((row.get(0)?, row.get(1)?)) },
)?;
let mut txs = vec![]; let mut txs = vec![];
@ -72,8 +54,8 @@ fn collect_ordered_txs_to_move(conn: &rusqlite::Connection, txs_from: RangeFrom<
let t = t?; let t = t?;
txs.push(t.0); txs.push(t.0);
t.1 t.1
}, }
None => bail!(DbErrorKind::TimelinesInvalidRange) None => bail!(DbErrorKind::TimelinesInvalidRange),
}; };
while let Some(t) = rows.next() { while let Some(t) = rows.next() {
@ -87,13 +69,22 @@ fn collect_ordered_txs_to_move(conn: &rusqlite::Connection, txs_from: RangeFrom<
Ok(txs) Ok(txs)
} }
fn move_transactions_to(conn: &rusqlite::Connection, tx_ids: &[Entid], new_timeline: Entid) -> Result<()> { fn move_transactions_to(
conn: &rusqlite::Connection,
tx_ids: &[Entid],
new_timeline: Entid,
) -> Result<()> {
// Move specified transactions over to a specified timeline. // Move specified transactions over to a specified timeline.
conn.execute(&format!( conn.execute(
"UPDATE timelined_transactions SET timeline = {} WHERE tx IN {}", &format!(
"UPDATE timelined_transactions SET timeline = {} WHERE tx IN {}",
new_timeline, new_timeline,
::repeat_values(tx_ids.len(), 1) ::repeat_values(tx_ids.len(), 1)
), &(tx_ids.iter().map(|x| x as &rusqlite::types::ToSql).collect::<Vec<_>>()) ),
&(tx_ids
.iter()
.map(|x| x as &dyn rusqlite::types::ToSql)
.collect::<Vec<_>>()),
)?; )?;
Ok(()) Ok(())
} }
@ -104,28 +95,34 @@ fn remove_tx_from_datoms(conn: &rusqlite::Connection, tx_id: Entid) -> Result<()
} }
fn is_timeline_empty(conn: &rusqlite::Connection, timeline: Entid) -> Result<bool> { fn is_timeline_empty(conn: &rusqlite::Connection, timeline: Entid) -> Result<bool> {
let mut stmt = conn.prepare("SELECT timeline FROM timelined_transactions WHERE timeline = ? GROUP BY timeline")?; let mut stmt = conn.prepare(
let rows = stmt.query_and_then(&[&timeline], |row| -> Result<i64> { "SELECT timeline FROM timelined_transactions WHERE timeline = ? GROUP BY timeline",
Ok(row.get_checked(0)?) )?;
})?; let rows = stmt.query_and_then(&[&timeline], |row| -> Result<i64> { Ok(row.get(0)?) })?;
Ok(rows.count() == 0) Ok(rows.count() == 0)
} }
/// Get terms for tx_id, reversing them in meaning (swap add & retract). /// Get terms for tx_id, reversing them in meaning (swap add & retract).
fn reversed_terms_for(conn: &rusqlite::Connection, tx_id: Entid) -> Result<Vec<TermWithoutTempIds>> { fn reversed_terms_for(
conn: &rusqlite::Connection,
tx_id: Entid,
) -> Result<Vec<TermWithoutTempIds>> {
let mut stmt = conn.prepare("SELECT e, a, v, value_type_tag, tx, added FROM timelined_transactions WHERE tx = ? AND timeline = ? ORDER BY tx DESC")?; let mut stmt = conn.prepare("SELECT e, a, v, value_type_tag, tx, added FROM timelined_transactions WHERE tx = ? AND timeline = ? ORDER BY tx DESC")?;
let mut rows = stmt.query_and_then(&[&tx_id, &::TIMELINE_MAIN], |row| -> Result<TermWithoutTempIds> { let mut rows = stmt.query_and_then(
let op = match row.get_checked(5)? { &[&tx_id, &::TIMELINE_MAIN],
true => OpType::Retract, |row| -> Result<TermWithoutTempIds> {
false => OpType::Add let op = match row.get(5)? {
}; true => OpType::Retract,
Ok(Term::AddOrRetract( false => OpType::Add,
op, };
KnownEntid(row.get_checked(0)?), Ok(Term::AddOrRetract(
row.get_checked(1)?, op,
TypedValue::from_sql_value_pair(row.get_checked(2)?, row.get_checked(3)?)?, KnownEntid(row.get(0)?),
)) row.get(1)?,
})?; TypedValue::from_sql_value_pair(row.get(2)?, row.get(3)?)?,
))
},
)?;
let mut terms = vec![]; let mut terms = vec![];
@ -136,11 +133,17 @@ fn reversed_terms_for(conn: &rusqlite::Connection, tx_id: Entid) -> Result<Vec<T
} }
/// Move specified transaction RangeFrom off of main timeline. /// Move specified transaction RangeFrom off of main timeline.
pub fn move_from_main_timeline(conn: &rusqlite::Connection, schema: &Schema, pub fn move_from_main_timeline(
partition_map: PartitionMap, txs_from: RangeFrom<Entid>, new_timeline: Entid) -> Result<(Option<Schema>, PartitionMap)> { conn: &rusqlite::Connection,
schema: &Schema,
partition_map: PartitionMap,
txs_from: RangeFrom<Entid>,
new_timeline: Entid,
) -> Result<(Option<Schema>, PartitionMap)> {
if new_timeline == ::TIMELINE_MAIN { if new_timeline == ::TIMELINE_MAIN {
bail!(DbErrorKind::NotYetImplemented(format!("Can't move transactions to main timeline"))); bail!(DbErrorKind::NotYetImplemented(format!(
"Can't move transactions to main timeline"
)));
} }
// We don't currently ensure that moving transactions onto a non-empty timeline // We don't currently ensure that moving transactions onto a non-empty timeline
@ -158,9 +161,14 @@ pub fn move_from_main_timeline(conn: &rusqlite::Connection, schema: &Schema,
// Rewind schema and datoms. // Rewind schema and datoms.
let (report, _, new_schema, _) = transact_terms_with_action( let (report, _, new_schema, _) = transact_terms_with_action(
conn, partition_map.clone(), schema, schema, NullWatcher(), conn,
partition_map.clone(),
schema,
schema,
NullWatcher(),
reversed_terms.into_iter().map(|t| t.rewrap()), reversed_terms.into_iter().map(|t| t.rewrap()),
InternSet::new(), TransactorAction::Materialize InternSet::new(),
TransactorAction::Materialize,
)?; )?;
// Rewind operation generated a 'tx' and a 'txInstant' assertion, which got // Rewind operation generated a 'tx' and a 'txInstant' assertion, which got
@ -188,13 +196,9 @@ mod tests {
use edn; use edn;
use std::borrow::{ use std::borrow::Borrow;
Borrow,
};
use debug::{ use debug::TestConn;
TestConn,
};
use bootstrap; use bootstrap;
@ -203,7 +207,7 @@ mod tests {
fn update_conn(conn: &mut TestConn, schema: &Option<Schema>, pmap: &PartitionMap) { fn update_conn(conn: &mut TestConn, schema: &Option<Schema>, pmap: &PartitionMap) {
match schema { match schema {
&Some(ref s) => conn.schema = s.clone(), &Some(ref s) => conn.schema = s.clone(),
&None => () &None => (),
}; };
conn.partition_map = pmap.clone(); conn.partition_map = pmap.clone();
} }
@ -223,9 +227,13 @@ mod tests {
let partition_map1 = conn.partition_map.clone(); let partition_map1 = conn.partition_map.clone();
let (new_schema, new_partition_map) = move_from_main_timeline( let (new_schema, new_partition_map) = move_from_main_timeline(
&conn.sqlite, &conn.schema, conn.partition_map.clone(), &conn.sqlite,
conn.last_tx_id().., 1 &conn.schema,
).expect("moved single tx"); conn.partition_map.clone(),
conn.last_tx_id()..,
1,
)
.expect("moved single tx");
update_conn(&mut conn, &new_schema, &new_partition_map); update_conn(&mut conn, &new_schema, &new_partition_map);
assert_matches!(conn.datoms(), "[]"); assert_matches!(conn.datoms(), "[]");
@ -238,20 +246,30 @@ mod tests {
// Ensure that we can't move transactions to a non-empty timeline: // Ensure that we can't move transactions to a non-empty timeline:
move_from_main_timeline( move_from_main_timeline(
&conn.sqlite, &conn.schema, conn.partition_map.clone(), &conn.sqlite,
conn.last_tx_id().., 1 &conn.schema,
).expect_err("Can't move transactions to a non-empty timeline"); conn.partition_map.clone(),
conn.last_tx_id()..,
1,
)
.expect_err("Can't move transactions to a non-empty timeline");
assert_eq!(report1.tx_id, report2.tx_id); assert_eq!(report1.tx_id, report2.tx_id);
assert_eq!(partition_map1, partition_map2); assert_eq!(partition_map1, partition_map2);
assert_matches!(conn.datoms(), r#" assert_matches!(
conn.datoms(),
r#"
[[37 :db/doc "test"]] [[37 :db/doc "test"]]
"#); "#
assert_matches!(conn.transactions(), r#" );
assert_matches!(
conn.transactions(),
r#"
[[[37 :db/doc "test" ?tx true] [[[37 :db/doc "test" ?tx true]
[?tx :db/txInstant ?ms ?tx true]]] [?tx :db/txInstant ?ms ?tx true]]]
"#); "#
);
} }
#[test] #[test]
@ -271,9 +289,13 @@ mod tests {
let schema1 = conn.schema.clone(); let schema1 = conn.schema.clone();
let (new_schema, new_partition_map) = move_from_main_timeline( let (new_schema, new_partition_map) = move_from_main_timeline(
&conn.sqlite, &conn.schema, conn.partition_map.clone(), &conn.sqlite,
conn.last_tx_id().., 1 &conn.schema,
).expect("moved single tx"); conn.partition_map.clone(),
conn.last_tx_id()..,
1,
)
.expect("moved single tx");
update_conn(&mut conn, &new_schema, &new_partition_map); update_conn(&mut conn, &new_schema, &new_partition_map);
assert_matches!(conn.datoms(), "[]"); assert_matches!(conn.datoms(), "[]");
@ -287,17 +309,23 @@ mod tests {
assert_eq!(conn.partition_map, partition_map1); assert_eq!(conn.partition_map, partition_map1);
assert_eq!(conn.schema, schema1); assert_eq!(conn.schema, schema1);
assert_matches!(conn.datoms(), r#" assert_matches!(
conn.datoms(),
r#"
[[?e :db/ident :test/entid] [[?e :db/ident :test/entid]
[?e :db/doc "test"] [?e :db/doc "test"]
[?e :db.schema/version 1]] [?e :db.schema/version 1]]
"#); "#
assert_matches!(conn.transactions(), r#" );
assert_matches!(
conn.transactions(),
r#"
[[[?e :db/ident :test/entid ?tx true] [[[?e :db/ident :test/entid ?tx true]
[?e :db/doc "test" ?tx true] [?e :db/doc "test" ?tx true]
[?e :db.schema/version 1 ?tx true] [?e :db.schema/version 1 ?tx true]
[?tx :db/txInstant ?ms ?tx true]]] [?tx :db/txInstant ?ms ?tx true]]]
"#); "#
);
} }
#[test] #[test]
@ -306,59 +334,83 @@ mod tests {
conn.sanitized_partition_map(); conn.sanitized_partition_map();
// Transact a basic schema. // Transact a basic schema.
assert_transact!(conn, r#" assert_transact!(
conn,
r#"
[{:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity :db/index true}] [{:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity :db/index true}]
"#); "#
);
// Make an assertion against our schema. // Make an assertion against our schema.
assert_transact!(conn, r#"[{:person/name "Vanya"}]"#); assert_transact!(conn, r#"[{:person/name "Vanya"}]"#);
// Move that assertion away from the main timeline. // Move that assertion away from the main timeline.
let (new_schema, new_partition_map) = move_from_main_timeline( let (new_schema, new_partition_map) = move_from_main_timeline(
&conn.sqlite, &conn.schema, conn.partition_map.clone(), &conn.sqlite,
conn.last_tx_id().., 1 &conn.schema,
).expect("moved single tx"); conn.partition_map.clone(),
conn.last_tx_id()..,
1,
)
.expect("moved single tx");
update_conn(&mut conn, &new_schema, &new_partition_map); update_conn(&mut conn, &new_schema, &new_partition_map);
// Assert that our datoms are now just the schema. // Assert that our datoms are now just the schema.
assert_matches!(conn.datoms(), " assert_matches!(
conn.datoms(),
"
[[?e :db/ident :person/name] [[?e :db/ident :person/name]
[?e :db/valueType :db.type/string] [?e :db/valueType :db.type/string]
[?e :db/cardinality :db.cardinality/one] [?e :db/cardinality :db.cardinality/one]
[?e :db/unique :db.unique/identity] [?e :db/unique :db.unique/identity]
[?e :db/index true]]"); [?e :db/index true]]"
);
// Same for transactions. // Same for transactions.
assert_matches!(conn.transactions(), " assert_matches!(
conn.transactions(),
"
[[[?e :db/ident :person/name ?tx true] [[[?e :db/ident :person/name ?tx true]
[?e :db/valueType :db.type/string ?tx true] [?e :db/valueType :db.type/string ?tx true]
[?e :db/cardinality :db.cardinality/one ?tx true] [?e :db/cardinality :db.cardinality/one ?tx true]
[?e :db/unique :db.unique/identity ?tx true] [?e :db/unique :db.unique/identity ?tx true]
[?e :db/index true ?tx true] [?e :db/index true ?tx true]
[?tx :db/txInstant ?ms ?tx true]]]"); [?tx :db/txInstant ?ms ?tx true]]]"
);
// Re-assert our initial fact against our schema. // Re-assert our initial fact against our schema.
assert_transact!(conn, r#" assert_transact!(
[[:db/add "tempid" :person/name "Vanya"]]"#); conn,
r#"
[[:db/add "tempid" :person/name "Vanya"]]"#
);
// Now, change that fact. This is the "clashing" transaction, if we're // Now, change that fact. This is the "clashing" transaction, if we're
// performing a timeline move using the transactor. // performing a timeline move using the transactor.
assert_transact!(conn, r#" assert_transact!(
[[:db/add (lookup-ref :person/name "Vanya") :person/name "Ivan"]]"#); conn,
r#"
[[:db/add (lookup-ref :person/name "Vanya") :person/name "Ivan"]]"#
);
// Assert that our datoms are now the schema and the final assertion. // Assert that our datoms are now the schema and the final assertion.
assert_matches!(conn.datoms(), r#" assert_matches!(
conn.datoms(),
r#"
[[?e1 :db/ident :person/name] [[?e1 :db/ident :person/name]
[?e1 :db/valueType :db.type/string] [?e1 :db/valueType :db.type/string]
[?e1 :db/cardinality :db.cardinality/one] [?e1 :db/cardinality :db.cardinality/one]
[?e1 :db/unique :db.unique/identity] [?e1 :db/unique :db.unique/identity]
[?e1 :db/index true] [?e1 :db/index true]
[?e2 :person/name "Ivan"]] [?e2 :person/name "Ivan"]]
"#); "#
);
// Assert that we have three correct looking transactions. // Assert that we have three correct looking transactions.
// This will fail if we're not cleaning up the 'datoms' table // This will fail if we're not cleaning up the 'datoms' table
// after the timeline move. // after the timeline move.
assert_matches!(conn.transactions(), r#" assert_matches!(
conn.transactions(),
r#"
[[ [[
[?e1 :db/ident :person/name ?tx1 true] [?e1 :db/ident :person/name ?tx1 true]
[?e1 :db/valueType :db.type/string ?tx1 true] [?e1 :db/valueType :db.type/string ?tx1 true]
@ -376,7 +428,8 @@ mod tests {
[?e2 :person/name "Vanya" ?tx3 false] [?e2 :person/name "Vanya" ?tx3 false]
[?tx3 :db/txInstant ?ms3 ?tx3 true] [?tx3 :db/txInstant ?ms3 ?tx3 true]
]] ]]
"#); "#
);
} }
#[test] #[test]
@ -397,8 +450,13 @@ mod tests {
let schema1 = conn.schema.clone(); let schema1 = conn.schema.clone();
let (new_schema, new_partition_map) = move_from_main_timeline( let (new_schema, new_partition_map) = move_from_main_timeline(
&conn.sqlite, &conn.schema, conn.partition_map.clone(), &conn.sqlite,
report1.tx_id.., 1).expect("moved single tx"); &conn.schema,
conn.partition_map.clone(),
report1.tx_id..,
1,
)
.expect("moved single tx");
update_conn(&mut conn, &new_schema, &new_partition_map); update_conn(&mut conn, &new_schema, &new_partition_map);
assert_matches!(conn.datoms(), "[]"); assert_matches!(conn.datoms(), "[]");
@ -414,15 +472,20 @@ mod tests {
assert_eq!(partition_map1, partition_map2); assert_eq!(partition_map1, partition_map2);
assert_eq!(schema1, schema2); assert_eq!(schema1, schema2);
assert_matches!(conn.datoms(), r#" assert_matches!(
conn.datoms(),
r#"
[[?e1 :db/ident :test/one] [[?e1 :db/ident :test/one]
[?e1 :db/valueType :db.type/long] [?e1 :db/valueType :db.type/long]
[?e1 :db/cardinality :db.cardinality/one] [?e1 :db/cardinality :db.cardinality/one]
[?e2 :db/ident :test/many] [?e2 :db/ident :test/many]
[?e2 :db/valueType :db.type/long] [?e2 :db/valueType :db.type/long]
[?e2 :db/cardinality :db.cardinality/many]] [?e2 :db/cardinality :db.cardinality/many]]
"#); "#
assert_matches!(conn.transactions(), r#" );
assert_matches!(
conn.transactions(),
r#"
[[[?e1 :db/ident :test/one ?tx1 true] [[[?e1 :db/ident :test/one ?tx1 true]
[?e1 :db/valueType :db.type/long ?tx1 true] [?e1 :db/valueType :db.type/long ?tx1 true]
[?e1 :db/cardinality :db.cardinality/one ?tx1 true] [?e1 :db/cardinality :db.cardinality/one ?tx1 true]
@ -430,7 +493,8 @@ mod tests {
[?e2 :db/valueType :db.type/long ?tx1 true] [?e2 :db/valueType :db.type/long ?tx1 true]
[?e2 :db/cardinality :db.cardinality/many ?tx1 true] [?e2 :db/cardinality :db.cardinality/many ?tx1 true]
[?tx1 :db/txInstant ?ms ?tx1 true]]] [?tx1 :db/txInstant ?ms ?tx1 true]]]
"#); "#
);
} }
#[test] #[test]
@ -458,8 +522,13 @@ mod tests {
let schema1 = conn.schema.clone(); let schema1 = conn.schema.clone();
let (new_schema, new_partition_map) = move_from_main_timeline( let (new_schema, new_partition_map) = move_from_main_timeline(
&conn.sqlite, &conn.schema, conn.partition_map.clone(), &conn.sqlite,
report1.tx_id.., 1).expect("moved single tx"); &conn.schema,
conn.partition_map.clone(),
report1.tx_id..,
1,
)
.expect("moved single tx");
update_conn(&mut conn, &new_schema, &new_partition_map); update_conn(&mut conn, &new_schema, &new_partition_map);
assert_matches!(conn.datoms(), "[]"); assert_matches!(conn.datoms(), "[]");
@ -475,15 +544,20 @@ mod tests {
assert_eq!(partition_map1, partition_map2); assert_eq!(partition_map1, partition_map2);
assert_eq!(schema1, schema2); assert_eq!(schema1, schema2);
assert_matches!(conn.datoms(), r#" assert_matches!(
conn.datoms(),
r#"
[[?e1 :db/ident :test/one] [[?e1 :db/ident :test/one]
[?e1 :db/valueType :db.type/string] [?e1 :db/valueType :db.type/string]
[?e1 :db/cardinality :db.cardinality/one] [?e1 :db/cardinality :db.cardinality/one]
[?e1 :db/unique :db.unique/value] [?e1 :db/unique :db.unique/value]
[?e1 :db/index true] [?e1 :db/index true]
[?e1 :db/fulltext true]] [?e1 :db/fulltext true]]
"#); "#
assert_matches!(conn.transactions(), r#" );
assert_matches!(
conn.transactions(),
r#"
[[[?e1 :db/ident :test/one ?tx1 true] [[[?e1 :db/ident :test/one ?tx1 true]
[?e1 :db/valueType :db.type/string ?tx1 true] [?e1 :db/valueType :db.type/string ?tx1 true]
[?e1 :db/cardinality :db.cardinality/one ?tx1 true] [?e1 :db/cardinality :db.cardinality/one ?tx1 true]
@ -491,10 +565,11 @@ mod tests {
[?e1 :db/index true ?tx1 true] [?e1 :db/index true ?tx1 true]
[?e1 :db/fulltext true ?tx1 true] [?e1 :db/fulltext true ?tx1 true]
[?tx1 :db/txInstant ?ms ?tx1 true]]] [?tx1 :db/txInstant ?ms ?tx1 true]]]
"#); "#
);
} }
#[test] #[test]
fn test_pop_schema_all_attributes_component() { fn test_pop_schema_all_attributes_component() {
let mut conn = TestConn::default(); let mut conn = TestConn::default();
conn.sanitized_partition_map(); conn.sanitized_partition_map();
@ -519,8 +594,13 @@ mod tests {
let schema1 = conn.schema.clone(); let schema1 = conn.schema.clone();
let (new_schema, new_partition_map) = move_from_main_timeline( let (new_schema, new_partition_map) = move_from_main_timeline(
&conn.sqlite, &conn.schema, conn.partition_map.clone(), &conn.sqlite,
report1.tx_id.., 1).expect("moved single tx"); &conn.schema,
conn.partition_map.clone(),
report1.tx_id..,
1,
)
.expect("moved single tx");
update_conn(&mut conn, &new_schema, &new_partition_map); update_conn(&mut conn, &new_schema, &new_partition_map);
assert_matches!(conn.datoms(), "[]"); assert_matches!(conn.datoms(), "[]");
@ -531,7 +611,10 @@ mod tests {
assert_eq!(conn.schema.entid_map, schema0.entid_map); assert_eq!(conn.schema.entid_map, schema0.entid_map);
assert_eq!(conn.schema.ident_map, schema0.ident_map); assert_eq!(conn.schema.ident_map, schema0.ident_map);
assert_eq!(conn.schema.attribute_map, schema0.attribute_map); assert_eq!(conn.schema.attribute_map, schema0.attribute_map);
assert_eq!(conn.schema.component_attributes, schema0.component_attributes); assert_eq!(
conn.schema.component_attributes,
schema0.component_attributes
);
// Assert the whole schema, just in case we missed something: // Assert the whole schema, just in case we missed something:
assert_eq!(conn.schema, schema0); assert_eq!(conn.schema, schema0);
@ -543,15 +626,20 @@ mod tests {
assert_eq!(partition_map1, partition_map2); assert_eq!(partition_map1, partition_map2);
assert_eq!(schema1, schema2); assert_eq!(schema1, schema2);
assert_matches!(conn.datoms(), r#" assert_matches!(
conn.datoms(),
r#"
[[?e1 :db/ident :test/one] [[?e1 :db/ident :test/one]
[?e1 :db/valueType :db.type/ref] [?e1 :db/valueType :db.type/ref]
[?e1 :db/cardinality :db.cardinality/one] [?e1 :db/cardinality :db.cardinality/one]
[?e1 :db/unique :db.unique/value] [?e1 :db/unique :db.unique/value]
[?e1 :db/isComponent true] [?e1 :db/isComponent true]
[?e1 :db/index true]] [?e1 :db/index true]]
"#); "#
assert_matches!(conn.transactions(), r#" );
assert_matches!(
conn.transactions(),
r#"
[[[?e1 :db/ident :test/one ?tx1 true] [[[?e1 :db/ident :test/one ?tx1 true]
[?e1 :db/valueType :db.type/ref ?tx1 true] [?e1 :db/valueType :db.type/ref ?tx1 true]
[?e1 :db/cardinality :db.cardinality/one ?tx1 true] [?e1 :db/cardinality :db.cardinality/one ?tx1 true]
@ -559,7 +647,8 @@ mod tests {
[?e1 :db/isComponent true ?tx1 true] [?e1 :db/isComponent true ?tx1 true]
[?e1 :db/index true ?tx1 true] [?e1 :db/index true ?tx1 true]
[?tx1 :db/txInstant ?ms ?tx1 true]]] [?tx1 :db/txInstant ?ms ?tx1 true]]]
"#); "#
);
} }
#[test] #[test]
@ -569,12 +658,17 @@ mod tests {
let partition_map_after_bootstrap = conn.partition_map.clone(); let partition_map_after_bootstrap = conn.partition_map.clone();
assert_eq!((65536..65538), assert_eq!(
conn.partition_map.allocate_entids(":db.part/user", 2)); (65536..65538),
let tx_report0 = assert_transact!(conn, r#"[ conn.partition_map.allocate_entids(":db.part/user", 2)
);
let tx_report0 = assert_transact!(
conn,
r#"[
{:db/id 65536 :db/ident :test/one :db/valueType :db.type/long :db/cardinality :db.cardinality/one :db/unique :db.unique/identity :db/index true} {:db/id 65536 :db/ident :test/one :db/valueType :db.type/long :db/cardinality :db.cardinality/one :db/unique :db.unique/identity :db/index true}
{:db/id 65537 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many} {:db/id 65537 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many}
]"#); ]"#
);
let first = "[ let first = "[
[65536 :db/ident :test/one] [65536 :db/ident :test/one]
@ -590,21 +684,28 @@ mod tests {
let partition_map0 = conn.partition_map.clone(); let partition_map0 = conn.partition_map.clone();
assert_eq!((65538..65539), assert_eq!(
conn.partition_map.allocate_entids(":db.part/user", 1)); (65538..65539),
let tx_report1 = assert_transact!(conn, r#"[ conn.partition_map.allocate_entids(":db.part/user", 1)
);
let tx_report1 = assert_transact!(
conn,
r#"[
[:db/add 65538 :test/one 1] [:db/add 65538 :test/one 1]
[:db/add 65538 :test/many 2] [:db/add 65538 :test/many 2]
[:db/add 65538 :test/many 3] [:db/add 65538 :test/many 3]
]"#); ]"#
);
let schema1 = conn.schema.clone(); let schema1 = conn.schema.clone();
let partition_map1 = conn.partition_map.clone(); let partition_map1 = conn.partition_map.clone();
assert_matches!(conn.last_transaction(), assert_matches!(
"[[65538 :test/one 1 ?tx true] conn.last_transaction(),
"[[65538 :test/one 1 ?tx true]
[65538 :test/many 2 ?tx true] [65538 :test/many 2 ?tx true]
[65538 :test/many 3 ?tx true] [65538 :test/many 3 ?tx true]
[?tx :db/txInstant ?ms ?tx true]]"); [?tx :db/txInstant ?ms ?tx true]]"
);
let second = "[ let second = "[
[65536 :db/ident :test/one] [65536 :db/ident :test/one]
@ -621,20 +722,25 @@ mod tests {
]"; ]";
assert_matches!(conn.datoms(), second); assert_matches!(conn.datoms(), second);
let tx_report2 = assert_transact!(conn, r#"[ let tx_report2 = assert_transact!(
conn,
r#"[
[:db/add 65538 :test/one 2] [:db/add 65538 :test/one 2]
[:db/add 65538 :test/many 2] [:db/add 65538 :test/many 2]
[:db/retract 65538 :test/many 3] [:db/retract 65538 :test/many 3]
[:db/add 65538 :test/many 4] [:db/add 65538 :test/many 4]
]"#); ]"#
);
let schema2 = conn.schema.clone(); let schema2 = conn.schema.clone();
assert_matches!(conn.last_transaction(), assert_matches!(
"[[65538 :test/one 1 ?tx false] conn.last_transaction(),
"[[65538 :test/one 1 ?tx false]
[65538 :test/one 2 ?tx true] [65538 :test/one 2 ?tx true]
[65538 :test/many 3 ?tx false] [65538 :test/many 3 ?tx false]
[65538 :test/many 4 ?tx true] [65538 :test/many 4 ?tx true]
[?tx :db/txInstant ?ms ?tx true]]"); [?tx :db/txInstant ?ms ?tx true]]"
);
let third = "[ let third = "[
[65536 :db/ident :test/one] [65536 :db/ident :test/one]
@ -652,8 +758,13 @@ mod tests {
assert_matches!(conn.datoms(), third); assert_matches!(conn.datoms(), third);
let (new_schema, new_partition_map) = move_from_main_timeline( let (new_schema, new_partition_map) = move_from_main_timeline(
&conn.sqlite, &conn.schema, conn.partition_map.clone(), &conn.sqlite,
tx_report2.tx_id.., 1).expect("moved timeline"); &conn.schema,
conn.partition_map.clone(),
tx_report2.tx_id..,
1,
)
.expect("moved timeline");
update_conn(&mut conn, &new_schema, &new_partition_map); update_conn(&mut conn, &new_schema, &new_partition_map);
assert_matches!(conn.datoms(), second); assert_matches!(conn.datoms(), second);
@ -664,8 +775,13 @@ mod tests {
assert_eq!(conn.partition_map, partition_map1); assert_eq!(conn.partition_map, partition_map1);
let (new_schema, new_partition_map) = move_from_main_timeline( let (new_schema, new_partition_map) = move_from_main_timeline(
&conn.sqlite, &conn.schema, conn.partition_map.clone(), &conn.sqlite,
tx_report1.tx_id.., 2).expect("moved timeline"); &conn.schema,
conn.partition_map.clone(),
tx_report1.tx_id..,
2,
)
.expect("moved timeline");
update_conn(&mut conn, &new_schema, &new_partition_map); update_conn(&mut conn, &new_schema, &new_partition_map);
assert_matches!(conn.datoms(), first); assert_matches!(conn.datoms(), first);
assert_eq!(None, new_schema); assert_eq!(None, new_schema);
@ -673,8 +789,13 @@ mod tests {
assert_eq!(conn.partition_map, partition_map0); assert_eq!(conn.partition_map, partition_map0);
let (new_schema, new_partition_map) = move_from_main_timeline( let (new_schema, new_partition_map) = move_from_main_timeline(
&conn.sqlite, &conn.schema, conn.partition_map.clone(), &conn.sqlite,
tx_report0.tx_id.., 3).expect("moved timeline"); &conn.schema,
conn.partition_map.clone(),
tx_report0.tx_id..,
3,
)
.expect("moved timeline");
update_conn(&mut conn, &new_schema, &new_partition_map); update_conn(&mut conn, &new_schema, &new_partition_map);
assert_eq!(true, new_schema.is_some()); assert_eq!(true, new_schema.is_some());
assert_eq!(bootstrap::bootstrap_schema(), conn.schema); assert_eq!(bootstrap::bootstrap_schema(), conn.schema);
@ -690,31 +811,47 @@ mod tests {
let partition_map_after_bootstrap = conn.partition_map.clone(); let partition_map_after_bootstrap = conn.partition_map.clone();
assert_eq!((65536..65539), assert_eq!(
conn.partition_map.allocate_entids(":db.part/user", 3)); (65536..65539),
let tx_report0 = assert_transact!(conn, r#"[ conn.partition_map.allocate_entids(":db.part/user", 3)
);
let tx_report0 = assert_transact!(
conn,
r#"[
{:db/id 65536 :db/ident :test/one :db/valueType :db.type/long :db/cardinality :db.cardinality/one} {:db/id 65536 :db/ident :test/one :db/valueType :db.type/long :db/cardinality :db.cardinality/one}
{:db/id 65537 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many} {:db/id 65537 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many}
]"#); ]"#
);
assert_transact!(conn, r#"[ assert_transact!(
conn,
r#"[
[:db/add 65538 :test/one 1] [:db/add 65538 :test/one 1]
[:db/add 65538 :test/many 2] [:db/add 65538 :test/many 2]
[:db/add 65538 :test/many 3] [:db/add 65538 :test/many 3]
]"#); ]"#
);
assert_transact!(conn, r#"[ assert_transact!(
conn,
r#"[
[:db/add 65538 :test/one 2] [:db/add 65538 :test/one 2]
[:db/add 65538 :test/many 2] [:db/add 65538 :test/many 2]
[:db/retract 65538 :test/many 3] [:db/retract 65538 :test/many 3]
[:db/add 65538 :test/many 4] [:db/add 65538 :test/many 4]
]"#); ]"#
);
// Remove all of these transactions from the main timeline, // Remove all of these transactions from the main timeline,
// ensure we get back to a "just bootstrapped" state. // ensure we get back to a "just bootstrapped" state.
let (new_schema, new_partition_map) = move_from_main_timeline( let (new_schema, new_partition_map) = move_from_main_timeline(
&conn.sqlite, &conn.schema, conn.partition_map.clone(), &conn.sqlite,
tx_report0.tx_id.., 1).expect("moved timeline"); &conn.schema,
conn.partition_map.clone(),
tx_report0.tx_id..,
1,
)
.expect("moved timeline");
update_conn(&mut conn, &new_schema, &new_partition_map); update_conn(&mut conn, &new_schema, &new_partition_map);
update_conn(&mut conn, &new_schema, &new_partition_map); update_conn(&mut conn, &new_schema, &new_partition_map);

File diff suppressed because it is too large Load diff

View file

@ -8,24 +8,13 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use std::collections::{ use std::collections::{BTreeMap, BTreeSet};
BTreeSet,
BTreeMap,
};
use core_traits::{ use core_traits::{Entid, TypedValue, ValueType};
Entid,
TypedValue,
ValueType,
};
use db_traits::errors::{ use db_traits::errors::CardinalityConflict;
CardinalityConflict,
};
use internal_types::{ use internal_types::AEVTrie;
AEVTrie,
};
/// Map from found [e a v] to expected type. /// Map from found [e a v] to expected type.
pub(crate) type TypeDisagreements = BTreeMap<(Entid, Entid, TypedValue), ValueType>; pub(crate) type TypeDisagreements = BTreeMap<(Entid, Entid, TypedValue), ValueType>;
@ -63,7 +52,9 @@ pub(crate) fn type_disagreements<'schema>(aev_trie: &AEVTrie<'schema>) -> TypeDi
/// We try to be maximally helpful by yielding every malformed set of datoms, rather than just the /// We try to be maximally helpful by yielding every malformed set of datoms, rather than just the
/// first set, or even the first conflict. In the future, we might change this choice, or allow the /// first set, or even the first conflict. In the future, we might change this choice, or allow the
/// consumer to specify the robustness of the cardinality checking desired. /// consumer to specify the robustness of the cardinality checking desired.
pub(crate) fn cardinality_conflicts<'schema>(aev_trie: &AEVTrie<'schema>) -> Vec<CardinalityConflict> { pub(crate) fn cardinality_conflicts<'schema>(
aev_trie: &AEVTrie<'schema>,
) -> Vec<CardinalityConflict> {
let mut errors = vec![]; let mut errors = vec![];
for (&(a, attribute), evs) in aev_trie { for (&(a, attribute), evs) in aev_trie {

View file

@ -8,64 +8,50 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use std::sync::{ use std::sync::{Arc, Weak};
Arc,
Weak,
};
use std::sync::mpsc::{ use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
channel,
Receiver,
RecvError,
Sender,
};
use std::thread; use std::thread;
use indexmap::{ use indexmap::IndexMap;
IndexMap,
};
use core_traits::{ use core_traits::{Entid, TypedValue};
Entid,
TypedValue,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::entities::{ use edn::entities::OpType;
OpType,
};
use db_traits::errors::{ use db_traits::errors::Result;
Result,
};
use types::{ use types::AttributeSet;
AttributeSet,
};
use watcher::TransactWatcher; use watcher::TransactWatcher;
pub struct TxObserver { pub struct TxObserver {
notify_fn: Arc<Box<Fn(&str, IndexMap<&Entid, &AttributeSet>) + Send + Sync>>, notify_fn: Arc<Box<dyn Fn(&str, IndexMap<&Entid, &AttributeSet>) + Send + Sync>>,
attributes: AttributeSet, attributes: AttributeSet,
} }
impl TxObserver { impl TxObserver {
pub fn new<F>(attributes: AttributeSet, notify_fn: F) -> TxObserver where F: Fn(&str, IndexMap<&Entid, &AttributeSet>) + 'static + Send + Sync { pub fn new<F>(attributes: AttributeSet, notify_fn: F) -> TxObserver
where
F: Fn(&str, IndexMap<&Entid, &AttributeSet>) + 'static + Send + Sync,
{
TxObserver { TxObserver {
notify_fn: Arc::new(Box::new(notify_fn)), notify_fn: Arc::new(Box::new(notify_fn)),
attributes, attributes,
} }
} }
pub fn applicable_reports<'r>(&self, reports: &'r IndexMap<Entid, AttributeSet>) -> IndexMap<&'r Entid, &'r AttributeSet> { pub fn applicable_reports<'r>(
reports.into_iter() &self,
.filter(|&(_txid, attrs)| !self.attributes.is_disjoint(attrs)) reports: &'r IndexMap<Entid, AttributeSet>,
.collect() ) -> IndexMap<&'r Entid, &'r AttributeSet> {
reports
.into_iter()
.filter(|&(_txid, attrs)| !self.attributes.is_disjoint(attrs))
.collect()
} }
fn notify(&self, key: &str, reports: IndexMap<&Entid, &AttributeSet>) { fn notify(&self, key: &str, reports: IndexMap<&Entid, &AttributeSet>) {
@ -83,7 +69,10 @@ pub struct TxCommand {
} }
impl TxCommand { impl TxCommand {
fn new(observers: &Arc<IndexMap<String, Arc<TxObserver>>>, reports: IndexMap<Entid, AttributeSet>) -> Self { fn new(
observers: &Arc<IndexMap<String, Arc<TxObserver>>>,
reports: IndexMap<Entid, AttributeSet>,
) -> Self {
TxCommand { TxCommand {
reports, reports,
observers: Arc::downgrade(observers), observers: Arc::downgrade(observers),
@ -106,7 +95,7 @@ impl Command for TxCommand {
pub struct TxObservationService { pub struct TxObservationService {
observers: Arc<IndexMap<String, Arc<TxObserver>>>, observers: Arc<IndexMap<String, Arc<TxObserver>>>,
executor: Option<Sender<Box<Command + Send>>>, executor: Option<Sender<Box<dyn Command + Send>>>,
} }
impl TxObservationService { impl TxObservationService {
@ -141,7 +130,10 @@ impl TxObservationService {
} }
let executor = self.executor.get_or_insert_with(|| { let executor = self.executor.get_or_insert_with(|| {
let (tx, rx): (Sender<Box<Command + Send>>, Receiver<Box<Command + Send>>) = channel(); let (tx, rx): (
Sender<Box<dyn Command + Send>>,
Receiver<Box<dyn Command + Send>>,
) = channel();
let mut worker = CommandExecutor::new(rx); let mut worker = CommandExecutor::new(rx);
thread::spawn(move || { thread::spawn(move || {
@ -182,21 +174,20 @@ impl TransactWatcher for InProgressObserverTransactWatcher {
} }
fn done(&mut self, t: &Entid, _schema: &Schema) -> Result<()> { fn done(&mut self, t: &Entid, _schema: &Schema) -> Result<()> {
let collected_attributes = ::std::mem::replace(&mut self.collected_attributes, Default::default()); let collected_attributes =
::std::mem::replace(&mut self.collected_attributes, Default::default());
self.txes.insert(*t, collected_attributes); self.txes.insert(*t, collected_attributes);
Ok(()) Ok(())
} }
} }
struct CommandExecutor { struct CommandExecutor {
receiver: Receiver<Box<Command + Send>>, receiver: Receiver<Box<dyn Command + Send>>,
} }
impl CommandExecutor { impl CommandExecutor {
fn new(rx: Receiver<Box<Command + Send>>) -> Self { fn new(rx: Receiver<Box<dyn Command + Send>>) -> Self {
CommandExecutor { CommandExecutor { receiver: rx }
receiver: rx,
}
} }
fn main(&mut self) { fn main(&mut self) {
@ -207,12 +198,10 @@ impl CommandExecutor {
// sync_channel) is disconnected, implying that no further messages will ever be // sync_channel) is disconnected, implying that no further messages will ever be
// received." // received."
// No need to log here. // No need to log here.
return return;
}, }
Ok(mut cmd) => { Ok(mut cmd) => cmd.execute(),
cmd.execute()
},
} }
} }
} }

View file

@ -10,44 +10,23 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::collections::{ use std::collections::{BTreeMap, BTreeSet, HashMap};
BTreeMap, use std::iter::FromIterator;
BTreeSet, use std::ops::{Deref, DerefMut, Range};
HashMap,
};
use std::iter::{
FromIterator,
};
use std::ops::{
Deref,
DerefMut,
Range,
};
extern crate mentat_core; extern crate mentat_core;
use core_traits::{ use core_traits::{Entid, TypedValue, ValueType};
Entid,
TypedValue,
ValueType,
};
pub use self::mentat_core::{ pub use self::mentat_core::{DateTime, Schema, Utc};
DateTime,
Schema,
Utc,
};
use edn::entities::{ use edn::entities::{EntityPlace, TempId};
EntityPlace,
TempId,
};
use db_traits::errors as errors; use db_traits::errors;
/// Represents one partition of the entid space. /// Represents one partition of the entid space.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
#[cfg_attr(feature = "syncable", derive(Serialize,Deserialize))] #[cfg_attr(feature = "syncable", derive(Serialize, Deserialize))]
pub struct Partition { pub struct Partition {
/// The first entid in the partition. /// The first entid in the partition.
pub start: Entid, pub start: Entid,
@ -61,12 +40,22 @@ pub struct Partition {
} }
impl Partition { impl Partition {
pub fn new(start: Entid, end: Entid, next_entid_to_allocate: Entid, allow_excision: bool) -> Partition { pub fn new(
start: Entid,
end: Entid,
next_entid_to_allocate: Entid,
allow_excision: bool,
) -> Partition {
assert!( assert!(
start <= next_entid_to_allocate && next_entid_to_allocate <= end, start <= next_entid_to_allocate && next_entid_to_allocate <= end,
"A partition represents a monotonic increasing sequence of entids." "A partition represents a monotonic increasing sequence of entids."
); );
Partition { start, end, next_entid_to_allocate, allow_excision } Partition {
start,
end,
next_entid_to_allocate,
allow_excision,
}
} }
pub fn contains_entid(&self, e: Entid) -> bool { pub fn contains_entid(&self, e: Entid) -> bool {
@ -82,7 +71,10 @@ impl Partition {
} }
pub fn set_next_entid(&mut self, e: Entid) { pub fn set_next_entid(&mut self, e: Entid) {
assert!(self.allows_entid(e), "Partition index must be within its allocated space."); assert!(
self.allows_entid(e),
"Partition index must be within its allocated space."
);
self.next_entid_to_allocate = e; self.next_entid_to_allocate = e;
} }
@ -95,7 +87,7 @@ impl Partition {
/// Map partition names to `Partition` instances. /// Map partition names to `Partition` instances.
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
#[cfg_attr(feature = "syncable", derive(Serialize,Deserialize))] #[cfg_attr(feature = "syncable", derive(Serialize, Deserialize))]
pub struct PartitionMap(BTreeMap<String, Partition>); pub struct PartitionMap(BTreeMap<String, Partition>);
impl Deref for PartitionMap { impl Deref for PartitionMap {
@ -113,7 +105,7 @@ impl DerefMut for PartitionMap {
} }
impl FromIterator<(String, Partition)> for PartitionMap { impl FromIterator<(String, Partition)> for PartitionMap {
fn from_iter<T: IntoIterator<Item=(String, Partition)>>(iter: T) -> Self { fn from_iter<T: IntoIterator<Item = (String, Partition)>>(iter: T) -> Self {
PartitionMap(iter.into_iter().collect()) PartitionMap(iter.into_iter().collect())
} }
} }
@ -121,7 +113,7 @@ impl FromIterator<(String, Partition)> for PartitionMap {
/// Represents the metadata required to query from, or apply transactions to, a Mentat store. /// Represents the metadata required to query from, or apply transactions to, a Mentat store.
/// ///
/// See https://github.com/mozilla/mentat/wiki/Thoughts:-modeling-db-conn-in-Rust. /// See https://github.com/mozilla/mentat/wiki/Thoughts:-modeling-db-conn-in-Rust.
#[derive(Clone,Debug,Default,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct DB { pub struct DB {
/// Map partition name->`Partition`. /// Map partition name->`Partition`.
/// ///
@ -136,7 +128,7 @@ impl DB {
pub fn new(partition_map: PartitionMap, schema: Schema) -> DB { pub fn new(partition_map: PartitionMap, schema: Schema) -> DB {
DB { DB {
partition_map: partition_map, partition_map: partition_map,
schema: schema schema: schema,
} }
} }
} }
@ -163,7 +155,8 @@ pub type AttributeSet = BTreeSet<Entid>;
pub trait TransactableValue: Clone { pub trait TransactableValue: Clone {
/// Coerce this value place into the given type. This is where we perform schema-aware /// Coerce this value place into the given type. This is where we perform schema-aware
/// coercion, for example coercing an integral value into a ref where appropriate. /// coercion, for example coercing an integral value into a ref where appropriate.
fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> errors::Result<TypedValue>; fn into_typed_value(self, schema: &Schema, value_type: ValueType)
-> errors::Result<TypedValue>;
/// Make an entity place out of this value place. This is where we limit values in nested maps /// Make an entity place out of this value place. This is where we limit values in nested maps
/// to valid entity places. /// to valid entity places.

View file

@ -13,52 +13,31 @@
//! This module implements the upsert resolution algorithm described at //! This module implements the upsert resolution algorithm described at
//! https://github.com/mozilla/mentat/wiki/Transacting:-upsert-resolution-algorithm. //! https://github.com/mozilla/mentat/wiki/Transacting:-upsert-resolution-algorithm.
use std::collections::{ use std::collections::{BTreeMap, BTreeSet};
BTreeMap,
BTreeSet,
};
use indexmap; use indexmap;
use petgraph::unionfind; use petgraph::unionfind;
use db_traits::errors::{ use db_traits::errors::{DbErrorKind, Result};
DbErrorKind,
Result,
};
use types::{
AVPair,
};
use internal_types::{ use internal_types::{
Population, Population, TempIdHandle, TempIdMap, Term, TermWithTempIds, TermWithoutTempIds, TypedValueOr,
TempIdHandle,
TempIdMap,
Term,
TermWithoutTempIds,
TermWithTempIds,
TypedValueOr,
}; };
use types::AVPair;
use mentat_core::util::Either::*; use mentat_core::util::Either::*;
use core_traits::{ use core_traits::{attribute, Attribute, Entid, TypedValue};
attribute,
Attribute,
Entid,
TypedValue,
};
use mentat_core::{
Schema,
};
use edn::entities::OpType; use edn::entities::OpType;
use mentat_core::Schema;
use schema::SchemaBuilding; use schema::SchemaBuilding;
/// A "Simple upsert" that looks like [:db/add TEMPID a v], where a is :db.unique/identity. /// A "Simple upsert" that looks like [:db/add TEMPID a v], where a is :db.unique/identity.
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
struct UpsertE(TempIdHandle, Entid, TypedValue); struct UpsertE(TempIdHandle, Entid, TypedValue);
/// A "Complex upsert" that looks like [:db/add TEMPID a OTHERID], where a is :db.unique/identity /// A "Complex upsert" that looks like [:db/add TEMPID a OTHERID], where a is :db.unique/identity
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
struct UpsertEV(TempIdHandle, Entid, TempIdHandle); struct UpsertEV(TempIdHandle, Entid, TempIdHandle);
/// A generation collects entities into populations at a single evolutionary step in the upsert /// A generation collects entities into populations at a single evolutionary step in the upsert
@ -67,7 +46,7 @@ struct UpsertEV(TempIdHandle, Entid, TempIdHandle);
/// The upsert resolution process is only concerned with [:db/add ...] entities until the final /// The upsert resolution process is only concerned with [:db/add ...] entities until the final
/// entid allocations. That's why we separate into special simple and complex upsert types /// entid allocations. That's why we separate into special simple and complex upsert types
/// immediately, and then collect the more general term types for final resolution. /// immediately, and then collect the more general term types for final resolution.
#[derive(Clone,Debug,Default,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub(crate) struct Generation { pub(crate) struct Generation {
/// "Simple upserts" that look like [:db/add TEMPID a v], where a is :db.unique/identity. /// "Simple upserts" that look like [:db/add TEMPID a v], where a is :db.unique/identity.
upserts_e: Vec<UpsertE>, upserts_e: Vec<UpsertE>,
@ -90,7 +69,7 @@ pub(crate) struct Generation {
resolved: Vec<TermWithoutTempIds>, resolved: Vec<TermWithoutTempIds>,
} }
#[derive(Clone,Debug,Default,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub(crate) struct FinalPopulations { pub(crate) struct FinalPopulations {
/// Upserts that upserted. /// Upserts that upserted.
pub upserted: Vec<TermWithoutTempIds>, pub upserted: Vec<TermWithoutTempIds>,
@ -105,7 +84,10 @@ pub(crate) struct FinalPopulations {
impl Generation { impl Generation {
/// Split entities into a generation of populations that need to evolve to have their tempids /// Split entities into a generation of populations that need to evolve to have their tempids
/// resolved or allocated, and a population of inert entities that do not reference tempids. /// resolved or allocated, and a population of inert entities that do not reference tempids.
pub(crate) fn from<I>(terms: I, schema: &Schema) -> Result<(Generation, Population)> where I: IntoIterator<Item=TermWithTempIds> { pub(crate) fn from<I>(terms: I, schema: &Schema) -> Result<(Generation, Population)>
where
I: IntoIterator<Item = TermWithTempIds>,
{
let mut generation = Generation::default(); let mut generation = Generation::default();
let mut inert = vec![]; let mut inert = vec![];
@ -120,22 +102,28 @@ impl Generation {
if op == OpType::Add && is_unique(a)? { if op == OpType::Add && is_unique(a)? {
generation.upserts_ev.push(UpsertEV(e, a, v)); generation.upserts_ev.push(UpsertEV(e, a, v));
} else { } else {
generation.allocations.push(Term::AddOrRetract(op, Right(e), a, Right(v))); generation
.allocations
.push(Term::AddOrRetract(op, Right(e), a, Right(v)));
} }
}, }
Term::AddOrRetract(op, Right(e), a, Left(v)) => { Term::AddOrRetract(op, Right(e), a, Left(v)) => {
if op == OpType::Add && is_unique(a)? { if op == OpType::Add && is_unique(a)? {
generation.upserts_e.push(UpsertE(e, a, v)); generation.upserts_e.push(UpsertE(e, a, v));
} else { } else {
generation.allocations.push(Term::AddOrRetract(op, Right(e), a, Left(v))); generation
.allocations
.push(Term::AddOrRetract(op, Right(e), a, Left(v)));
} }
}, }
Term::AddOrRetract(op, Left(e), a, Right(v)) => { Term::AddOrRetract(op, Left(e), a, Right(v)) => {
generation.allocations.push(Term::AddOrRetract(op, Left(e), a, Right(v))); generation
}, .allocations
.push(Term::AddOrRetract(op, Left(e), a, Right(v)));
}
Term::AddOrRetract(op, Left(e), a, Left(v)) => { Term::AddOrRetract(op, Left(e), a, Left(v)) => {
inert.push(Term::AddOrRetract(op, Left(e), a, Left(v))); inert.push(Term::AddOrRetract(op, Left(e), a, Left(v)));
}, }
} }
} }
@ -164,7 +152,10 @@ impl Generation {
for UpsertE(t, a, v) in self.upserts_e { for UpsertE(t, a, v) in self.upserts_e {
match temp_id_map.get(&*t) { match temp_id_map.get(&*t) {
Some(&n) => next.upserted.push(Term::AddOrRetract(OpType::Add, n, a, v)), Some(&n) => next.upserted.push(Term::AddOrRetract(OpType::Add, n, a, v)),
None => next.allocations.push(Term::AddOrRetract(OpType::Add, Right(t), a, Left(v))), None => {
next.allocations
.push(Term::AddOrRetract(OpType::Add, Right(t), a, Left(v)))
}
} }
} }
@ -175,10 +166,13 @@ impl Generation {
// could conflict. Moving straight to resolved doesn't give us a chance to // could conflict. Moving straight to resolved doesn't give us a chance to
// search the store for the conflict. // search the store for the conflict.
next.upserts_e.push(UpsertE(t1, a, TypedValue::Ref(n2.0))) next.upserts_e.push(UpsertE(t1, a, TypedValue::Ref(n2.0)))
}, }
(None, Some(&n2)) => next.upserts_e.push(UpsertE(t1, a, TypedValue::Ref(n2.0))), (None, Some(&n2)) => next.upserts_e.push(UpsertE(t1, a, TypedValue::Ref(n2.0))),
(Some(&n1), None) => next.allocations.push(Term::AddOrRetract(OpType::Add, Left(n1), a, Right(t2))), (Some(&n1), None) => {
(None, None) => next.upserts_ev.push(UpsertEV(t1, a, t2)) next.allocations
.push(Term::AddOrRetract(OpType::Add, Left(n1), a, Right(t2)))
}
(None, None) => next.upserts_ev.push(UpsertEV(t1, a, t2)),
} }
} }
@ -190,23 +184,40 @@ impl Generation {
match term { match term {
Term::AddOrRetract(op, Right(t1), a, Right(t2)) => { Term::AddOrRetract(op, Right(t1), a, Right(t2)) => {
match (temp_id_map.get(&*t1), temp_id_map.get(&*t2)) { match (temp_id_map.get(&*t1), temp_id_map.get(&*t2)) {
(Some(&n1), Some(&n2)) => next.resolved.push(Term::AddOrRetract(op, n1, a, TypedValue::Ref(n2.0))), (Some(&n1), Some(&n2)) => {
(None, Some(&n2)) => next.allocations.push(Term::AddOrRetract(op, Right(t1), a, Left(TypedValue::Ref(n2.0)))), next.resolved
(Some(&n1), None) => next.allocations.push(Term::AddOrRetract(op, Left(n1), a, Right(t2))), .push(Term::AddOrRetract(op, n1, a, TypedValue::Ref(n2.0)))
(None, None) => next.allocations.push(Term::AddOrRetract(op, Right(t1), a, Right(t2))), }
(None, Some(&n2)) => next.allocations.push(Term::AddOrRetract(
op,
Right(t1),
a,
Left(TypedValue::Ref(n2.0)),
)),
(Some(&n1), None) => {
next.allocations
.push(Term::AddOrRetract(op, Left(n1), a, Right(t2)))
}
(None, None) => {
next.allocations
.push(Term::AddOrRetract(op, Right(t1), a, Right(t2)))
}
} }
}
Term::AddOrRetract(op, Right(t), a, Left(v)) => match temp_id_map.get(&*t) {
Some(&n) => next.resolved.push(Term::AddOrRetract(op, n, a, v)),
None => next
.allocations
.push(Term::AddOrRetract(op, Right(t), a, Left(v))),
}, },
Term::AddOrRetract(op, Right(t), a, Left(v)) => { Term::AddOrRetract(op, Left(e), a, Right(t)) => match temp_id_map.get(&*t) {
match temp_id_map.get(&*t) { Some(&n) => {
Some(&n) => next.resolved.push(Term::AddOrRetract(op, n, a, v)), next.resolved
None => next.allocations.push(Term::AddOrRetract(op, Right(t), a, Left(v))), .push(Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0)))
}
},
Term::AddOrRetract(op, Left(e), a, Right(t)) => {
match temp_id_map.get(&*t) {
Some(&n) => next.resolved.push(Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0))),
None => next.allocations.push(Term::AddOrRetract(op, Left(e), a, Right(t))),
} }
None => next
.allocations
.push(Term::AddOrRetract(op, Left(e), a, Right(t))),
}, },
Term::AddOrRetract(_, Left(_), _, Left(_)) => unreachable!(), Term::AddOrRetract(_, Left(_), _, Left(_)) => unreachable!(),
} }
@ -232,7 +243,11 @@ impl Generation {
let mut upserts_ev = vec![]; let mut upserts_ev = vec![];
::std::mem::swap(&mut self.upserts_ev, &mut upserts_ev); ::std::mem::swap(&mut self.upserts_ev, &mut upserts_ev);
self.allocations.extend(upserts_ev.into_iter().map(|UpsertEV(t1, a, t2)| Term::AddOrRetract(OpType::Add, Right(t1), a, Right(t2)))); self.allocations.extend(
upserts_ev.into_iter().map(|UpsertEV(t1, a, t2)| {
Term::AddOrRetract(OpType::Add, Right(t1), a, Right(t2))
}),
);
Ok(()) Ok(())
} }
@ -241,12 +256,16 @@ impl Generation {
/// ///
/// Some of the tempids may be identified, so we also provide a map from tempid to a dense set /// Some of the tempids may be identified, so we also provide a map from tempid to a dense set
/// of contiguous integer labels. /// of contiguous integer labels.
pub(crate) fn temp_ids_in_allocations(&self, schema: &Schema) -> Result<BTreeMap<TempIdHandle, usize>> { pub(crate) fn temp_ids_in_allocations(
&self,
schema: &Schema,
) -> Result<BTreeMap<TempIdHandle, usize>> {
assert!(self.upserts_e.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!"); assert!(self.upserts_e.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!");
assert!(self.upserts_ev.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!"); assert!(self.upserts_ev.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!");
let mut temp_ids: BTreeSet<TempIdHandle> = BTreeSet::default(); let mut temp_ids: BTreeSet<TempIdHandle> = BTreeSet::default();
let mut tempid_avs: BTreeMap<(Entid, TypedValueOr<TempIdHandle>), Vec<TempIdHandle>> = BTreeMap::default(); let mut tempid_avs: BTreeMap<(Entid, TypedValueOr<TempIdHandle>), Vec<TempIdHandle>> =
BTreeMap::default();
for term in self.allocations.iter() { for term in self.allocations.iter() {
match term { match term {
@ -255,24 +274,30 @@ impl Generation {
temp_ids.insert(t2.clone()); temp_ids.insert(t2.clone());
let attribute: &Attribute = schema.require_attribute_for_entid(a)?; let attribute: &Attribute = schema.require_attribute_for_entid(a)?;
if attribute.unique == Some(attribute::Unique::Identity) { if attribute.unique == Some(attribute::Unique::Identity) {
tempid_avs.entry((a, Right(t2.clone()))).or_insert(vec![]).push(t1.clone()); tempid_avs
.entry((a, Right(t2.clone())))
.or_insert(vec![])
.push(t1.clone());
} }
}, }
&Term::AddOrRetract(OpType::Add, Right(ref t), a, ref x @ Left(_)) => { &Term::AddOrRetract(OpType::Add, Right(ref t), a, ref x @ Left(_)) => {
temp_ids.insert(t.clone()); temp_ids.insert(t.clone());
let attribute: &Attribute = schema.require_attribute_for_entid(a)?; let attribute: &Attribute = schema.require_attribute_for_entid(a)?;
if attribute.unique == Some(attribute::Unique::Identity) { if attribute.unique == Some(attribute::Unique::Identity) {
tempid_avs.entry((a, x.clone())).or_insert(vec![]).push(t.clone()); tempid_avs
.entry((a, x.clone()))
.or_insert(vec![])
.push(t.clone());
} }
}, }
&Term::AddOrRetract(OpType::Add, Left(_), _, Right(ref t)) => { &Term::AddOrRetract(OpType::Add, Left(_), _, Right(ref t)) => {
temp_ids.insert(t.clone()); temp_ids.insert(t.clone());
}, }
&Term::AddOrRetract(OpType::Add, Left(_), _, Left(_)) => unreachable!(), &Term::AddOrRetract(OpType::Add, Left(_), _, Left(_)) => unreachable!(),
&Term::AddOrRetract(OpType::Retract, _, _, _) => { &Term::AddOrRetract(OpType::Retract, _, _, _) => {
// [:db/retract ...] entities never allocate entids; they have to resolve due to // [:db/retract ...] entities never allocate entids; they have to resolve due to
// other upserts (or they fail the transaction). // other upserts (or they fail the transaction).
}, }
} }
} }
@ -282,16 +307,25 @@ impl Generation {
// The union-find implementation from petgraph operates on contiguous indices, so we need to // The union-find implementation from petgraph operates on contiguous indices, so we need to
// maintain the map from our tempids to indices ourselves. // maintain the map from our tempids to indices ourselves.
let temp_ids: BTreeMap<TempIdHandle, usize> = temp_ids.into_iter().enumerate().map(|(i, tempid)| (tempid, i)).collect(); let temp_ids: BTreeMap<TempIdHandle, usize> = temp_ids
.into_iter()
.enumerate()
.map(|(i, tempid)| (tempid, i))
.collect();
debug!("need to label tempids aggregated using tempid_avs {:?}", tempid_avs); debug!(
"need to label tempids aggregated using tempid_avs {:?}",
tempid_avs
);
for vs in tempid_avs.values() { for vs in tempid_avs.values() {
vs.first().and_then(|first| temp_ids.get(first)).map(|&first_index| { vs.first()
for tempid in vs { .and_then(|first| temp_ids.get(first))
temp_ids.get(tempid).map(|&i| uf.union(first_index, i)); .map(|&first_index| {
} for tempid in vs {
}); temp_ids.get(tempid).map(|&i| uf.union(first_index, i));
}
});
} }
debug!("union-find aggregation {:?}", uf.clone().into_labeling()); debug!("union-find aggregation {:?}", uf.clone().into_labeling());
@ -308,17 +342,26 @@ impl Generation {
for (tempid, tempid_index) in temp_ids { for (tempid, tempid_index) in temp_ids {
let rep = uf.find_mut(tempid_index); let rep = uf.find_mut(tempid_index);
dense_labels.insert(rep); dense_labels.insert(rep);
dense_labels.get_full(&rep).map(|(dense_index, _)| tempid_map.insert(tempid.clone(), dense_index)); dense_labels
.get_full(&rep)
.map(|(dense_index, _)| tempid_map.insert(tempid.clone(), dense_index));
} }
debug!("labeled tempids using {} labels: {:?}", dense_labels.len(), tempid_map); debug!(
"labeled tempids using {} labels: {:?}",
dense_labels.len(),
tempid_map
);
Ok(tempid_map) Ok(tempid_map)
} }
/// After evolution is complete, use the provided allocated entids to segment `self` into /// After evolution is complete, use the provided allocated entids to segment `self` into
/// populations, each with no references to tempids. /// populations, each with no references to tempids.
pub(crate) fn into_final_populations(self, temp_id_map: &TempIdMap) -> Result<FinalPopulations> { pub(crate) fn into_final_populations(
self,
temp_id_map: &TempIdMap,
) -> Result<FinalPopulations> {
assert!(self.upserts_e.is_empty()); assert!(self.upserts_e.is_empty());
assert!(self.upserts_ev.is_empty()); assert!(self.upserts_ev.is_empty());
@ -336,21 +379,27 @@ impl Generation {
(OpType::Add, _, _) => unreachable!(), // This is a coding error -- every tempid in a :db/add entity should resolve or be allocated. (OpType::Add, _, _) => unreachable!(), // This is a coding error -- every tempid in a :db/add entity should resolve or be allocated.
(OpType::Retract, _, _) => bail!(DbErrorKind::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: one of {}, {}", t1, t2))), (OpType::Retract, _, _) => bail!(DbErrorKind::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: one of {}, {}", t1, t2))),
} }
}, }
Term::AddOrRetract(op, Right(t), a, Left(v)) => { Term::AddOrRetract(op, Right(t), a, Left(v)) => {
match (op, temp_id_map.get(&*t)) { match (op, temp_id_map.get(&*t)) {
(op, Some(&n)) => Term::AddOrRetract(op, n, a, v), (op, Some(&n)) => Term::AddOrRetract(op, n, a, v),
(OpType::Add, _) => unreachable!(), // This is a coding error. (OpType::Add, _) => unreachable!(), // This is a coding error.
(OpType::Retract, _) => bail!(DbErrorKind::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: {}", t))), (OpType::Retract, _) => bail!(DbErrorKind::NotYetImplemented(format!(
"[:db/retract ...] entity referenced tempid that did not upsert: {}",
t
))),
} }
}, }
Term::AddOrRetract(op, Left(e), a, Right(t)) => { Term::AddOrRetract(op, Left(e), a, Right(t)) => {
match (op, temp_id_map.get(&*t)) { match (op, temp_id_map.get(&*t)) {
(op, Some(&n)) => Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0)), (op, Some(&n)) => Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0)),
(OpType::Add, _) => unreachable!(), // This is a coding error. (OpType::Add, _) => unreachable!(), // This is a coding error.
(OpType::Retract, _) => bail!(DbErrorKind::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: {}", t))), (OpType::Retract, _) => bail!(DbErrorKind::NotYetImplemented(format!(
"[:db/retract ...] entity referenced tempid that did not upsert: {}",
t
))),
} }
}, }
Term::AddOrRetract(_, Left(_), _, Left(_)) => unreachable!(), // This is a coding error -- these should not be in allocations. Term::AddOrRetract(_, Left(_), _, Left(_)) => unreachable!(), // This is a coding error -- these should not be in allocations.
}; };
populations.allocated.push(allocated); populations.allocated.push(allocated);

View file

@ -17,22 +17,13 @@
// - When observers are registered we want to flip some flags as writes occur so that we can // - When observers are registered we want to flip some flags as writes occur so that we can
// notifying them outside the transaction. // notifying them outside the transaction.
use core_traits::{ use core_traits::{Entid, TypedValue};
Entid,
TypedValue,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::entities::{ use edn::entities::OpType;
OpType,
};
use db_traits::errors::{ use db_traits::errors::Result;
Result,
};
pub trait TransactWatcher { pub trait TransactWatcher {
fn datom(&mut self, op: OpType, e: Entid, a: Entid, v: &TypedValue); fn datom(&mut self, op: OpType, e: Entid, a: Entid, v: &TypedValue);
@ -47,8 +38,7 @@ pub trait TransactWatcher {
pub struct NullWatcher(); pub struct NullWatcher();
impl TransactWatcher for NullWatcher { impl TransactWatcher for NullWatcher {
fn datom(&mut self, _op: OpType, _e: Entid, _a: Entid, _v: &TypedValue) { fn datom(&mut self, _op: OpType, _e: Entid, _a: Entid, _v: &TypedValue) {}
}
fn done(&mut self, _t: &Entid, _schema: &Schema) -> Result<()> { fn done(&mut self, _t: &Entid, _schema: &Schema) -> Result<()> {
Ok(()) Ok(())

View file

@ -8,8 +8,8 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate edn;
extern crate core_traits; extern crate core_traits;
extern crate edn;
extern crate mentat_db; extern crate mentat_db;
extern crate ordered_float; extern crate ordered_float;
extern crate rusqlite; extern crate rusqlite;
@ -18,44 +18,100 @@ use ordered_float::OrderedFloat;
use edn::symbols; use edn::symbols;
use core_traits::{ use core_traits::{TypedValue, ValueType};
TypedValue,
ValueType,
};
use mentat_db::db::TypedSQLValue; use mentat_db::db::TypedSQLValue;
// It's not possible to test to_sql_value_pair since rusqlite::ToSqlOutput doesn't implement // It's not possible to test to_sql_value_pair since rusqlite::ToSqlOutput doesn't implement
// PartialEq. // PartialEq.
#[test] #[test]
fn test_from_sql_value_pair() { fn test_from_sql_value_pair() {
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1234), 0).unwrap(), TypedValue::Ref(1234)); assert_eq!(
TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1234), 0).unwrap(),
TypedValue::Ref(1234)
);
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(0), 1).unwrap(), TypedValue::Boolean(false)); assert_eq!(
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1), 1).unwrap(), TypedValue::Boolean(true)); TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(0), 1).unwrap(),
TypedValue::Boolean(false)
);
assert_eq!(
TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1), 1).unwrap(),
TypedValue::Boolean(true)
);
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(0), 5).unwrap(), TypedValue::Long(0)); assert_eq!(
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1234), 5).unwrap(), TypedValue::Long(1234)); TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(0), 5).unwrap(),
TypedValue::Long(0)
);
assert_eq!(
TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1234), 5).unwrap(),
TypedValue::Long(1234)
);
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Real(0.0), 5).unwrap(), TypedValue::Double(OrderedFloat(0.0))); assert_eq!(
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Real(0.5), 5).unwrap(), TypedValue::Double(OrderedFloat(0.5))); TypedValue::from_sql_value_pair(rusqlite::types::Value::Real(0.0), 5).unwrap(),
TypedValue::Double(OrderedFloat(0.0))
);
assert_eq!(
TypedValue::from_sql_value_pair(rusqlite::types::Value::Real(0.5), 5).unwrap(),
TypedValue::Double(OrderedFloat(0.5))
);
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Text(":db/keyword".into()), 10).unwrap(), TypedValue::typed_string(":db/keyword")); assert_eq!(
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Text(":db/keyword".into()), 13).unwrap(), TypedValue::typed_ns_keyword("db", "keyword")); TypedValue::from_sql_value_pair(rusqlite::types::Value::Text(":db/keyword".into()), 10)
.unwrap(),
TypedValue::typed_string(":db/keyword")
);
assert_eq!(
TypedValue::from_sql_value_pair(rusqlite::types::Value::Text(":db/keyword".into()), 13)
.unwrap(),
TypedValue::typed_ns_keyword("db", "keyword")
);
} }
#[test] #[test]
fn test_to_edn_value_pair() { fn test_to_edn_value_pair() {
assert_eq!(TypedValue::Ref(1234).to_edn_value_pair(), (edn::Value::Integer(1234), ValueType::Ref)); assert_eq!(
TypedValue::Ref(1234).to_edn_value_pair(),
(edn::Value::Integer(1234), ValueType::Ref)
);
assert_eq!(TypedValue::Boolean(false).to_edn_value_pair(), (edn::Value::Boolean(false), ValueType::Boolean)); assert_eq!(
assert_eq!(TypedValue::Boolean(true).to_edn_value_pair(), (edn::Value::Boolean(true), ValueType::Boolean)); TypedValue::Boolean(false).to_edn_value_pair(),
(edn::Value::Boolean(false), ValueType::Boolean)
);
assert_eq!(
TypedValue::Boolean(true).to_edn_value_pair(),
(edn::Value::Boolean(true), ValueType::Boolean)
);
assert_eq!(TypedValue::Long(0).to_edn_value_pair(), (edn::Value::Integer(0), ValueType::Long)); assert_eq!(
assert_eq!(TypedValue::Long(1234).to_edn_value_pair(), (edn::Value::Integer(1234), ValueType::Long)); TypedValue::Long(0).to_edn_value_pair(),
(edn::Value::Integer(0), ValueType::Long)
);
assert_eq!(
TypedValue::Long(1234).to_edn_value_pair(),
(edn::Value::Integer(1234), ValueType::Long)
);
assert_eq!(TypedValue::Double(OrderedFloat(0.0)).to_edn_value_pair(), (edn::Value::Float(OrderedFloat(0.0)), ValueType::Double)); assert_eq!(
assert_eq!(TypedValue::Double(OrderedFloat(0.5)).to_edn_value_pair(), (edn::Value::Float(OrderedFloat(0.5)), ValueType::Double)); TypedValue::Double(OrderedFloat(0.0)).to_edn_value_pair(),
(edn::Value::Float(OrderedFloat(0.0)), ValueType::Double)
);
assert_eq!(
TypedValue::Double(OrderedFloat(0.5)).to_edn_value_pair(),
(edn::Value::Float(OrderedFloat(0.5)), ValueType::Double)
);
assert_eq!(TypedValue::typed_string(":db/keyword").to_edn_value_pair(), (edn::Value::Text(":db/keyword".into()), ValueType::String)); assert_eq!(
assert_eq!(TypedValue::typed_ns_keyword("db", "keyword").to_edn_value_pair(), (edn::Value::Keyword(symbols::Keyword::namespaced("db", "keyword")), ValueType::Keyword)); TypedValue::typed_string(":db/keyword").to_edn_value_pair(),
(edn::Value::Text(":db/keyword".into()), ValueType::String)
);
assert_eq!(
TypedValue::typed_ns_keyword("db", "keyword").to_edn_value_pair(),
(
edn::Value::Keyword(symbols::Keyword::namespaced("db", "keyword")),
ValueType::Keyword
)
);
} }

View file

@ -11,12 +11,12 @@ build = "build.rs"
readme = "./README.md" readme = "./README.md"
[dependencies] [dependencies]
chrono = "0.4" chrono = "0.4.10"
itertools = "0.7" itertools = "0.8.2"
num = "0.1" num = "0.2.1"
ordered-float = "0.5" ordered-float = "1.0.2"
pretty = "0.2" pretty = "0.9.0"
uuid = { version = "0.5", features = ["v4", "serde"] } uuid = { version = "0.8", features = ["v4", "serde"] }
serde = { version = "1.0", optional = true } serde = { version = "1.0", optional = true }
serde_derive = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true }

View file

@ -13,18 +13,11 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt; use std::fmt;
use value_rc::{ use value_rc::ValueRc;
ValueRc,
};
use symbols::{ use symbols::{Keyword, PlainSymbol};
Keyword,
PlainSymbol,
};
use types::{ use types::ValueAndSpan;
ValueAndSpan,
};
/// `EntityPlace` and `ValuePlace` embed values, either directly (i.e., `ValuePlace::Atom`) or /// `EntityPlace` and `ValuePlace` embed values, either directly (i.e., `ValuePlace::Atom`) or
/// indirectly (i.e., `EntityPlace::LookupRef`). In order to maintain the graph of `Into` and /// indirectly (i.e., `EntityPlace::LookupRef`). In order to maintain the graph of `Into` and

View file

@ -12,14 +12,9 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::hash::Hash; use std::hash::Hash;
use std::ops::{ use std::ops::{Deref, DerefMut};
Deref,
DerefMut,
};
use ::{ use ValueRc;
ValueRc,
};
/// An `InternSet` allows to "intern" some potentially large values, maintaining a single value /// An `InternSet` allows to "intern" some potentially large values, maintaining a single value
/// instance owned by the `InternSet` and leaving consumers with lightweight ref-counted handles to /// instance owned by the `InternSet` and leaving consumers with lightweight ref-counted handles to
@ -29,11 +24,17 @@ use ::{
/// ///
/// See https://en.wikipedia.org/wiki/String_interning for discussion. /// See https://en.wikipedia.org/wiki/String_interning for discussion.
#[derive(Clone, Debug, Default, Eq, PartialEq)] #[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct InternSet<T> where T: Eq + Hash { pub struct InternSet<T>
where
T: Eq + Hash,
{
inner: HashSet<ValueRc<T>>, inner: HashSet<ValueRc<T>>,
} }
impl<T> Deref for InternSet<T> where T: Eq + Hash { impl<T> Deref for InternSet<T>
where
T: Eq + Hash,
{
type Target = HashSet<ValueRc<T>>; type Target = HashSet<ValueRc<T>>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -41,13 +42,19 @@ impl<T> Deref for InternSet<T> where T: Eq + Hash {
} }
} }
impl<T> DerefMut for InternSet<T> where T: Eq + Hash { impl<T> DerefMut for InternSet<T>
where
T: Eq + Hash,
{
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner &mut self.inner
} }
} }
impl<T> InternSet<T> where T: Eq + Hash { impl<T> InternSet<T>
where
T: Eq + Hash,
{
pub fn new() -> InternSet<T> { pub fn new() -> InternSet<T> {
InternSet { InternSet {
inner: HashSet::new(), inner: HashSet::new(),

View file

@ -8,6 +8,8 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
#![allow(ellipsis_inclusive_range_patterns)]
extern crate chrono; extern crate chrono;
extern crate itertools; extern crate itertools;
extern crate num; extern crate num;
@ -24,23 +26,17 @@ extern crate serde_derive;
pub mod entities; pub mod entities;
pub mod intern_set; pub mod intern_set;
pub use intern_set::{ pub use intern_set::InternSet;
InternSet,
};
// Intentionally not pub. // Intentionally not pub.
pub mod matcher;
mod namespaceable_name; mod namespaceable_name;
pub mod pretty_print;
pub mod query; pub mod query;
pub mod symbols; pub mod symbols;
pub mod types; pub mod types;
pub mod pretty_print;
pub mod utils; pub mod utils;
pub mod matcher;
pub mod value_rc; pub mod value_rc;
pub use value_rc::{ pub use value_rc::{Cloned, FromRc, ValueRc};
Cloned,
FromRc,
ValueRc,
};
pub mod parse { pub mod parse {
include!(concat!(env!("OUT_DIR"), "/edn.rs")); include!(concat!(env!("OUT_DIR"), "/edn.rs"));
@ -54,20 +50,8 @@ pub use uuid::Uuid;
// Export from our modules. // Export from our modules.
pub use parse::ParseError; pub use parse::ParseError;
pub use uuid::ParseError as UuidParseError;
pub use types::{ pub use types::{
FromMicros, FromMicros, FromMillis, Span, SpannedValue, ToMicros, ToMillis, Value, ValueAndSpan,
FromMillis,
Span,
SpannedValue,
ToMicros,
ToMillis,
Value,
ValueAndSpan,
}; };
pub use symbols::{ pub use symbols::{Keyword, NamespacedSymbol, PlainSymbol};
Keyword,
NamespacedSymbol,
PlainSymbol,
};

View file

@ -8,9 +8,9 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use std::collections::HashMap;
use std::cell::RefCell;
use itertools::diff_with; use itertools::diff_with;
use std::cell::RefCell;
use std::collections::HashMap;
use symbols; use symbols;
use types::Value; use types::Value;
@ -21,7 +21,7 @@ trait PatternMatchingRules<'a, T> {
fn matches_any(pattern: &T) -> bool; fn matches_any(pattern: &T) -> bool;
/// Return the placeholder name if the given pattern matches a placeholder. /// Return the placeholder name if the given pattern matches a placeholder.
fn matches_placeholder(pattern: &'a T) -> Option<(&'a String)>; fn matches_placeholder(pattern: &'a T) -> Option<&'a String>;
} }
/// A default type implementing `PatternMatchingRules` specialized on /// A default type implementing `PatternMatchingRules` specialized on
@ -34,14 +34,20 @@ impl<'a> PatternMatchingRules<'a, Value> for DefaultPatternMatchingRules {
fn matches_any(pattern: &Value) -> bool { fn matches_any(pattern: &Value) -> bool {
match *pattern { match *pattern {
Value::PlainSymbol(symbols::PlainSymbol(ref s)) => s.starts_with('_'), Value::PlainSymbol(symbols::PlainSymbol(ref s)) => s.starts_with('_'),
_ => false _ => false,
} }
} }
fn matches_placeholder(pattern: &'a Value) -> Option<(&'a String)> { fn matches_placeholder(pattern: &'a Value) -> Option<&'a String> {
match *pattern { match *pattern {
Value::PlainSymbol(symbols::PlainSymbol(ref s)) => if s.starts_with('?') { Some(s) } else { None }, Value::PlainSymbol(symbols::PlainSymbol(ref s)) => {
_ => None if s.starts_with('?') {
Some(s)
} else {
None
}
}
_ => None,
} }
} }
} }
@ -52,14 +58,14 @@ impl<'a> PatternMatchingRules<'a, Value> for DefaultPatternMatchingRules {
/// * `[_ _]` matches an arbitrary two-element vector; /// * `[_ _]` matches an arbitrary two-element vector;
/// * `[?x ?x]` matches `[1 1]` and `[#{} #{}]` but not `[1 2]` or `[[] #{}]`; /// * `[?x ?x]` matches `[1 1]` and `[#{} #{}]` but not `[1 2]` or `[[] #{}]`;
struct Matcher<'a> { struct Matcher<'a> {
placeholders: RefCell<HashMap<&'a String, &'a Value>> placeholders: RefCell<HashMap<&'a String, &'a Value>>,
} }
impl<'a> Matcher<'a> { impl<'a> Matcher<'a> {
/// Creates a Matcher instance. /// Creates a Matcher instance.
fn new() -> Matcher<'a> { fn new() -> Matcher<'a> {
Matcher { Matcher {
placeholders: RefCell::default() placeholders: RefCell::default(),
} }
} }
@ -67,7 +73,9 @@ impl<'a> Matcher<'a> {
/// and `pattern`) utilizing a specified pattern matching ruleset `T`. /// and `pattern`) utilizing a specified pattern matching ruleset `T`.
/// Returns true if matching succeeds. /// Returns true if matching succeeds.
fn match_with_rules<T>(value: &'a Value, pattern: &'a Value) -> bool fn match_with_rules<T>(value: &'a Value, pattern: &'a Value) -> bool
where T: PatternMatchingRules<'a, Value> { where
T: PatternMatchingRules<'a, Value>,
{
let matcher = Matcher::new(); let matcher = Matcher::new();
matcher.match_internal::<T>(value, pattern) matcher.match_internal::<T>(value, pattern)
} }
@ -76,7 +84,9 @@ impl<'a> Matcher<'a> {
/// performing pattern matching. Note that the internal `placeholders` cache /// performing pattern matching. Note that the internal `placeholders` cache
/// might not be empty on invocation. /// might not be empty on invocation.
fn match_internal<T>(&self, value: &'a Value, pattern: &'a Value) -> bool fn match_internal<T>(&self, value: &'a Value, pattern: &'a Value) -> bool
where T: PatternMatchingRules<'a, Value> { where
T: PatternMatchingRules<'a, Value>,
{
use Value::*; use Value::*;
if T::matches_any(pattern) { if T::matches_any(pattern) {
@ -86,19 +96,35 @@ impl<'a> Matcher<'a> {
value == *placeholders.entry(symbol).or_insert(value) value == *placeholders.entry(symbol).or_insert(value)
} else { } else {
match (value, pattern) { match (value, pattern) {
(&Vector(ref v), &Vector(ref p)) => (&Vector(ref v), &Vector(ref p)) => {
diff_with(v, p, |a, b| self.match_internal::<T>(a, b)).is_none(), diff_with(v, p, |a, b| self.match_internal::<T>(a, b)).is_none()
(&List(ref v), &List(ref p)) => }
diff_with(v, p, |a, b| self.match_internal::<T>(a, b)).is_none(), (&List(ref v), &List(ref p)) => {
(&Set(ref v), &Set(ref p)) => diff_with(v, p, |a, b| self.match_internal::<T>(a, b)).is_none()
v.len() == p.len() && }
v.iter().all(|a| p.iter().any(|b| self.match_internal::<T>(a, b))) && (&Set(ref v), &Set(ref p)) => {
p.iter().all(|b| v.iter().any(|a| self.match_internal::<T>(a, b))), v.len() == p.len()
(&Map(ref v), &Map(ref p)) => && v.iter()
v.len() == p.len() && .all(|a| p.iter().any(|b| self.match_internal::<T>(a, b)))
v.iter().all(|a| p.iter().any(|b| self.match_internal::<T>(a.0, b.0) && self.match_internal::<T>(a.1, b.1))) && && p.iter()
p.iter().all(|b| v.iter().any(|a| self.match_internal::<T>(a.0, b.0) && self.match_internal::<T>(a.1, b.1))), .all(|b| v.iter().any(|a| self.match_internal::<T>(a, b)))
_ => value == pattern }
(&Map(ref v), &Map(ref p)) => {
v.len() == p.len()
&& v.iter().all(|a| {
p.iter().any(|b| {
self.match_internal::<T>(a.0, b.0)
&& self.match_internal::<T>(a.1, b.1)
})
})
&& p.iter().all(|b| {
v.iter().any(|a| {
self.match_internal::<T>(a.0, b.0)
&& self.match_internal::<T>(a.1, b.1)
})
})
}
_ => value == pattern,
} }
} }
} }
@ -127,7 +153,7 @@ mod test {
}; };
( $pattern:tt !~ $value:tt ) => { ( $pattern:tt !~ $value:tt ) => {
assert_match!($pattern, $value, false); assert_match!($pattern, $value, false);
} };
} }
#[test] #[test]

View file

@ -8,25 +8,14 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use std::cmp::{ use std::cmp::{Ord, Ordering, PartialOrd};
Ord,
Ordering,
PartialOrd,
};
use std::fmt; use std::fmt;
#[cfg(feature = "serde_support")] #[cfg(feature = "serde_support")]
use serde::de::{ use serde::de::{self, Deserialize, Deserializer};
self,
Deserialize,
Deserializer
};
#[cfg(feature = "serde_support")] #[cfg(feature = "serde_support")]
use serde::ser::{ use serde::ser::{Serialize, Serializer};
Serialize,
Serializer,
};
// Data storage for both NamespaceableKeyword and NamespaceableSymbol. // Data storage for both NamespaceableKeyword and NamespaceableSymbol.
#[derive(Clone, Eq, Hash, PartialEq)] #[derive(Clone, Eq, Hash, PartialEq)]
@ -56,7 +45,10 @@ pub struct NamespaceableName {
impl NamespaceableName { impl NamespaceableName {
#[inline] #[inline]
pub fn plain<T>(name: T) -> Self where T: Into<String> { pub fn plain<T>(name: T) -> Self
where
T: Into<String>,
{
let n = name.into(); let n = name.into();
assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed."); assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed.");
@ -67,14 +59,21 @@ impl NamespaceableName {
} }
#[inline] #[inline]
pub fn namespaced<N, T>(namespace: N, name: T) -> Self where N: AsRef<str>, T: AsRef<str> { pub fn namespaced<N, T>(namespace: N, name: T) -> Self
where
N: AsRef<str>,
T: AsRef<str>,
{
let n = name.as_ref(); let n = name.as_ref();
let ns = namespace.as_ref(); let ns = namespace.as_ref();
// Note: These invariants are not required for safety. That is, if we // Note: These invariants are not required for safety. That is, if we
// decide to allow these we can safely remove them. // decide to allow these we can safely remove them.
assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed."); assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed.");
assert!(!ns.is_empty(), "Symbols and keywords cannot have an empty non-null namespace."); assert!(
!ns.is_empty(),
"Symbols and keywords cannot have an empty non-null namespace."
);
let mut dest = String::with_capacity(n.len() + ns.len()); let mut dest = String::with_capacity(n.len() + ns.len());
@ -90,7 +89,11 @@ impl NamespaceableName {
} }
} }
fn new<N, T>(namespace: Option<N>, name: T) -> Self where N: AsRef<str>, T: AsRef<str> { fn new<N, T>(namespace: Option<N>, name: T) -> Self
where
N: AsRef<str>,
T: AsRef<str>,
{
if let Some(ns) = namespace { if let Some(ns) = namespace {
Self::namespaced(ns, name) Self::namespaced(ns, name)
} else { } else {
@ -143,11 +146,12 @@ impl NamespaceableName {
#[inline] #[inline]
pub fn components<'a>(&'a self) -> (&'a str, &'a str) { pub fn components<'a>(&'a self) -> (&'a str, &'a str) {
if self.boundary > 0 { if self.boundary > 0 {
(&self.components[0..self.boundary], (
&self.components[(self.boundary + 1)..]) &self.components[0..self.boundary],
&self.components[(self.boundary + 1)..],
)
} else { } else {
(&self.components[0..0], (&self.components[0..0], &self.components)
&self.components)
} }
} }
} }
@ -163,7 +167,7 @@ impl PartialOrd for NamespaceableName {
(_, _) => { (_, _) => {
// Just use a lexicographic ordering. // Just use a lexicographic ordering.
self.components().partial_cmp(&other.components()) self.components().partial_cmp(&other.components())
}, }
} }
} }
} }
@ -178,9 +182,9 @@ impl Ord for NamespaceableName {
impl fmt::Debug for NamespaceableName { impl fmt::Debug for NamespaceableName {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("NamespaceableName") fmt.debug_struct("NamespaceableName")
.field("namespace", &self.namespace()) .field("namespace", &self.namespace())
.field("name", &self.name()) .field("name", &self.name())
.finish() .finish()
} }
} }
@ -210,14 +214,19 @@ struct SerializedNamespaceableName<'a> {
#[cfg(feature = "serde_support")] #[cfg(feature = "serde_support")]
impl<'de> Deserialize<'de> for NamespaceableName { impl<'de> Deserialize<'de> for NamespaceableName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let separated = SerializedNamespaceableName::deserialize(deserializer)?; let separated = SerializedNamespaceableName::deserialize(deserializer)?;
if separated.name.len() == 0 { if separated.name.len() == 0 {
return Err(de::Error::custom("Empty name in keyword or symbol")); return Err(de::Error::custom("Empty name in keyword or symbol"));
} }
if let Some(ns) = separated.namespace { if let Some(ns) = separated.namespace {
if ns.len() == 0 { if ns.len() == 0 {
Err(de::Error::custom("Empty but present namespace in keyword or symbol")) Err(de::Error::custom(
"Empty but present namespace in keyword or symbol",
))
} else { } else {
Ok(NamespaceableName::namespaced(ns, separated.name)) Ok(NamespaceableName::namespaced(ns, separated.name))
} }
@ -229,7 +238,10 @@ impl<'de> Deserialize<'de> for NamespaceableName {
#[cfg(feature = "serde_support")] #[cfg(feature = "serde_support")]
impl Serialize for NamespaceableName { impl Serialize for NamespaceableName {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let ser = SerializedNamespaceableName { let ser = SerializedNamespaceableName {
namespace: self.namespace(), namespace: self.namespace(),
name: self.name(), name: self.name(),
@ -245,12 +257,18 @@ mod test {
#[test] #[test]
fn test_new_invariants_maintained() { fn test_new_invariants_maintained() {
assert!(panic::catch_unwind(|| NamespaceableName::namespaced("", "foo")).is_err(), assert!(
"Empty namespace should panic"); panic::catch_unwind(|| NamespaceableName::namespaced("", "foo")).is_err(),
assert!(panic::catch_unwind(|| NamespaceableName::namespaced("foo", "")).is_err(), "Empty namespace should panic"
"Empty name should panic"); );
assert!(panic::catch_unwind(|| NamespaceableName::namespaced("", "")).is_err(), assert!(
"Should panic if both fields are empty"); panic::catch_unwind(|| NamespaceableName::namespaced("foo", "")).is_err(),
"Empty name should panic"
);
assert!(
panic::catch_unwind(|| NamespaceableName::namespaced("", "")).is_err(),
"Should panic if both fields are empty"
);
} }
#[test] #[test]
@ -286,19 +304,22 @@ mod test {
n3.clone(), n3.clone(),
n2.clone(), n2.clone(),
n1.clone(), n1.clone(),
n4.clone() n4.clone(),
]; ];
arr.sort(); arr.sort();
assert_eq!(arr, [ assert_eq!(
n0.clone(), arr,
n2.clone(), [
n1.clone(), n0.clone(),
n3.clone(), n2.clone(),
n4.clone(), n1.clone(),
n5.clone(), n3.clone(),
n6.clone(), n4.clone(),
]); n5.clone(),
n6.clone(),
]
);
} }
} }

View file

@ -8,15 +8,13 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use chrono::{ use chrono::SecondsFormat;
SecondsFormat,
};
use itertools::Itertools; use itertools::Itertools;
use pretty; use pretty;
use std::io;
use std::borrow::Cow; use std::borrow::Cow;
use std::io;
use types::Value; use types::Value;
@ -29,7 +27,10 @@ impl Value {
} }
/// Write a pretty representation of this `Value` to the given writer. /// Write a pretty representation of this `Value` to the given writer.
fn write_pretty<W>(&self, width: usize, out: &mut W) -> Result<(), io::Error> where W: io::Write { fn write_pretty<W>(&self, width: usize, out: &mut W) -> Result<(), io::Error>
where
W: io::Write,
{
self.as_doc(&pretty::BoxAllocator).1.render(width, out) self.as_doc(&pretty::BoxAllocator).1.render(width, out)
} }
@ -41,28 +42,50 @@ impl Value {
/// [1, /// [1,
/// 2, /// 2,
/// 3]. /// 3].
fn bracket<'a, A, T, I>(&'a self, allocator: &'a A, open: T, vs: I, close: T) -> pretty::DocBuilder<'a, A> fn bracket<'a, A, T, I>(
where A: pretty::DocAllocator<'a>, T: Into<Cow<'a, str>>, I: IntoIterator<Item=&'a Value> { &'a self,
allocator: &'a A,
open: T,
vs: I,
close: T,
) -> pretty::DocBuilder<'a, A>
where
A: pretty::DocAllocator<'a>,
<A as pretty::DocAllocator<'a>>::Doc: std::clone::Clone,
T: Into<Cow<'a, str>>,
I: IntoIterator<Item = &'a Value>,
{
let open = open.into(); let open = open.into();
let n = open.len(); let n = open.len() as isize;
let i = vs.into_iter().map(|v| v.as_doc(allocator)).intersperse(allocator.space()); let i = vs
allocator.text(open) .into_iter()
.map(|v| v.as_doc(allocator))
.intersperse(allocator.space());
allocator
.text(open)
.append(allocator.concat(i).nest(n)) .append(allocator.concat(i).nest(n))
.append(allocator.text(close)) .append(allocator.text(close))
.group() .group()
} }
/// Recursively traverses this value and creates a pretty.rs document. /// Recursively traverses this value and creates a pretty.rs document.
/// This pretty printing implementation is optimized for edn queries /// A pretty printing implementation for edn queries optimized for
/// readability and limited whitespace expansion. /// readability and limited whitespace expansion.
fn as_doc<'a, A>(&'a self, pp: &'a A) -> pretty::DocBuilder<'a, A> fn as_doc<'a, A>(&'a self, pp: &'a A) -> pretty::DocBuilder<'a, A>
where A: pretty::DocAllocator<'a> { where
A: pretty::DocAllocator<'a>,
<A as pretty::DocAllocator<'a>>::Doc: std::clone::Clone,
{
match *self { match *self {
Value::Vector(ref vs) => self.bracket(pp, "[", vs, "]"), Value::Vector(ref vs) => self.bracket(pp, "[", vs, "]"),
Value::List(ref vs) => self.bracket(pp, "(", vs, ")"), Value::List(ref vs) => self.bracket(pp, "(", vs, ")"),
Value::Set(ref vs) => self.bracket(pp, "#{", vs, "}"), Value::Set(ref vs) => self.bracket(pp, "#{", vs, "}"),
Value::Map(ref vs) => { Value::Map(ref vs) => {
let xs = vs.iter().rev().map(|(k, v)| k.as_doc(pp).append(pp.space()).append(v.as_doc(pp)).group()).intersperse(pp.space()); let xs = vs
.iter()
.rev()
.map(|(k, v)| k.as_doc(pp).append(pp.space()).append(v.as_doc(pp)).group())
.intersperse(pp.space());
pp.text("{") pp.text("{")
.append(pp.concat(xs).nest(1)) .append(pp.concat(xs).nest(1))
.append(pp.text("}")) .append(pp.text("}"))
@ -72,9 +95,15 @@ impl Value {
Value::PlainSymbol(ref v) => pp.text(v.to_string()), Value::PlainSymbol(ref v) => pp.text(v.to_string()),
Value::Keyword(ref v) => pp.text(v.to_string()), Value::Keyword(ref v) => pp.text(v.to_string()),
Value::Text(ref v) => pp.text("\"").append(v.as_str()).append("\""), Value::Text(ref v) => pp.text("\"").append(v.as_str()).append("\""),
Value::Uuid(ref u) => pp.text("#uuid \"").append(u.hyphenated().to_string()).append("\""), Value::Uuid(ref u) => pp
Value::Instant(ref v) => pp.text("#inst \"").append(v.to_rfc3339_opts(SecondsFormat::AutoSi, true)).append("\""), .text("#uuid \"")
_ => pp.text(self.to_string()) .append(u.to_hyphenated().to_string())
.append("\""),
Value::Instant(ref v) => pp
.text("#inst \"")
.append(v.to_rfc3339_opts(SecondsFormat::AutoSi, true))
.append("\""),
_ => pp.text(self.to_string()),
} }
} }
} }
@ -105,13 +134,16 @@ mod test {
let data = parse::value(string).unwrap().without_spans(); let data = parse::value(string).unwrap().without_spans();
assert_eq!(data.to_pretty(20).unwrap(), "[1 2 3 4 5 6]"); assert_eq!(data.to_pretty(20).unwrap(), "[1 2 3 4 5 6]");
assert_eq!(data.to_pretty(10).unwrap(), "\ assert_eq!(
data.to_pretty(10).unwrap(),
"\
[1 [1
2 2
3 3
4 4
5 5
6]"); 6]"
);
} }
#[test] #[test]
@ -120,10 +152,13 @@ mod test {
let data = parse::value(string).unwrap().without_spans(); let data = parse::value(string).unwrap().without_spans();
assert_eq!(data.to_pretty(20).unwrap(), "{:a 1 :b 2 :c 3}"); assert_eq!(data.to_pretty(20).unwrap(), "{:a 1 :b 2 :c 3}");
assert_eq!(data.to_pretty(10).unwrap(), "\ assert_eq!(
data.to_pretty(10).unwrap(),
"\
{:a 1 {:a 1
:b 2 :b 2
:c 3}"); :c 3}"
);
} }
#[test] #[test]
@ -131,7 +166,9 @@ mod test {
let string = "[ 1 2 ( 3.14 ) #{ 4N } { foo/bar 42 :baz/boz 43 } [ ] :five :six/seven eight nine/ten true false nil #f NaN #f -Infinity #f +Infinity ]"; let string = "[ 1 2 ( 3.14 ) #{ 4N } { foo/bar 42 :baz/boz 43 } [ ] :five :six/seven eight nine/ten true false nil #f NaN #f -Infinity #f +Infinity ]";
let data = parse::value(string).unwrap().without_spans(); let data = parse::value(string).unwrap().without_spans();
assert_eq!(data.to_pretty(40).unwrap(), "\ assert_eq!(
data.to_pretty(40).unwrap(),
"\
[1 [1
2 2
(3.14) (3.14)
@ -147,7 +184,8 @@ mod test {
nil nil
#f NaN #f NaN
#f -Infinity #f -Infinity
#f +Infinity]"); #f +Infinity]"
);
} }
#[test] #[test]
@ -155,7 +193,9 @@ mod test {
let string = "[:find ?id ?bar ?baz :in $ :where [?id :session/keyword-foo ?symbol1 ?symbol2 \"some string\"] [?tx :db/tx ?ts]]"; let string = "[:find ?id ?bar ?baz :in $ :where [?id :session/keyword-foo ?symbol1 ?symbol2 \"some string\"] [?tx :db/tx ?ts]]";
let data = parse::value(string).unwrap().without_spans(); let data = parse::value(string).unwrap().without_spans();
assert_eq!(data.to_pretty(40).unwrap(), "\ assert_eq!(
data.to_pretty(40).unwrap(),
"\
[:find [:find
?id ?id
?bar ?bar
@ -168,7 +208,8 @@ mod test {
?symbol1 ?symbol1
?symbol2 ?symbol2
\"some string\"] \"some string\"]
[?tx :db/tx ?ts]]"); [?tx :db/tx ?ts]]"
);
} }
#[test] #[test]
@ -176,7 +217,9 @@ mod test {
let string = "[:find [?id ?bar ?baz] :in [$] :where [?id :session/keyword-foo ?symbol1 ?symbol2 \"some string\"] [?tx :db/tx ?ts] (not-join [?id] [?id :session/keyword-bar _])]"; let string = "[:find [?id ?bar ?baz] :in [$] :where [?id :session/keyword-foo ?symbol1 ?symbol2 \"some string\"] [?tx :db/tx ?ts] (not-join [?id] [?id :session/keyword-bar _])]";
let data = parse::value(string).unwrap().without_spans(); let data = parse::value(string).unwrap().without_spans();
assert_eq!(data.to_pretty(40).unwrap(), "\ assert_eq!(
data.to_pretty(40).unwrap(),
"\
[:find [:find
[?id ?bar ?baz] [?id ?bar ?baz]
:in :in
@ -190,6 +233,7 @@ mod test {
[?tx :db/tx ?ts] [?tx :db/tx ?ts]
(not-join (not-join
[?id] [?id]
[?id :session/keyword-bar _])]"); [?id :session/keyword-bar _])]"
);
} }
} }

View file

@ -29,37 +29,19 @@
///! inner type directly in conjunction with matching on the enum. Before diving ///! inner type directly in conjunction with matching on the enum. Before diving
///! deeply into this it's worth recognizing that this loss of 'sovereignty' is ///! deeply into this it's worth recognizing that this loss of 'sovereignty' is
///! a tradeoff against well-typed function signatures and other such boundaries. ///! a tradeoff against well-typed function signatures and other such boundaries.
use std::collections::{BTreeSet, HashSet};
use std::collections::{
BTreeSet,
HashSet,
};
use std; use std;
use std::fmt; use std::fmt;
use std::rc::{ use std::rc::Rc;
Rc,
};
use ::{ use {BigInt, DateTime, OrderedFloat, Utc, Uuid};
BigInt,
DateTime,
OrderedFloat,
Uuid,
Utc,
};
use ::value_rc::{ use value_rc::{FromRc, ValueRc};
FromRc,
ValueRc,
};
pub use ::{ pub use {Keyword, PlainSymbol};
Keyword,
PlainSymbol,
};
pub type SrcVarName = String; // Do not include the required syntactic '$'. pub type SrcVarName = String; // Do not include the required syntactic '$'.
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Variable(pub Rc<PlainSymbol>); pub struct Variable(pub Rc<PlainSymbol>);
@ -167,7 +149,7 @@ pub enum Direction {
/// An abstract declaration of ordering: direction and variable. /// An abstract declaration of ordering: direction and variable.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct Order(pub Direction, pub Variable); // Future: Element instead of Variable? pub struct Order(pub Direction, pub Variable); // Future: Element instead of Variable?
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum SrcVar { pub enum SrcVar {
@ -236,36 +218,26 @@ pub enum FnArg {
impl FromValue<FnArg> for FnArg { impl FromValue<FnArg> for FnArg {
fn from_value(v: &::ValueAndSpan) -> Option<FnArg> { fn from_value(v: &::ValueAndSpan) -> Option<FnArg> {
use ::SpannedValue::*; use SpannedValue::*;
match v.inner { match v.inner {
Integer(x) => Integer(x) => Some(FnArg::EntidOrInteger(x)),
Some(FnArg::EntidOrInteger(x)), PlainSymbol(ref x) if x.is_src_symbol() => SrcVar::from_symbol(x).map(FnArg::SrcVar),
PlainSymbol(ref x) if x.is_src_symbol() => PlainSymbol(ref x) if x.is_var_symbol() => {
SrcVar::from_symbol(x).map(FnArg::SrcVar), Variable::from_symbol(x).map(FnArg::Variable)
PlainSymbol(ref x) if x.is_var_symbol() => }
Variable::from_symbol(x).map(FnArg::Variable),
PlainSymbol(_) => None, PlainSymbol(_) => None,
Keyword(ref x) => Keyword(ref x) => Some(FnArg::IdentOrKeyword(x.clone())),
Some(FnArg::IdentOrKeyword(x.clone())), Instant(x) => Some(FnArg::Constant(NonIntegerConstant::Instant(x))),
Instant(x) => Uuid(x) => Some(FnArg::Constant(NonIntegerConstant::Uuid(x))),
Some(FnArg::Constant(NonIntegerConstant::Instant(x))), Boolean(x) => Some(FnArg::Constant(NonIntegerConstant::Boolean(x))),
Uuid(x) => Float(x) => Some(FnArg::Constant(NonIntegerConstant::Float(x))),
Some(FnArg::Constant(NonIntegerConstant::Uuid(x))), BigInteger(ref x) => Some(FnArg::Constant(NonIntegerConstant::BigInteger(x.clone()))),
Boolean(x) =>
Some(FnArg::Constant(NonIntegerConstant::Boolean(x))),
Float(x) =>
Some(FnArg::Constant(NonIntegerConstant::Float(x))),
BigInteger(ref x) =>
Some(FnArg::Constant(NonIntegerConstant::BigInteger(x.clone()))),
Text(ref x) => Text(ref x) =>
// TODO: intern strings. #398. // TODO: intern strings. #398.
Some(FnArg::Constant(x.clone().into())), {
Nil | Some(FnArg::Constant(x.clone().into()))
NamespacedSymbol(_) | }
Vector(_) | Nil | NamespacedSymbol(_) | Vector(_) | List(_) | Set(_) | Map(_) => None,
List(_) |
Set(_) |
Map(_) => None,
} }
} }
} }
@ -281,7 +253,7 @@ impl std::fmt::Display for FnArg {
} else { } else {
write!(f, "{:?}", var) write!(f, "{:?}", var)
} }
}, }
&FnArg::EntidOrInteger(entid) => write!(f, "{}", entid), &FnArg::EntidOrInteger(entid) => write!(f, "{}", entid),
&FnArg::IdentOrKeyword(ref kw) => write!(f, "{}", kw), &FnArg::IdentOrKeyword(ref kw) => write!(f, "{}", kw),
&FnArg::Constant(ref constant) => write!(f, "{:?}", constant), &FnArg::Constant(ref constant) => write!(f, "{:?}", constant),
@ -309,7 +281,7 @@ impl FnArg {
pub enum PatternNonValuePlace { pub enum PatternNonValuePlace {
Placeholder, Placeholder,
Variable(Variable), Variable(Variable),
Entid(i64), // Will always be +ve. See #190. Entid(i64), // Will always be +ve. See #190.
Ident(ValueRc<Keyword>), Ident(ValueRc<Keyword>),
} }
@ -332,17 +304,17 @@ impl PatternNonValuePlace {
match self { match self {
PatternNonValuePlace::Placeholder => PatternValuePlace::Placeholder, PatternNonValuePlace::Placeholder => PatternValuePlace::Placeholder,
PatternNonValuePlace::Variable(x) => PatternValuePlace::Variable(x), PatternNonValuePlace::Variable(x) => PatternValuePlace::Variable(x),
PatternNonValuePlace::Entid(x) => PatternValuePlace::EntidOrInteger(x), PatternNonValuePlace::Entid(x) => PatternValuePlace::EntidOrInteger(x),
PatternNonValuePlace::Ident(x) => PatternValuePlace::IdentOrKeyword(x), PatternNonValuePlace::Ident(x) => PatternValuePlace::IdentOrKeyword(x),
} }
} }
fn to_pattern_value_place(&self) -> PatternValuePlace { fn to_pattern_value_place(&self) -> PatternValuePlace {
match *self { match *self {
PatternNonValuePlace::Placeholder => PatternValuePlace::Placeholder, PatternNonValuePlace::Placeholder => PatternValuePlace::Placeholder,
PatternNonValuePlace::Variable(ref x) => PatternValuePlace::Variable(x.clone()), PatternNonValuePlace::Variable(ref x) => PatternValuePlace::Variable(x.clone()),
PatternNonValuePlace::Entid(x) => PatternValuePlace::EntidOrInteger(x), PatternNonValuePlace::Entid(x) => PatternValuePlace::EntidOrInteger(x),
PatternNonValuePlace::Ident(ref x) => PatternValuePlace::IdentOrKeyword(x.clone()), PatternNonValuePlace::Ident(ref x) => PatternValuePlace::IdentOrKeyword(x.clone()),
} }
} }
} }
@ -350,22 +322,25 @@ impl PatternNonValuePlace {
impl FromValue<PatternNonValuePlace> for PatternNonValuePlace { impl FromValue<PatternNonValuePlace> for PatternNonValuePlace {
fn from_value(v: &::ValueAndSpan) -> Option<PatternNonValuePlace> { fn from_value(v: &::ValueAndSpan) -> Option<PatternNonValuePlace> {
match v.inner { match v.inner {
::SpannedValue::Integer(x) => if x >= 0 { ::SpannedValue::Integer(x) => {
Some(PatternNonValuePlace::Entid(x)) if x >= 0 {
} else { Some(PatternNonValuePlace::Entid(x))
None
},
::SpannedValue::PlainSymbol(ref x) => if x.0.as_str() == "_" {
Some(PatternNonValuePlace::Placeholder)
} else {
if let Some(v) = Variable::from_symbol(x) {
Some(PatternNonValuePlace::Variable(v))
} else { } else {
None None
} }
}, }
::SpannedValue::Keyword(ref x) => ::SpannedValue::PlainSymbol(ref x) => {
Some(x.clone().into()), if x.0.as_str() == "_" {
Some(PatternNonValuePlace::Placeholder)
} else {
if let Some(v) = Variable::from_symbol(x) {
Some(PatternNonValuePlace::Variable(v))
} else {
None
}
}
}
::SpannedValue::Keyword(ref x) => Some(x.clone().into()),
_ => None, _ => None,
} }
} }
@ -404,32 +379,39 @@ impl From<Keyword> for PatternValuePlace {
impl FromValue<PatternValuePlace> for PatternValuePlace { impl FromValue<PatternValuePlace> for PatternValuePlace {
fn from_value(v: &::ValueAndSpan) -> Option<PatternValuePlace> { fn from_value(v: &::ValueAndSpan) -> Option<PatternValuePlace> {
match v.inner { match v.inner {
::SpannedValue::Integer(x) => ::SpannedValue::Integer(x) => Some(PatternValuePlace::EntidOrInteger(x)),
Some(PatternValuePlace::EntidOrInteger(x)), ::SpannedValue::PlainSymbol(ref x) if x.0.as_str() == "_" => {
::SpannedValue::PlainSymbol(ref x) if x.0.as_str() == "_" => Some(PatternValuePlace::Placeholder)
Some(PatternValuePlace::Placeholder), }
::SpannedValue::PlainSymbol(ref x) => ::SpannedValue::PlainSymbol(ref x) => {
Variable::from_symbol(x).map(PatternValuePlace::Variable), Variable::from_symbol(x).map(PatternValuePlace::Variable)
::SpannedValue::Keyword(ref x) if x.is_namespaced() => }
Some(x.clone().into()), ::SpannedValue::Keyword(ref x) if x.is_namespaced() => Some(x.clone().into()),
::SpannedValue::Boolean(x) => ::SpannedValue::Boolean(x) => {
Some(PatternValuePlace::Constant(NonIntegerConstant::Boolean(x))), Some(PatternValuePlace::Constant(NonIntegerConstant::Boolean(x)))
::SpannedValue::Float(x) => }
Some(PatternValuePlace::Constant(NonIntegerConstant::Float(x))), ::SpannedValue::Float(x) => {
::SpannedValue::BigInteger(ref x) => Some(PatternValuePlace::Constant(NonIntegerConstant::Float(x)))
Some(PatternValuePlace::Constant(NonIntegerConstant::BigInteger(x.clone()))), }
::SpannedValue::Instant(x) => ::SpannedValue::BigInteger(ref x) => Some(PatternValuePlace::Constant(
Some(PatternValuePlace::Constant(NonIntegerConstant::Instant(x))), NonIntegerConstant::BigInteger(x.clone()),
)),
::SpannedValue::Instant(x) => {
Some(PatternValuePlace::Constant(NonIntegerConstant::Instant(x)))
}
::SpannedValue::Text(ref x) => ::SpannedValue::Text(ref x) =>
// TODO: intern strings. #398. // TODO: intern strings. #398.
Some(PatternValuePlace::Constant(x.clone().into())), {
::SpannedValue::Uuid(ref u) => Some(PatternValuePlace::Constant(x.clone().into()))
Some(PatternValuePlace::Constant(NonIntegerConstant::Uuid(u.clone()))), }
::SpannedValue::Uuid(ref u) => Some(PatternValuePlace::Constant(
NonIntegerConstant::Uuid(u.clone()),
)),
// These don't appear in queries. // These don't appear in queries.
::SpannedValue::Nil => None, ::SpannedValue::Nil => None,
::SpannedValue::NamespacedSymbol(_) => None, ::SpannedValue::NamespacedSymbol(_) => None,
::SpannedValue::Keyword(_) => None, // … yet. ::SpannedValue::Keyword(_) => None, // … yet.
::SpannedValue::Map(_) => None, ::SpannedValue::Map(_) => None,
::SpannedValue::List(_) => None, ::SpannedValue::List(_) => None,
::SpannedValue::Set(_) => None, ::SpannedValue::Set(_) => None,
@ -443,29 +425,35 @@ impl PatternValuePlace {
#[allow(dead_code)] #[allow(dead_code)]
fn into_pattern_non_value_place(self) -> Option<PatternNonValuePlace> { fn into_pattern_non_value_place(self) -> Option<PatternNonValuePlace> {
match self { match self {
PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder), PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder),
PatternValuePlace::Variable(x) => Some(PatternNonValuePlace::Variable(x)), PatternValuePlace::Variable(x) => Some(PatternNonValuePlace::Variable(x)),
PatternValuePlace::EntidOrInteger(x) => if x >= 0 { PatternValuePlace::EntidOrInteger(x) => {
Some(PatternNonValuePlace::Entid(x)) if x >= 0 {
} else { Some(PatternNonValuePlace::Entid(x))
None } else {
}, None
}
}
PatternValuePlace::IdentOrKeyword(x) => Some(PatternNonValuePlace::Ident(x)), PatternValuePlace::IdentOrKeyword(x) => Some(PatternNonValuePlace::Ident(x)),
PatternValuePlace::Constant(_) => None, PatternValuePlace::Constant(_) => None,
} }
} }
fn to_pattern_non_value_place(&self) -> Option<PatternNonValuePlace> { fn to_pattern_non_value_place(&self) -> Option<PatternNonValuePlace> {
match *self { match *self {
PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder), PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder),
PatternValuePlace::Variable(ref x) => Some(PatternNonValuePlace::Variable(x.clone())), PatternValuePlace::Variable(ref x) => Some(PatternNonValuePlace::Variable(x.clone())),
PatternValuePlace::EntidOrInteger(x) => if x >= 0 { PatternValuePlace::EntidOrInteger(x) => {
Some(PatternNonValuePlace::Entid(x)) if x >= 0 {
} else { Some(PatternNonValuePlace::Entid(x))
None } else {
}, None
PatternValuePlace::IdentOrKeyword(ref x) => Some(PatternNonValuePlace::Ident(x.clone())), }
PatternValuePlace::Constant(_) => None, }
PatternValuePlace::IdentOrKeyword(ref x) => {
Some(PatternNonValuePlace::Ident(x.clone()))
}
PatternValuePlace::Constant(_) => None,
} }
} }
} }
@ -510,12 +498,8 @@ pub enum PullAttributeSpec {
impl std::fmt::Display for PullConcreteAttribute { impl std::fmt::Display for PullConcreteAttribute {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
&PullConcreteAttribute::Ident(ref k) => { &PullConcreteAttribute::Ident(ref k) => write!(f, "{}", k),
write!(f, "{}", k) &PullConcreteAttribute::Entid(i) => write!(f, "{}", i),
},
&PullConcreteAttribute::Entid(i) => {
write!(f, "{}", i)
},
} }
} }
} }
@ -530,21 +514,15 @@ impl std::fmt::Display for NamedPullAttribute {
} }
} }
impl std::fmt::Display for PullAttributeSpec { impl std::fmt::Display for PullAttributeSpec {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
&PullAttributeSpec::Wildcard => { &PullAttributeSpec::Wildcard => write!(f, "*"),
write!(f, "*") &PullAttributeSpec::Attribute(ref attr) => write!(f, "{}", attr),
},
&PullAttributeSpec::Attribute(ref attr) => {
write!(f, "{}", attr)
},
} }
} }
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct Pull { pub struct Pull {
pub var: Variable, pub var: Variable,
@ -592,26 +570,23 @@ impl From<Variable> for Element {
impl std::fmt::Display for Element { impl std::fmt::Display for Element {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
&Element::Variable(ref var) => { &Element::Variable(ref var) => write!(f, "{}", var),
write!(f, "{}", var) &Element::Pull(Pull {
}, ref var,
&Element::Pull(Pull { ref var, ref patterns }) => { ref patterns,
}) => {
write!(f, "(pull {} [ ", var)?; write!(f, "(pull {} [ ", var)?;
for p in patterns.iter() { for p in patterns.iter() {
write!(f, "{} ", p)?; write!(f, "{} ", p)?;
} }
write!(f, "])") write!(f, "])")
}
&Element::Aggregate(ref agg) => match agg.args.len() {
0 => write!(f, "({})", agg.func),
1 => write!(f, "({} {})", agg.func, agg.args[0]),
_ => write!(f, "({} {:?})", agg.func, agg.args),
}, },
&Element::Aggregate(ref agg) => { &Element::Corresponding(ref var) => write!(f, "(the {})", var),
match agg.args.len() {
0 => write!(f, "({})", agg.func),
1 => write!(f, "({} {})", agg.func, agg.args[0]),
_ => write!(f, "({} {:?})", agg.func, agg.args),
}
},
&Element::Corresponding(ref var) => {
write!(f, "(the {})", var)
},
} }
} }
} }
@ -675,9 +650,9 @@ impl FindSpec {
use self::FindSpec::*; use self::FindSpec::*;
match self { match self {
&FindScalar(..) => true, &FindScalar(..) => true,
&FindTuple(..) => true, &FindTuple(..) => true,
&FindRel(..) => false, &FindRel(..) => false,
&FindColl(..) => false, &FindColl(..) => false,
} }
} }
@ -685,12 +660,11 @@ impl FindSpec {
use self::FindSpec::*; use self::FindSpec::*;
match self { match self {
&FindScalar(..) => 1, &FindScalar(..) => 1,
&FindColl(..) => 1, &FindColl(..) => 1,
&FindTuple(ref elems) | &FindRel(ref elems) => elems.len(), &FindTuple(ref elems) | &FindRel(ref elems) => elems.len(),
} }
} }
/// Returns true if the provided `FindSpec` cares about distinct results. /// Returns true if the provided `FindSpec` cares about distinct results.
/// ///
/// I use the words "cares about" because find is generally defined in terms of producing distinct /// I use the words "cares about" because find is generally defined in terms of producing distinct
@ -713,13 +687,13 @@ impl FindSpec {
!self.is_unit_limited() !self.is_unit_limited()
} }
pub fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> { pub fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
use self::FindSpec::*; use self::FindSpec::*;
match self { match self {
&FindScalar(ref e) => Box::new(std::iter::once(e)), &FindScalar(ref e) => Box::new(std::iter::once(e)),
&FindColl(ref e) => Box::new(std::iter::once(e)), &FindColl(ref e) => Box::new(std::iter::once(e)),
&FindTuple(ref v) => Box::new(v.iter()), &FindTuple(ref v) => Box::new(v.iter()),
&FindRel(ref v) => Box::new(v.iter()), &FindRel(ref v) => Box::new(v.iter()),
} }
} }
} }
@ -748,7 +722,7 @@ impl VariableOrPlaceholder {
} }
} }
#[derive(Clone,Debug,Eq,PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum Binding { pub enum Binding {
BindScalar(Variable), BindScalar(Variable),
BindColl(Variable), BindColl(Variable),
@ -761,7 +735,9 @@ impl Binding {
pub fn variables(&self) -> Vec<Option<Variable>> { pub fn variables(&self) -> Vec<Option<Variable>> {
match self { match self {
&Binding::BindScalar(ref var) | &Binding::BindColl(ref var) => vec![Some(var.clone())], &Binding::BindScalar(ref var) | &Binding::BindColl(ref var) => vec![Some(var.clone())],
&Binding::BindRel(ref vars) | &Binding::BindTuple(ref vars) => vars.iter().map(|x| x.var().cloned()).collect(), &Binding::BindRel(ref vars) | &Binding::BindTuple(ref vars) => {
vars.iter().map(|x| x.var().cloned()).collect()
}
} }
} }
@ -769,7 +745,9 @@ impl Binding {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
match self { match self {
&Binding::BindScalar(_) | &Binding::BindColl(_) => false, &Binding::BindScalar(_) | &Binding::BindColl(_) => false,
&Binding::BindRel(ref vars) | &Binding::BindTuple(ref vars) => vars.iter().all(|x| x.var().is_none()), &Binding::BindRel(ref vars) | &Binding::BindTuple(ref vars) => {
vars.iter().all(|x| x.var().is_none())
}
} }
} }
@ -826,18 +804,22 @@ pub struct Pattern {
} }
impl Pattern { impl Pattern {
pub fn simple(e: PatternNonValuePlace, pub fn simple(
a: PatternNonValuePlace, e: PatternNonValuePlace,
v: PatternValuePlace) -> Option<Pattern> { a: PatternNonValuePlace,
v: PatternValuePlace,
) -> Option<Pattern> {
Pattern::new(None, e, a, v, PatternNonValuePlace::Placeholder) Pattern::new(None, e, a, v, PatternNonValuePlace::Placeholder)
} }
pub fn new(src: Option<SrcVar>, pub fn new(
e: PatternNonValuePlace, src: Option<SrcVar>,
a: PatternNonValuePlace, e: PatternNonValuePlace,
v: PatternValuePlace, a: PatternNonValuePlace,
tx: PatternNonValuePlace) -> Option<Pattern> { v: PatternValuePlace,
let aa = a.clone(); // Too tired of fighting borrow scope for now. tx: PatternNonValuePlace,
) -> Option<Pattern> {
let aa = a.clone(); // Too tired of fighting borrow scope for now.
if let PatternNonValuePlace::Ident(ref k) = aa { if let PatternNonValuePlace::Ident(ref k) = aa {
if k.is_backward() { if k.is_backward() {
// e and v have different types; we must convert them. // e and v have different types; we must convert them.
@ -1005,7 +987,9 @@ pub(crate) enum QueryPart {
/// We split `ParsedQuery` from `FindQuery` because it's not easy to generalize over containers /// We split `ParsedQuery` from `FindQuery` because it's not easy to generalize over containers
/// (here, `Vec` and `BTreeSet`) in Rust. /// (here, `Vec` and `BTreeSet`) in Rust.
impl ParsedQuery { impl ParsedQuery {
pub(crate) fn from_parts(parts: Vec<QueryPart>) -> std::result::Result<ParsedQuery, &'static str> { pub(crate) fn from_parts(
parts: Vec<QueryPart>,
) -> std::result::Result<ParsedQuery, &'static str> {
let mut find_spec: Option<FindSpec> = None; let mut find_spec: Option<FindSpec> = None;
let mut with: Option<Vec<Variable>> = None; let mut with: Option<Vec<Variable>> = None;
let mut in_vars: Option<Vec<Variable>> = None; let mut in_vars: Option<Vec<Variable>> = None;
@ -1020,37 +1004,37 @@ impl ParsedQuery {
return Err("find query has repeated :find"); return Err("find query has repeated :find");
} }
find_spec = Some(x) find_spec = Some(x)
}, }
QueryPart::WithVars(x) => { QueryPart::WithVars(x) => {
if with.is_some() { if with.is_some() {
return Err("find query has repeated :with"); return Err("find query has repeated :with");
} }
with = Some(x) with = Some(x)
}, }
QueryPart::InVars(x) => { QueryPart::InVars(x) => {
if in_vars.is_some() { if in_vars.is_some() {
return Err("find query has repeated :in"); return Err("find query has repeated :in");
} }
in_vars = Some(x) in_vars = Some(x)
}, }
QueryPart::Limit(x) => { QueryPart::Limit(x) => {
if limit.is_some() { if limit.is_some() {
return Err("find query has repeated :limit"); return Err("find query has repeated :limit");
} }
limit = Some(x) limit = Some(x)
}, }
QueryPart::WhereClauses(x) => { QueryPart::WhereClauses(x) => {
if where_clauses.is_some() { if where_clauses.is_some() {
return Err("find query has repeated :where"); return Err("find query has repeated :where");
} }
where_clauses = Some(x) where_clauses = Some(x)
}, }
QueryPart::Order(x) => { QueryPart::Order(x) => {
if order.is_some() { if order.is_some() {
return Err("find query has repeated :order"); return Err("find query has repeated :order");
} }
order = Some(x) order = Some(x)
}, }
} }
} }
@ -1110,13 +1094,13 @@ impl ContainsVariables for WhereClause {
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) { fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
use self::WhereClause::*; use self::WhereClause::*;
match self { match self {
&OrJoin(ref o) => o.accumulate_mentioned_variables(acc), &OrJoin(ref o) => o.accumulate_mentioned_variables(acc),
&Pred(ref p) => p.accumulate_mentioned_variables(acc), &Pred(ref p) => p.accumulate_mentioned_variables(acc),
&Pattern(ref p) => p.accumulate_mentioned_variables(acc), &Pattern(ref p) => p.accumulate_mentioned_variables(acc),
&NotJoin(ref n) => n.accumulate_mentioned_variables(acc), &NotJoin(ref n) => n.accumulate_mentioned_variables(acc),
&WhereFn(ref f) => f.accumulate_mentioned_variables(acc), &WhereFn(ref f) => f.accumulate_mentioned_variables(acc),
&TypeAnnotation(ref a) => a.accumulate_mentioned_variables(acc), &TypeAnnotation(ref a) => a.accumulate_mentioned_variables(acc),
&RuleExpr => (), &RuleExpr => (),
} }
} }
} }
@ -1125,7 +1109,11 @@ impl ContainsVariables for OrWhereClause {
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) { fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
use self::OrWhereClause::*; use self::OrWhereClause::*;
match self { match self {
&And(ref clauses) => for clause in clauses { clause.accumulate_mentioned_variables(acc) }, &And(ref clauses) => {
for clause in clauses {
clause.accumulate_mentioned_variables(acc)
}
}
&Clause(ref clause) => clause.accumulate_mentioned_variables(acc), &Clause(ref clause) => clause.accumulate_mentioned_variables(acc),
} }
} }
@ -1142,9 +1130,9 @@ impl ContainsVariables for OrJoin {
impl OrJoin { impl OrJoin {
pub fn dismember(self) -> (Vec<OrWhereClause>, UnifyVars, BTreeSet<Variable>) { pub fn dismember(self) -> (Vec<OrWhereClause>, UnifyVars, BTreeSet<Variable>) {
let vars = match self.mentioned_vars { let vars = match self.mentioned_vars {
Some(m) => m, Some(m) => m,
None => self.collect_mentioned_variables(), None => self.collect_mentioned_variables(),
}; };
(self.clauses, self.unify_vars, vars) (self.clauses, self.unify_vars, vars)
} }
@ -1189,16 +1177,14 @@ impl ContainsVariables for TypeAnnotation {
impl ContainsVariables for Binding { impl ContainsVariables for Binding {
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) { fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
match self { match self {
&Binding::BindScalar(ref v) | &Binding::BindColl(ref v) => { &Binding::BindScalar(ref v) | &Binding::BindColl(ref v) => acc_ref(acc, v),
acc_ref(acc, v)
},
&Binding::BindRel(ref vs) | &Binding::BindTuple(ref vs) => { &Binding::BindRel(ref vs) | &Binding::BindTuple(ref vs) => {
for v in vs { for v in vs {
if let &VariableOrPlaceholder::Variable(ref v) = v { if let &VariableOrPlaceholder::Variable(ref v) = v {
acc_ref(acc, v); acc_ref(acc, v);
} }
} }
}, }
} }
} }
} }

View file

@ -8,11 +8,7 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use std::fmt::{ use std::fmt::{Display, Formatter, Write};
Display,
Formatter,
Write,
};
use namespaceable_name::NamespaceableName; use namespaceable_name::NamespaceableName;
@ -20,14 +16,14 @@ use namespaceable_name::NamespaceableName;
macro_rules! ns_keyword { macro_rules! ns_keyword {
($ns: expr, $name: expr) => {{ ($ns: expr, $name: expr) => {{
$crate::Keyword::namespaced($ns, $name) $crate::Keyword::namespaced($ns, $name)
}} }};
} }
/// A simplification of Clojure's Symbol. /// A simplification of Clojure's Symbol.
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct PlainSymbol(pub String); pub struct PlainSymbol(pub String);
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct NamespacedSymbol(NamespaceableName); pub struct NamespacedSymbol(NamespaceableName);
/// A keyword is a symbol, optionally with a namespace, that prints with a leading colon. /// A keyword is a symbol, optionally with a namespace, that prints with a leading colon.
@ -67,12 +63,15 @@ pub struct NamespacedSymbol(NamespaceableName);
/// ///
/// Future: fast equality (interning?) for keywords. /// Future: fast equality (interning?) for keywords.
/// ///
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Keyword(NamespaceableName); pub struct Keyword(NamespaceableName);
impl PlainSymbol { impl PlainSymbol {
pub fn plain<T>(name: T) -> Self where T: Into<String> { pub fn plain<T>(name: T) -> Self
where
T: Into<String>,
{
let n = name.into(); let n = name.into();
assert!(!n.is_empty(), "Symbols cannot be unnamed."); assert!(!n.is_empty(), "Symbols cannot be unnamed.");
@ -107,9 +106,16 @@ impl PlainSymbol {
} }
impl NamespacedSymbol { impl NamespacedSymbol {
pub fn namespaced<N, T>(namespace: N, name: T) -> Self where N: AsRef<str>, T: AsRef<str> { pub fn namespaced<N, T>(namespace: N, name: T) -> Self
where
N: AsRef<str>,
T: AsRef<str>,
{
let r = namespace.as_ref(); let r = namespace.as_ref();
assert!(!r.is_empty(), "Namespaced symbols cannot have an empty non-null namespace."); assert!(
!r.is_empty(),
"Namespaced symbols cannot have an empty non-null namespace."
);
NamespacedSymbol(NamespaceableName::namespaced(r, name)) NamespacedSymbol(NamespaceableName::namespaced(r, name))
} }
@ -130,7 +136,10 @@ impl NamespacedSymbol {
} }
impl Keyword { impl Keyword {
pub fn plain<T>(name: T) -> Self where T: Into<String> { pub fn plain<T>(name: T) -> Self
where
T: Into<String>,
{
Keyword(NamespaceableName::plain(name)) Keyword(NamespaceableName::plain(name))
} }
} }
@ -147,9 +156,16 @@ impl Keyword {
/// ``` /// ```
/// ///
/// See also the `kw!` macro in the main `mentat` crate. /// See also the `kw!` macro in the main `mentat` crate.
pub fn namespaced<N, T>(namespace: N, name: T) -> Self where N: AsRef<str>, T: AsRef<str> { pub fn namespaced<N, T>(namespace: N, name: T) -> Self
where
N: AsRef<str>,
T: AsRef<str>,
{
let r = namespace.as_ref(); let r = namespace.as_ref();
assert!(!r.is_empty(), "Namespaced keywords cannot have an empty non-null namespace."); assert!(
!r.is_empty(),
"Namespaced keywords cannot have an empty non-null namespace."
);
Keyword(NamespaceableName::namespaced(r, name)) Keyword(NamespaceableName::namespaced(r, name))
} }
@ -307,8 +323,12 @@ impl Display for Keyword {
#[test] #[test]
fn test_ns_keyword_macro() { fn test_ns_keyword_macro() {
assert_eq!(ns_keyword!("test", "name").to_string(), assert_eq!(
Keyword::namespaced("test", "name").to_string()); ns_keyword!("test", "name").to_string(),
assert_eq!(ns_keyword!("ns", "_name").to_string(), Keyword::namespaced("test", "name").to_string()
Keyword::namespaced("ns", "_name").to_string()); );
assert_eq!(
ns_keyword!("ns", "_name").to_string(),
Keyword::namespaced("ns", "_name").to_string()
);
} }

View file

@ -8,17 +8,17 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
#![cfg_attr(feature = "cargo-clippy", allow(linkedlist))] #![allow(redundant_semicolon)]
use std::collections::{BTreeSet, BTreeMap, LinkedList}; use std::cmp::{Ord, Ordering, PartialOrd};
use std::cmp::{Ordering, Ord, PartialOrd}; use std::collections::{BTreeMap, BTreeSet, LinkedList};
use std::fmt::{Display, Formatter};
use std::f64; use std::f64;
use std::fmt::{Display, Formatter};
use chrono::{ use chrono::{
DateTime, DateTime,
SecondsFormat, SecondsFormat,
TimeZone, // For Utc::timestamp. The compiler incorrectly complains that this is unused. TimeZone, // For Utc::timestamp. The compiler incorrectly complains that this is unused.
Utc, Utc,
}; };
use num::BigInt; use num::BigInt;
@ -94,7 +94,10 @@ pub struct ValueAndSpan {
} }
impl ValueAndSpan { impl ValueAndSpan {
pub fn new<I>(spanned_value: SpannedValue, span: I) -> ValueAndSpan where I: Into<Option<Span>> { pub fn new<I>(spanned_value: SpannedValue, span: I) -> ValueAndSpan
where
I: Into<Option<Span>>,
{
ValueAndSpan { ValueAndSpan {
inner: spanned_value, inner: spanned_value,
span: span.into().unwrap_or(Span(0, 0)), // TODO: consider if this has implications. span: span.into().unwrap_or(Span(0, 0)), // TODO: consider if this has implications.
@ -136,7 +139,7 @@ impl Value {
/// But right now, it's used in the bootstrapper. We'll fix that soon. /// But right now, it's used in the bootstrapper. We'll fix that soon.
pub fn with_spans(self) -> ValueAndSpan { pub fn with_spans(self) -> ValueAndSpan {
let s = self.to_pretty(120).unwrap(); let s = self.to_pretty(120).unwrap();
use ::parse; use parse;
let with_spans = parse::value(&s).unwrap(); let with_spans = parse::value(&s).unwrap();
assert_eq!(self, with_spans.clone().without_spans()); assert_eq!(self, with_spans.clone().without_spans());
with_spans with_spans
@ -157,10 +160,18 @@ impl From<SpannedValue> for Value {
SpannedValue::PlainSymbol(v) => Value::PlainSymbol(v), SpannedValue::PlainSymbol(v) => Value::PlainSymbol(v),
SpannedValue::NamespacedSymbol(v) => Value::NamespacedSymbol(v), SpannedValue::NamespacedSymbol(v) => Value::NamespacedSymbol(v),
SpannedValue::Keyword(v) => Value::Keyword(v), SpannedValue::Keyword(v) => Value::Keyword(v),
SpannedValue::Vector(v) => Value::Vector(v.into_iter().map(|x| x.without_spans()).collect()), SpannedValue::Vector(v) => {
SpannedValue::List(v) => Value::List(v.into_iter().map(|x| x.without_spans()).collect()), Value::Vector(v.into_iter().map(|x| x.without_spans()).collect())
}
SpannedValue::List(v) => {
Value::List(v.into_iter().map(|x| x.without_spans()).collect())
}
SpannedValue::Set(v) => Value::Set(v.into_iter().map(|x| x.without_spans()).collect()), SpannedValue::Set(v) => Value::Set(v.into_iter().map(|x| x.without_spans()).collect()),
SpannedValue::Map(v) => Value::Map(v.into_iter().map(|(x, y)| (x.without_spans(), y.without_spans())).collect()), SpannedValue::Map(v) => Value::Map(
v.into_iter()
.map(|(x, y)| (x.without_spans(), y.without_spans()))
.collect(),
),
} }
} }
} }
@ -261,8 +272,9 @@ macro_rules! to_symbol {
( $namespace:expr, $name:expr, $t:tt ) => { ( $namespace:expr, $name:expr, $t:tt ) => {
$namespace.into().map_or_else( $namespace.into().map_or_else(
|| $t::PlainSymbol(symbols::PlainSymbol::plain($name)), || $t::PlainSymbol(symbols::PlainSymbol::plain($name)),
|ns| $t::NamespacedSymbol(symbols::NamespacedSymbol::namespaced(ns, $name))) |ns| $t::NamespacedSymbol(symbols::NamespacedSymbol::namespaced(ns, $name)),
} )
};
} }
/// Converts `name` into a plain or namespaced value keyword, depending on /// Converts `name` into a plain or namespaced value keyword, depending on
@ -290,8 +302,9 @@ macro_rules! to_keyword {
( $namespace:expr, $name:expr, $t:tt ) => { ( $namespace:expr, $name:expr, $t:tt ) => {
$namespace.into().map_or_else( $namespace.into().map_or_else(
|| $t::Keyword(symbols::Keyword::plain($name)), || $t::Keyword(symbols::Keyword::plain($name)),
|ns| $t::Keyword(symbols::Keyword::namespaced(ns, $name))) |ns| $t::Keyword(symbols::Keyword::namespaced(ns, $name)),
} )
};
} }
/// Implements multiple is*, as*, into* and from* methods common to /// Implements multiple is*, as*, into* and from* methods common to
@ -508,9 +521,9 @@ macro_rules! def_common_value_ord {
(&$t::List(ref a), &$t::List(ref b)) => b.cmp(a), (&$t::List(ref a), &$t::List(ref b)) => b.cmp(a),
(&$t::Set(ref a), &$t::Set(ref b)) => b.cmp(a), (&$t::Set(ref a), &$t::Set(ref b)) => b.cmp(a),
(&$t::Map(ref a), &$t::Map(ref b)) => b.cmp(a), (&$t::Map(ref a), &$t::Map(ref b)) => b.cmp(a),
_ => $value.precedence().cmp(&$other.precedence()) _ => $value.precedence().cmp(&$other.precedence()),
} }
} };
} }
/// Converts a Value or SpannedValue to string, given a formatter. /// Converts a Value or SpannedValue to string, given a formatter.
@ -522,7 +535,11 @@ macro_rules! def_common_value_display {
$t::Nil => write!($f, "nil"), $t::Nil => write!($f, "nil"),
$t::Boolean(v) => write!($f, "{}", v), $t::Boolean(v) => write!($f, "{}", v),
$t::Integer(v) => write!($f, "{}", v), $t::Integer(v) => write!($f, "{}", v),
$t::Instant(v) => write!($f, "#inst \"{}\"", v.to_rfc3339_opts(SecondsFormat::AutoSi, true)), $t::Instant(v) => write!(
$f,
"#inst \"{}\"",
v.to_rfc3339_opts(SecondsFormat::AutoSi, true)
),
$t::BigInteger(ref v) => write!($f, "{}N", v), $t::BigInteger(ref v) => write!($f, "{}N", v),
// TODO: make sure float syntax is correct. // TODO: make sure float syntax is correct.
$t::Float(ref v) => { $t::Float(ref v) => {
@ -538,7 +555,7 @@ macro_rules! def_common_value_display {
} }
// TODO: EDN escaping. // TODO: EDN escaping.
$t::Text(ref v) => write!($f, "\"{}\"", v), $t::Text(ref v) => write!($f, "\"{}\"", v),
$t::Uuid(ref u) => write!($f, "#uuid \"{}\"", u.hyphenated().to_string()), $t::Uuid(ref u) => write!($f, "#uuid \"{}\"", u.to_hyphenated().to_string()),
$t::PlainSymbol(ref v) => v.fmt($f), $t::PlainSymbol(ref v) => v.fmt($f),
$t::NamespacedSymbol(ref v) => v.fmt($f), $t::NamespacedSymbol(ref v) => v.fmt($f),
$t::Keyword(ref v) => v.fmt($f), $t::Keyword(ref v) => v.fmt($f),
@ -571,7 +588,7 @@ macro_rules! def_common_value_display {
write!($f, " }}") write!($f, " }}")
} }
} }
} };
} }
macro_rules! def_common_value_impl { macro_rules! def_common_value_impl {
@ -597,7 +614,7 @@ macro_rules! def_common_value_impl {
def_common_value_display!($t, self, f) def_common_value_display!($t, self, f)
} }
} }
} };
} }
def_common_value_impl!(Value<Value>); def_common_value_impl!(Value<Value>);
@ -674,22 +691,19 @@ impl ToMillis for DateTime<Utc> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
extern crate chrono; extern crate chrono;
extern crate ordered_float;
extern crate num; extern crate num;
extern crate ordered_float;
use super::*; use super::*;
use std::collections::{BTreeSet, BTreeMap, LinkedList}; use std::cmp::Ordering;
use std::cmp::{Ordering}; use std::collections::{BTreeMap, BTreeSet, LinkedList};
use std::iter::FromIterator;
use std::f64; use std::f64;
use std::iter::FromIterator;
use parse; use parse;
use chrono::{ use chrono::{DateTime, Utc};
DateTime,
Utc,
};
use num::BigInt; use num::BigInt;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
@ -702,9 +716,18 @@ mod test {
#[test] #[test]
fn test_value_from() { fn test_value_from() {
assert_eq!(Value::from_float(42f64), Value::Float(OrderedFloat::from(42f64))); assert_eq!(
assert_eq!(Value::from_ordered_float(OrderedFloat::from(42f64)), Value::Float(OrderedFloat::from(42f64))); Value::from_float(42f64),
assert_eq!(Value::from_bigint("42").unwrap(), Value::BigInteger(BigInt::from(42))); Value::Float(OrderedFloat::from(42f64))
);
assert_eq!(
Value::from_ordered_float(OrderedFloat::from(42f64)),
Value::Float(OrderedFloat::from(42f64))
);
assert_eq!(
Value::from_bigint("42").unwrap(),
Value::BigInteger(BigInt::from(42))
);
} }
#[test] #[test]
@ -716,15 +739,11 @@ mod test {
let data = Value::Vector(vec![ let data = Value::Vector(vec![
Value::Integer(1), Value::Integer(1),
Value::Integer(2), Value::Integer(2),
Value::List(LinkedList::from_iter(vec![ Value::List(LinkedList::from_iter(vec![Value::from_float(3.14)])),
Value::from_float(3.14) Value::Set(BTreeSet::from_iter(vec![Value::from_bigint("4").unwrap()])),
])),
Value::Set(BTreeSet::from_iter(vec![
Value::from_bigint("4").unwrap()
])),
Value::Map(BTreeMap::from_iter(vec![ Value::Map(BTreeMap::from_iter(vec![
(Value::from_symbol("foo", "bar"), Value::Integer(42)), (Value::from_symbol("foo", "bar"), Value::Integer(42)),
(Value::from_keyword("baz", "boz"), Value::Integer(43)) (Value::from_keyword("baz", "boz"), Value::Integer(43)),
])), ])),
Value::Vector(vec![]), Value::Vector(vec![]),
Value::from_keyword(None, "five"), Value::from_keyword(None, "five"),
@ -741,26 +760,68 @@ mod test {
assert_eq!(string, data.to_string()); assert_eq!(string, data.to_string());
assert_eq!(string, parse::value(&data.to_string()).unwrap().to_string()); assert_eq!(string, parse::value(&data.to_string()).unwrap().to_string());
assert_eq!(string, parse::value(&data.to_string()).unwrap().without_spans().to_string()); assert_eq!(
string,
parse::value(&data.to_string())
.unwrap()
.without_spans()
.to_string()
);
} }
#[test] #[test]
fn test_ord() { fn test_ord() {
// TODO: Check we follow the equality rules at the bottom of https://github.com/edn-format/edn // TODO: Check we follow the equality rules at the bottom of https://github.com/edn-format/edn
assert_eq!(Value::Nil.cmp(&Value::Nil), Ordering::Equal); assert_eq!(Value::Nil.cmp(&Value::Nil), Ordering::Equal);
assert_eq!(Value::Boolean(false).cmp(&Value::Boolean(true)), Ordering::Greater); assert_eq!(
Value::Boolean(false).cmp(&Value::Boolean(true)),
Ordering::Greater
);
assert_eq!(Value::Integer(1).cmp(&Value::Integer(2)), Ordering::Greater); assert_eq!(Value::Integer(1).cmp(&Value::Integer(2)), Ordering::Greater);
assert_eq!(Value::from_bigint("1").cmp(&Value::from_bigint("2")), Ordering::Greater); assert_eq!(
assert_eq!(Value::from_float(1f64).cmp(&Value::from_float(2f64)), Ordering::Greater); Value::from_bigint("1").cmp(&Value::from_bigint("2")),
assert_eq!(Value::Text("1".to_string()).cmp(&Value::Text("2".to_string())), Ordering::Greater); Ordering::Greater
assert_eq!(Value::from_symbol("a", "b").cmp(&Value::from_symbol("c", "d")), Ordering::Greater); );
assert_eq!(Value::from_symbol(None, "a").cmp(&Value::from_symbol(None, "b")), Ordering::Greater); assert_eq!(
assert_eq!(Value::from_keyword(":a", ":b").cmp(&Value::from_keyword(":c", ":d")), Ordering::Greater); Value::from_float(1f64).cmp(&Value::from_float(2f64)),
assert_eq!(Value::from_keyword(None, ":a").cmp(&Value::from_keyword(None, ":b")), Ordering::Greater); Ordering::Greater
assert_eq!(Value::Vector(vec![]).cmp(&Value::Vector(vec![])), Ordering::Equal); );
assert_eq!(Value::List(LinkedList::new()).cmp(&Value::List(LinkedList::new())), Ordering::Equal); assert_eq!(
assert_eq!(Value::Set(BTreeSet::new()).cmp(&Value::Set(BTreeSet::new())), Ordering::Equal); Value::Text("1".to_string()).cmp(&Value::Text("2".to_string())),
assert_eq!(Value::Map(BTreeMap::new()).cmp(&Value::Map(BTreeMap::new())), Ordering::Equal); Ordering::Greater
);
assert_eq!(
Value::from_symbol("a", "b").cmp(&Value::from_symbol("c", "d")),
Ordering::Greater
);
assert_eq!(
Value::from_symbol(None, "a").cmp(&Value::from_symbol(None, "b")),
Ordering::Greater
);
assert_eq!(
Value::from_keyword(":a", ":b").cmp(&Value::from_keyword(":c", ":d")),
Ordering::Greater
);
assert_eq!(
Value::from_keyword(None, ":a").cmp(&Value::from_keyword(None, ":b")),
Ordering::Greater
);
assert_eq!(
Value::Vector(vec![]).cmp(&Value::Vector(vec![])),
Ordering::Equal
);
assert_eq!(
Value::List(LinkedList::new()).cmp(&Value::List(LinkedList::new())),
Ordering::Equal
);
assert_eq!(
Value::Set(BTreeSet::new()).cmp(&Value::Set(BTreeSet::new())),
Ordering::Equal
);
assert_eq!(
Value::Map(BTreeMap::new()).cmp(&Value::Map(BTreeMap::new())),
Ordering::Equal
);
} }
#[test] #[test]

View file

@ -26,6 +26,6 @@ pub fn merge(left: &Value, right: &Value) -> Option<Value> {
result.extend(r.clone().into_iter()); result.extend(r.clone().into_iter());
Some(Value::Map(result)) Some(Value::Map(result))
} }
_ => None _ => None,
} }
} }

View file

@ -8,20 +8,19 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use ::std::rc::{ use std::rc::Rc;
Rc,
};
use ::std::sync::{ use std::sync::Arc;
Arc,
};
pub trait FromRc<T> { pub trait FromRc<T> {
fn from_rc(val: Rc<T>) -> Self; fn from_rc(val: Rc<T>) -> Self;
fn from_arc(val: Arc<T>) -> Self; fn from_arc(val: Arc<T>) -> Self;
} }
impl<T> FromRc<T> for Rc<T> where T: Sized + Clone { impl<T> FromRc<T> for Rc<T>
where
T: Sized + Clone,
{
fn from_rc(val: Rc<T>) -> Self { fn from_rc(val: Rc<T>) -> Self {
val.clone() val.clone()
} }
@ -34,7 +33,10 @@ impl<T> FromRc<T> for Rc<T> where T: Sized + Clone {
} }
} }
impl<T> FromRc<T> for Arc<T> where T: Sized + Clone { impl<T> FromRc<T> for Arc<T>
where
T: Sized + Clone,
{
fn from_rc(val: Rc<T>) -> Self { fn from_rc(val: Rc<T>) -> Self {
match ::std::rc::Rc::<T>::try_unwrap(val) { match ::std::rc::Rc::<T>::try_unwrap(val) {
Ok(v) => Self::new(v), Ok(v) => Self::new(v),
@ -47,7 +49,10 @@ impl<T> FromRc<T> for Arc<T> where T: Sized + Clone {
} }
} }
impl<T> FromRc<T> for Box<T> where T: Sized + Clone { impl<T> FromRc<T> for Box<T>
where
T: Sized + Clone,
{
fn from_rc(val: Rc<T>) -> Self { fn from_rc(val: Rc<T>) -> Self {
match ::std::rc::Rc::<T>::try_unwrap(val) { match ::std::rc::Rc::<T>::try_unwrap(val) {
Ok(v) => Self::new(v), Ok(v) => Self::new(v),
@ -69,7 +74,10 @@ pub trait Cloned<T> {
fn to_value_rc(&self) -> ValueRc<T>; fn to_value_rc(&self) -> ValueRc<T>;
} }
impl<T: Clone> Cloned<T> for Rc<T> where T: Sized + Clone { impl<T: Clone> Cloned<T> for Rc<T>
where
T: Sized + Clone,
{
fn cloned(&self) -> T { fn cloned(&self) -> T {
(*self.as_ref()).clone() (*self.as_ref()).clone()
} }
@ -79,7 +87,10 @@ impl<T: Clone> Cloned<T> for Rc<T> where T: Sized + Clone {
} }
} }
impl<T: Clone> Cloned<T> for Arc<T> where T: Sized + Clone { impl<T: Clone> Cloned<T> for Arc<T>
where
T: Sized + Clone,
{
fn cloned(&self) -> T { fn cloned(&self) -> T {
(*self.as_ref()).clone() (*self.as_ref()).clone()
} }
@ -89,7 +100,10 @@ impl<T: Clone> Cloned<T> for Arc<T> where T: Sized + Clone {
} }
} }
impl<T: Clone> Cloned<T> for Box<T> where T: Sized + Clone { impl<T: Clone> Cloned<T> for Box<T>
where
T: Sized + Clone,
{
fn cloned(&self) -> T { fn cloned(&self) -> T {
self.as_ref().clone() self.as_ref().clone()
} }

View file

@ -10,33 +10,14 @@
extern crate edn; extern crate edn;
use edn::{ use edn::{Keyword, PlainSymbol};
Keyword,
PlainSymbol,
};
use edn::query::{ use edn::query::{
Direction, Direction, Element, FindSpec, FnArg, Limit, NonIntegerConstant, OrJoin, OrWhereClause, Order,
Element, Pattern, PatternNonValuePlace, PatternValuePlace, Predicate, UnifyVars, Variable, WhereClause,
FindSpec,
FnArg,
Limit,
NonIntegerConstant,
Order,
OrJoin,
OrWhereClause,
Pattern,
PatternNonValuePlace,
PatternValuePlace,
Predicate,
UnifyVars,
Variable,
WhereClause,
}; };
use edn::parse::{ use edn::parse::parse_query;
parse_query,
};
///! N.B., parsing a query can be done without reference to a DB. ///! N.B., parsing a query can be done without reference to a DB.
///! Processing the parsed query into something we can work with ///! Processing the parsed query into something we can work with
@ -48,21 +29,29 @@ fn can_parse_predicates() {
let s = "[:find [?x ...] :where [?x _ ?y] [(< ?y 10)]]"; let s = "[:find [?x ...] :where [?x _ ?y] [(< ?y 10)]]";
let p = parse_query(s).unwrap(); let p = parse_query(s).unwrap();
assert_eq!(p.find_spec, assert_eq!(
FindSpec::FindColl(Element::Variable(Variable::from_valid_name("?x")))); p.find_spec,
assert_eq!(p.where_clauses, FindSpec::FindColl(Element::Variable(Variable::from_valid_name("?x")))
vec![ );
WhereClause::Pattern(Pattern { assert_eq!(
source: None, p.where_clauses,
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), vec![
attribute: PatternNonValuePlace::Placeholder, WhereClause::Pattern(Pattern {
value: PatternValuePlace::Variable(Variable::from_valid_name("?y")), source: None,
tx: PatternNonValuePlace::Placeholder, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
}), attribute: PatternNonValuePlace::Placeholder,
WhereClause::Pred(Predicate { operator: PlainSymbol::plain("<"), args: vec![ value: PatternValuePlace::Variable(Variable::from_valid_name("?y")),
FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(10), tx: PatternNonValuePlace::Placeholder,
]}), }),
]); WhereClause::Pred(Predicate {
operator: PlainSymbol::plain("<"),
args: vec![
FnArg::Variable(Variable::from_valid_name("?y")),
FnArg::EntidOrInteger(10),
]
}),
]
);
} }
#[test] #[test]
@ -70,32 +59,32 @@ fn can_parse_simple_or() {
let s = "[:find ?x . :where (or [?x _ 10] [?x _ 15])]"; let s = "[:find ?x . :where (or [?x _ 10] [?x _ 15])]";
let p = parse_query(s).unwrap(); let p = parse_query(s).unwrap();
assert_eq!(p.find_spec, assert_eq!(
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))); p.find_spec,
assert_eq!(p.where_clauses, FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))
vec![ );
WhereClause::OrJoin(OrJoin::new( assert_eq!(
UnifyVars::Implicit, p.where_clauses,
vec![ vec![WhereClause::OrJoin(OrJoin::new(
OrWhereClause::Clause( UnifyVars::Implicit,
WhereClause::Pattern(Pattern { vec![
source: None, OrWhereClause::Clause(WhereClause::Pattern(Pattern {
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), source: None,
attribute: PatternNonValuePlace::Placeholder, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
value: PatternValuePlace::EntidOrInteger(10), attribute: PatternNonValuePlace::Placeholder,
tx: PatternNonValuePlace::Placeholder, value: PatternValuePlace::EntidOrInteger(10),
})), tx: PatternNonValuePlace::Placeholder,
OrWhereClause::Clause( })),
WhereClause::Pattern(Pattern { OrWhereClause::Clause(WhereClause::Pattern(Pattern {
source: None, source: None,
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
attribute: PatternNonValuePlace::Placeholder, attribute: PatternNonValuePlace::Placeholder,
value: PatternValuePlace::EntidOrInteger(15), value: PatternValuePlace::EntidOrInteger(15),
tx: PatternNonValuePlace::Placeholder, tx: PatternNonValuePlace::Placeholder,
})), })),
], ],
)), )),]
]); );
} }
#[test] #[test]
@ -103,24 +92,23 @@ fn can_parse_unit_or_join() {
let s = "[:find ?x . :where (or-join [?x] [?x _ 15])]"; let s = "[:find ?x . :where (or-join [?x] [?x _ 15])]";
let p = parse_query(s).expect("to be able to parse find"); let p = parse_query(s).expect("to be able to parse find");
assert_eq!(p.find_spec, assert_eq!(
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))); p.find_spec,
assert_eq!(p.where_clauses, FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))
vec![ );
WhereClause::OrJoin(OrJoin::new( assert_eq!(
UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()), p.where_clauses,
vec![ vec![WhereClause::OrJoin(OrJoin::new(
OrWhereClause::Clause( UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()),
WhereClause::Pattern(Pattern { vec![OrWhereClause::Clause(WhereClause::Pattern(Pattern {
source: None, source: None,
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
attribute: PatternNonValuePlace::Placeholder, attribute: PatternNonValuePlace::Placeholder,
value: PatternValuePlace::EntidOrInteger(15), value: PatternValuePlace::EntidOrInteger(15),
tx: PatternNonValuePlace::Placeholder, tx: PatternNonValuePlace::Placeholder,
})), })),],
], )),]
)), );
]);
} }
#[test] #[test]
@ -128,32 +116,32 @@ fn can_parse_simple_or_join() {
let s = "[:find ?x . :where (or-join [?x] [?x _ 10] [?x _ -15])]"; let s = "[:find ?x . :where (or-join [?x] [?x _ 10] [?x _ -15])]";
let p = parse_query(s).unwrap(); let p = parse_query(s).unwrap();
assert_eq!(p.find_spec, assert_eq!(
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))); p.find_spec,
assert_eq!(p.where_clauses, FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))
vec![ );
WhereClause::OrJoin(OrJoin::new( assert_eq!(
UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()), p.where_clauses,
vec![ vec![WhereClause::OrJoin(OrJoin::new(
OrWhereClause::Clause( UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()),
WhereClause::Pattern(Pattern { vec![
source: None, OrWhereClause::Clause(WhereClause::Pattern(Pattern {
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), source: None,
attribute: PatternNonValuePlace::Placeholder, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
value: PatternValuePlace::EntidOrInteger(10), attribute: PatternNonValuePlace::Placeholder,
tx: PatternNonValuePlace::Placeholder, value: PatternValuePlace::EntidOrInteger(10),
})), tx: PatternNonValuePlace::Placeholder,
OrWhereClause::Clause( })),
WhereClause::Pattern(Pattern { OrWhereClause::Clause(WhereClause::Pattern(Pattern {
source: None, source: None,
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
attribute: PatternNonValuePlace::Placeholder, attribute: PatternNonValuePlace::Placeholder,
value: PatternValuePlace::EntidOrInteger(-15), value: PatternValuePlace::EntidOrInteger(-15),
tx: PatternNonValuePlace::Placeholder, tx: PatternNonValuePlace::Placeholder,
})), })),
], ],
)), )),]
]); );
} }
#[cfg(test)] #[cfg(test)]
@ -166,51 +154,57 @@ fn can_parse_simple_or_and_join() {
let s = "[:find ?x . :where (or [?x _ 10] (and (or [?x :foo/bar ?y] [?x :foo/baz ?y]) [(< ?y 1)]))]"; let s = "[:find ?x . :where (or [?x _ 10] (and (or [?x :foo/bar ?y] [?x :foo/baz ?y]) [(< ?y 1)]))]";
let p = parse_query(s).unwrap(); let p = parse_query(s).unwrap();
assert_eq!(p.find_spec, assert_eq!(
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))); p.find_spec,
assert_eq!(p.where_clauses, FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))
vec![ );
WhereClause::OrJoin(OrJoin::new( assert_eq!(
UnifyVars::Implicit, p.where_clauses,
vec![ vec![WhereClause::OrJoin(OrJoin::new(
OrWhereClause::Clause( UnifyVars::Implicit,
WhereClause::Pattern(Pattern { vec![
source: None, OrWhereClause::Clause(WhereClause::Pattern(Pattern {
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), source: None,
attribute: PatternNonValuePlace::Placeholder, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
value: PatternValuePlace::EntidOrInteger(10), attribute: PatternNonValuePlace::Placeholder,
tx: PatternNonValuePlace::Placeholder, value: PatternValuePlace::EntidOrInteger(10),
})), tx: PatternNonValuePlace::Placeholder,
OrWhereClause::And( })),
vec![ OrWhereClause::And(vec![
WhereClause::OrJoin(OrJoin::new( WhereClause::OrJoin(OrJoin::new(
UnifyVars::Implicit, UnifyVars::Implicit,
vec![ vec![
OrWhereClause::Clause(WhereClause::Pattern(Pattern { OrWhereClause::Clause(WhereClause::Pattern(Pattern {
source: None, source: None,
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), entity: PatternNonValuePlace::Variable(Variable::from_valid_name(
attribute: ident("foo", "bar"), "?x"
value: PatternValuePlace::Variable(Variable::from_valid_name("?y")), )),
tx: PatternNonValuePlace::Placeholder, attribute: ident("foo", "bar"),
})), value: PatternValuePlace::Variable(Variable::from_valid_name("?y")),
OrWhereClause::Clause(WhereClause::Pattern(Pattern { tx: PatternNonValuePlace::Placeholder,
source: None, })),
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), OrWhereClause::Clause(WhereClause::Pattern(Pattern {
attribute: ident("foo", "baz"), source: None,
value: PatternValuePlace::Variable(Variable::from_valid_name("?y")), entity: PatternNonValuePlace::Variable(Variable::from_valid_name(
tx: PatternNonValuePlace::Placeholder, "?x"
})), )),
], attribute: ident("foo", "baz"),
)), value: PatternValuePlace::Variable(Variable::from_valid_name("?y")),
tx: PatternNonValuePlace::Placeholder,
WhereClause::Pred(Predicate { operator: PlainSymbol::plain("<"), args: vec![ })),
FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(1), ],
]}), )),
], WhereClause::Pred(Predicate {
) operator: PlainSymbol::plain("<"),
], args: vec![
)), FnArg::Variable(Variable::from_valid_name("?y")),
]); FnArg::EntidOrInteger(1),
]
}),
],)
],
)),]
);
} }
#[test] #[test]
@ -220,21 +214,40 @@ fn can_parse_order_by() {
// Defaults to ascending. // Defaults to ascending.
let default = "[:find ?x :where [?x :foo/baz ?y] :order ?y]"; let default = "[:find ?x :where [?x :foo/baz ?y] :order ?y]";
assert_eq!(parse_query(default).unwrap().order, assert_eq!(
Some(vec![Order(Direction::Ascending, Variable::from_valid_name("?y"))])); parse_query(default).unwrap().order,
Some(vec![Order(
Direction::Ascending,
Variable::from_valid_name("?y")
)])
);
let ascending = "[:find ?x :where [?x :foo/baz ?y] :order (asc ?y)]"; let ascending = "[:find ?x :where [?x :foo/baz ?y] :order (asc ?y)]";
assert_eq!(parse_query(ascending).unwrap().order, assert_eq!(
Some(vec![Order(Direction::Ascending, Variable::from_valid_name("?y"))])); parse_query(ascending).unwrap().order,
Some(vec![Order(
Direction::Ascending,
Variable::from_valid_name("?y")
)])
);
let descending = "[:find ?x :where [?x :foo/baz ?y] :order (desc ?y)]"; let descending = "[:find ?x :where [?x :foo/baz ?y] :order (desc ?y)]";
assert_eq!(parse_query(descending).unwrap().order, assert_eq!(
Some(vec![Order(Direction::Descending, Variable::from_valid_name("?y"))])); parse_query(descending).unwrap().order,
Some(vec![Order(
Direction::Descending,
Variable::from_valid_name("?y")
)])
);
let mixed = "[:find ?x :where [?x :foo/baz ?y] :order (desc ?y) (asc ?x)]"; let mixed = "[:find ?x :where [?x :foo/baz ?y] :order (desc ?y) (asc ?x)]";
assert_eq!(parse_query(mixed).unwrap().order, assert_eq!(
Some(vec![Order(Direction::Descending, Variable::from_valid_name("?y")), parse_query(mixed).unwrap().order,
Order(Direction::Ascending, Variable::from_valid_name("?x"))])); Some(vec![
Order(Direction::Descending, Variable::from_valid_name("?y")),
Order(Direction::Ascending, Variable::from_valid_name("?x"))
])
);
} }
#[test] #[test]
@ -246,55 +259,76 @@ fn can_parse_limit() {
assert!(parse_query(zero_invalid).is_err()); assert!(parse_query(zero_invalid).is_err());
let none = "[:find ?x :where [?x :foo/baz ?y]]"; let none = "[:find ?x :where [?x :foo/baz ?y]]";
assert_eq!(parse_query(none).unwrap().limit, assert_eq!(parse_query(none).unwrap().limit, Limit::None);
Limit::None);
let one = "[:find ?x :where [?x :foo/baz ?y] :limit 1]"; let one = "[:find ?x :where [?x :foo/baz ?y] :limit 1]";
assert_eq!(parse_query(one).unwrap().limit, assert_eq!(parse_query(one).unwrap().limit, Limit::Fixed(1));
Limit::Fixed(1));
let onethousand = "[:find ?x :where [?x :foo/baz ?y] :limit 1000]"; let onethousand = "[:find ?x :where [?x :foo/baz ?y] :limit 1000]";
assert_eq!(parse_query(onethousand).unwrap().limit, assert_eq!(parse_query(onethousand).unwrap().limit, Limit::Fixed(1000));
Limit::Fixed(1000));
let variable_with_in = "[:find ?x :in ?limit :where [?x :foo/baz ?y] :limit ?limit]"; let variable_with_in = "[:find ?x :in ?limit :where [?x :foo/baz ?y] :limit ?limit]";
assert_eq!(parse_query(variable_with_in).unwrap().limit, assert_eq!(
Limit::Variable(Variable::from_valid_name("?limit"))); parse_query(variable_with_in).unwrap().limit,
Limit::Variable(Variable::from_valid_name("?limit"))
);
let variable_with_in_used = "[:find ?x :in ?limit :where [?x :foo/baz ?limit] :limit ?limit]"; let variable_with_in_used = "[:find ?x :in ?limit :where [?x :foo/baz ?limit] :limit ?limit]";
assert_eq!(parse_query(variable_with_in_used).unwrap().limit, assert_eq!(
Limit::Variable(Variable::from_valid_name("?limit"))); parse_query(variable_with_in_used).unwrap().limit,
Limit::Variable(Variable::from_valid_name("?limit"))
);
} }
#[test] #[test]
fn can_parse_uuid() { fn can_parse_uuid() {
let expected = edn::Uuid::parse_str("4cb3f828-752d-497a-90c9-b1fd516d5644").expect("valid uuid"); let expected =
edn::Uuid::parse_str("4cb3f828-752d-497a-90c9-b1fd516d5644").expect("valid uuid");
let s = "[:find ?x :where [?x :foo/baz #uuid \"4cb3f828-752d-497a-90c9-b1fd516d5644\"]]"; let s = "[:find ?x :where [?x :foo/baz #uuid \"4cb3f828-752d-497a-90c9-b1fd516d5644\"]]";
assert_eq!(parse_query(s).expect("parsed").where_clauses.pop().expect("a where clause"), assert_eq!(
WhereClause::Pattern( parse_query(s)
Pattern::new(None, .expect("parsed")
PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), .where_clauses
Keyword::namespaced("foo", "baz").into(), .pop()
PatternValuePlace::Constant(NonIntegerConstant::Uuid(expected)), .expect("a where clause"),
PatternNonValuePlace::Placeholder) WhereClause::Pattern(
.expect("valid pattern"))); Pattern::new(
None,
PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
Keyword::namespaced("foo", "baz").into(),
PatternValuePlace::Constant(NonIntegerConstant::Uuid(expected)),
PatternNonValuePlace::Placeholder
)
.expect("valid pattern")
)
);
} }
#[test] #[test]
fn can_parse_exotic_whitespace() { fn can_parse_exotic_whitespace() {
let expected = edn::Uuid::parse_str("4cb3f828-752d-497a-90c9-b1fd516d5644").expect("valid uuid"); let expected =
edn::Uuid::parse_str("4cb3f828-752d-497a-90c9-b1fd516d5644").expect("valid uuid");
// The query string from `can_parse_uuid`, with newlines, commas, and line comments interspersed. // The query string from `can_parse_uuid`, with newlines, commas, and line comments interspersed.
let s = r#"[:find let s = r#"[:find
?x ,, :where, ;atest ?x ,, :where, ;atest
[?x :foo/baz #uuid [?x :foo/baz #uuid
"4cb3f828-752d-497a-90c9-b1fd516d5644", ;testa "4cb3f828-752d-497a-90c9-b1fd516d5644", ;testa
,],, ,],;"#; ,],, ,],;"#;
assert_eq!(parse_query(s).expect("parsed").where_clauses.pop().expect("a where clause"), assert_eq!(
WhereClause::Pattern( parse_query(s)
Pattern::new(None, .expect("parsed")
PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), .where_clauses
Keyword::namespaced("foo", "baz").into(), .pop()
PatternValuePlace::Constant(NonIntegerConstant::Uuid(expected)), .expect("a where clause"),
PatternNonValuePlace::Placeholder) WhereClause::Pattern(
.expect("valid pattern"))); Pattern::new(
None,
PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
Keyword::namespaced("foo", "baz").into(),
PatternValuePlace::Constant(NonIntegerConstant::Uuid(expected)),
PatternNonValuePlace::Placeholder
)
.expect("valid pattern")
)
);
} }

View file

@ -8,11 +8,10 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
#![cfg(feature = "serde_support")] #![cfg(feature = "serde_support")]
extern crate serde_test;
extern crate serde_json; extern crate serde_json;
extern crate serde_test;
extern crate edn; extern crate edn;
use edn::symbols::Keyword; use edn::symbols::Keyword;
@ -22,19 +21,24 @@ use serde_test::{assert_tokens, Token};
#[test] #[test]
fn test_serialize_keyword() { fn test_serialize_keyword() {
let kw = Keyword::namespaced("foo", "bar"); let kw = Keyword::namespaced("foo", "bar");
assert_tokens(&kw, &[ assert_tokens(
Token::NewtypeStruct { name: "Keyword" }, &kw,
Token::Struct { name: "NamespaceableName", len: 2 }, &[
Token::Str("namespace"), Token::NewtypeStruct { name: "Keyword" },
Token::Some, Token::Struct {
Token::BorrowedStr("foo"), name: "NamespaceableName",
Token::Str("name"), len: 2,
Token::BorrowedStr("bar"), },
Token::StructEnd, Token::Str("namespace"),
]); Token::Some,
Token::BorrowedStr("foo"),
Token::Str("name"),
Token::BorrowedStr("bar"),
Token::StructEnd,
],
);
} }
#[cfg(feature = "serde_support")] #[cfg(feature = "serde_support")]
#[test] #[test]
fn test_deserialize_keyword() { fn test_deserialize_keyword() {
@ -51,6 +55,3 @@ fn test_deserialize_keyword() {
let not_kw = serde_json::from_str::<Keyword>(bad_ns_json); let not_kw = serde_json::from_str::<Keyword>(bad_ns_json);
assert!(not_kw.is_err()); assert!(not_kw.is_err());
} }

File diff suppressed because it is too large Load diff

View file

@ -21,4 +21,6 @@ pub enum LogLevel {
Error = 6, Error = 6,
} }
extern { pub fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int; } extern "C" {
pub fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
}

File diff suppressed because it is too large Load diff

View file

@ -9,15 +9,10 @@
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
pub mod strings { pub mod strings {
use std::ffi::{ use std::ffi::{CStr, CString};
CString,
CStr
};
use std::os::raw::c_char; use std::os::raw::c_char;
use mentat::{ use mentat::Keyword;
Keyword,
};
pub fn c_char_to_string(cchar: *const c_char) -> &'static str { pub fn c_char_to_string(cchar: *const c_char) -> &'static str {
assert!(!cchar.is_null()); assert!(!cchar.is_null());
@ -25,35 +20,38 @@ pub mod strings {
c_str.to_str().unwrap_or("") c_str.to_str().unwrap_or("")
} }
pub fn string_to_c_char<T>(r_string: T) -> *mut c_char where T: Into<String> { pub fn string_to_c_char<T>(r_string: T) -> *mut c_char
where
T: Into<String>,
{
CString::new(r_string.into()).unwrap().into_raw() CString::new(r_string.into()).unwrap().into_raw()
} }
pub fn kw_from_string(keyword_string: &'static str) -> Keyword { pub fn kw_from_string(keyword_string: &'static str) -> Keyword {
// TODO: validate. The input might not be a keyword! // TODO: validate. The input might not be a keyword!
let attr_name = keyword_string.trim_left_matches(":"); let attr_name = keyword_string.trim_start_matches(":");
let parts: Vec<&str> = attr_name.split("/").collect(); let parts: Vec<&str> = attr_name.split("/").collect();
Keyword::namespaced(parts[0], parts[1]) Keyword::namespaced(parts[0], parts[1])
} }
} }
pub mod log { pub mod log {
#[cfg(all(target_os="android", not(test)))] #[cfg(all(target_os = "android", not(test)))]
use std::ffi::CString; use std::ffi::CString;
#[cfg(all(target_os="android", not(test)))] #[cfg(all(target_os = "android", not(test)))]
use android; use android;
// TODO far from ideal. And, we might actually want to println in tests. // TODO far from ideal. And, we might actually want to println in tests.
#[cfg(all(not(target_os="android"), not(target_os="ios")))] #[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
pub fn d(_: &str) {} pub fn d(_: &str) {}
#[cfg(all(target_os="ios", not(test)))] #[cfg(all(target_os = "ios", not(test)))]
pub fn d(message: &str) { pub fn d(message: &str) {
eprintln!("{}", message); eprintln!("{}", message);
} }
#[cfg(all(target_os="android", not(test)))] #[cfg(all(target_os = "android", not(test)))]
pub fn d(message: &str) { pub fn d(message: &str) {
let message = CString::new(message).unwrap(); let message = CString::new(message).unwrap();
let message = message.as_ptr(); let message = message.as_ptr();
@ -65,9 +63,9 @@ pub mod log {
pub mod error { pub mod error {
use super::strings::string_to_c_char; use super::strings::string_to_c_char;
use std::os::raw::c_char;
use std::boxed::Box; use std::boxed::Box;
use std::fmt::Display; use std::fmt::Display;
use std::os::raw::c_char;
use std::ptr; use std::ptr;
/// Represents an error that occurred on the mentat side. Many mentat FFI functions take a /// Represents an error that occurred on the mentat side. Many mentat FFI functions take a
@ -96,7 +94,9 @@ pub mod error {
impl Default for ExternError { impl Default for ExternError {
fn default() -> ExternError { fn default() -> ExternError {
ExternError { message: ptr::null_mut() } ExternError {
message: ptr::null_mut(),
}
} }
} }
@ -108,7 +108,9 @@ pub mod error {
/// message (which was allocated on the heap and should eventually be freed) into /// message (which was allocated on the heap and should eventually be freed) into
/// `error.message` /// `error.message`
pub unsafe fn translate_result<T, E>(result: Result<T, E>, error: *mut ExternError) -> *mut T pub unsafe fn translate_result<T, E>(result: Result<T, E>, error: *mut ExternError) -> *mut T
where E: Display { where
E: Display,
{
// TODO: can't unwind across FFI... // TODO: can't unwind across FFI...
assert!(!error.is_null(), "Error output parameter is not optional"); assert!(!error.is_null(), "Error output parameter is not optional");
let error = &mut *error; let error = &mut *error;
@ -131,8 +133,13 @@ pub mod error {
/// - If `result` is `Err(e)`, returns a null pointer and stores a string representing the error /// - If `result` is `Err(e)`, returns a null pointer and stores a string representing the error
/// message (which was allocated on the heap and should eventually be freed) into /// message (which was allocated on the heap and should eventually be freed) into
/// `error.message` /// `error.message`
pub unsafe fn translate_opt_result<T, E>(result: Result<Option<T>, E>, error: *mut ExternError) -> *mut T pub unsafe fn translate_opt_result<T, E>(
where E: Display { result: Result<Option<T>, E>,
error: *mut ExternError,
) -> *mut T
where
E: Display,
{
assert!(!error.is_null(), "Error output parameter is not optional"); assert!(!error.is_null(), "Error output parameter is not optional");
let error = &mut *error; let error = &mut *error;
error.message = ptr::null_mut(); error.message = ptr::null_mut();
@ -148,10 +155,12 @@ pub mod error {
/// Identical to `translate_result`, but with additional type checking for the case that we have /// Identical to `translate_result`, but with additional type checking for the case that we have
/// a `Result<(), E>` (which we're about to drop on the floor). /// a `Result<(), E>` (which we're about to drop on the floor).
pub unsafe fn translate_void_result<E>(result: Result<(), E>, error: *mut ExternError) where E: Display { pub unsafe fn translate_void_result<E>(result: Result<(), E>, error: *mut ExternError)
where
E: Display,
{
// Note that Box<T> guarantees that if T is zero sized, it's not heap allocated. So not // Note that Box<T> guarantees that if T is zero sized, it's not heap allocated. So not
// only do we never need to free the return value of this, it would be a problem if someone did. // only do we never need to free the return value of this, it would be a problem if someone did.
translate_result(result, error); translate_result(result, error);
} }
} }

View file

@ -13,12 +13,14 @@ sqlcipher = ["rusqlite/sqlcipher"]
syncable = ["tolstoy_traits", "hyper", "serde_json"] syncable = ["tolstoy_traits", "hyper", "serde_json"]
[dependencies] [dependencies]
failure = "0.1.1" failure = "0.1.6"
failure_derive = "0.1.1" failure_derive = "0.1.6"
uuid = "0.5" http = "0.2.0"
tokio-core = "0.1.17"
uuid = "0.8"
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.13" version = "0.21"
features = ["limits"] features = ["limits"]
[dependencies.edn] [dependencies.edn]
@ -47,7 +49,7 @@ path = "../tolstoy-traits"
optional = true optional = true
[dependencies.hyper] [dependencies.hyper]
version = "0.11" version = "0.13.1"
optional = true optional = true
[dependencies.serde_json] [dependencies.serde_json]

View file

@ -20,31 +20,16 @@ use uuid;
use edn; use edn;
use core_traits::{ use core_traits::{Attribute, ValueType};
Attribute,
ValueType,
};
use db_traits::errors::{ use db_traits::errors::DbError;
DbError, use query_algebrizer_traits::errors::AlgebrizerError;
}; use query_projector_traits::errors::ProjectorError;
use query_algebrizer_traits::errors::{ use query_pull_traits::errors::PullError;
AlgebrizerError, use sql_traits::errors::SQLError;
};
use query_projector_traits::errors::{
ProjectorError,
};
use query_pull_traits::errors::{
PullError,
};
use sql_traits::errors::{
SQLError,
};
#[cfg(feature = "syncable")] #[cfg(feature = "syncable")]
use tolstoy_traits::errors::{ use tolstoy_traits::errors::TolstoyError;
TolstoyError,
};
#[cfg(feature = "syncable")] #[cfg(feature = "syncable")]
use hyper; use hyper;
@ -74,10 +59,16 @@ pub enum MentatError {
#[fail(display = "invalid vocabulary version")] #[fail(display = "invalid vocabulary version")]
InvalidVocabularyVersion, InvalidVocabularyVersion,
#[fail(display = "vocabulary {}/version {} already has attribute {}, and the requested definition differs", _0, _1, _2)] #[fail(
display = "vocabulary {}/version {} already has attribute {}, and the requested definition differs",
_0, _1, _2
)]
ConflictingAttributeDefinitions(String, u32, String, Attribute, Attribute), ConflictingAttributeDefinitions(String, u32, String, Attribute, Attribute),
#[fail(display = "existing vocabulary {} too new: wanted version {}, got version {}", _0, _1, _2)] #[fail(
display = "existing vocabulary {} too new: wanted version {}, got version {}",
_0, _1, _2
)]
ExistingVocabularyTooNew(String, u32, u32), ExistingVocabularyTooNew(String, u32, u32),
#[fail(display = "core schema: wanted version {}, got version {:?}", _0, _1)] #[fail(display = "core schema: wanted version {}, got version {:?}", _0, _1)]
@ -92,7 +83,10 @@ pub enum MentatError {
#[fail(display = "schema changed since query was prepared")] #[fail(display = "schema changed since query was prepared")]
PreparedQuerySchemaMismatch, PreparedQuerySchemaMismatch,
#[fail(display = "provided value of type {} doesn't match attribute value type {}", _0, _1)] #[fail(
display = "provided value of type {} doesn't match attribute value type {}",
_0, _1
)]
ValueTypeMismatch(ValueType, ValueType), ValueTypeMismatch(ValueType, ValueType),
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
@ -127,7 +121,7 @@ pub enum MentatError {
SQLError(#[cause] SQLError), SQLError(#[cause] SQLError),
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
UuidError(#[cause] uuid::ParseError), UuidError(#[cause] uuid::Error),
#[cfg(feature = "syncable")] #[cfg(feature = "syncable")]
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
@ -139,7 +133,7 @@ pub enum MentatError {
#[cfg(feature = "syncable")] #[cfg(feature = "syncable")]
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
UriError(#[cause] hyper::error::UriError), UriError(#[cause] http::uri::InvalidUri),
#[cfg(feature = "syncable")] #[cfg(feature = "syncable")]
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
@ -147,87 +141,87 @@ pub enum MentatError {
} }
impl From<std::io::Error> for MentatError { impl From<std::io::Error> for MentatError {
fn from(error: std::io::Error) -> MentatError { fn from(error: std::io::Error) -> Self {
MentatError::IoError(error) MentatError::IoError(error)
} }
} }
impl From<rusqlite::Error> for MentatError { impl From<rusqlite::Error> for MentatError {
fn from(error: rusqlite::Error) -> MentatError { fn from(error: rusqlite::Error) -> Self {
let cause = match error.cause() { let cause = match error.source() {
Some(e) => e.to_string(), Some(e) => e.to_string(),
None => "".to_string() None => "".to_string(),
}; };
MentatError::RusqliteError(error.to_string(), cause) MentatError::RusqliteError(error.to_string(), cause)
} }
} }
impl From<uuid::ParseError> for MentatError { impl From<uuid::Error> for MentatError {
fn from(error: uuid::ParseError) -> MentatError { fn from(error: uuid::Error) -> Self {
MentatError::UuidError(error) MentatError::UuidError(error)
} }
} }
impl From<edn::ParseError> for MentatError { impl From<edn::ParseError> for MentatError {
fn from(error: edn::ParseError) -> MentatError { fn from(error: edn::ParseError) -> Self {
MentatError::EdnParseError(error) MentatError::EdnParseError(error)
} }
} }
impl From<DbError> for MentatError { impl From<DbError> for MentatError {
fn from(error: DbError) -> MentatError { fn from(error: DbError) -> Self {
MentatError::DbError(error) MentatError::DbError(error)
} }
} }
impl From<AlgebrizerError> for MentatError { impl From<AlgebrizerError> for MentatError {
fn from(error: AlgebrizerError) -> MentatError { fn from(error: AlgebrizerError) -> Self {
MentatError::AlgebrizerError(error) MentatError::AlgebrizerError(error)
} }
} }
impl From<ProjectorError> for MentatError { impl From<ProjectorError> for MentatError {
fn from(error: ProjectorError) -> MentatError { fn from(error: ProjectorError) -> Self {
MentatError::ProjectorError(error) MentatError::ProjectorError(error)
} }
} }
impl From<PullError> for MentatError { impl From<PullError> for MentatError {
fn from(error: PullError) -> MentatError { fn from(error: PullError) -> Self {
MentatError::PullError(error) MentatError::PullError(error)
} }
} }
impl From<SQLError> for MentatError { impl From<SQLError> for MentatError {
fn from(error: SQLError) -> MentatError { fn from(error: SQLError) -> Self {
MentatError::SQLError(error) MentatError::SQLError(error)
} }
} }
#[cfg(feature = "syncable")] #[cfg(feature = "syncable")]
impl From<TolstoyError> for MentatError { impl From<TolstoyError> for MentatError {
fn from(error: TolstoyError) -> MentatError { fn from(error: TolstoyError) -> Self {
MentatError::TolstoyError(error) MentatError::TolstoyError(error)
} }
} }
#[cfg(feature = "syncable")] #[cfg(feature = "syncable")]
impl From<serde_json::Error> for MentatError { impl From<serde_json::Error> for MentatError {
fn from(error: serde_json::Error) -> MentatError { fn from(error: serde_json::Error) -> Self {
MentatError::SerializationError(error) MentatError::SerializationError(error)
} }
} }
#[cfg(feature = "syncable")] #[cfg(feature = "syncable")]
impl From<hyper::Error> for MentatError { impl From<hyper::Error> for MentatError {
fn from(error: hyper::Error) -> MentatError { fn from(error: hyper::Error) -> Self {
MentatError::NetworkError(error) MentatError::NetworkError(error)
} }
} }
#[cfg(feature = "syncable")] #[cfg(feature = "syncable")]
impl From<hyper::error::UriError> for MentatError { impl From<http::uri::InvalidUri> for MentatError {
fn from(error: hyper::error::UriError) -> MentatError { fn from(error: http::uri::InvalidUri) -> Self {
MentatError::UriError(error) MentatError::UriError(error)
} }
} }

View file

@ -14,12 +14,12 @@ extern crate failure_derive;
extern crate rusqlite; extern crate rusqlite;
extern crate edn;
extern crate core_traits; extern crate core_traits;
extern crate db_traits; extern crate db_traits;
extern crate query_pull_traits; extern crate edn;
extern crate query_projector_traits;
extern crate query_algebrizer_traits; extern crate query_algebrizer_traits;
extern crate query_projector_traits;
extern crate query_pull_traits;
extern crate sql_traits; extern crate sql_traits;
extern crate uuid; extern crate uuid;

View file

@ -10,18 +10,11 @@
use std; // To refer to std::result::Result. use std; // To refer to std::result::Result.
use core_traits::{ use core_traits::{ValueType, ValueTypeSet};
ValueType,
ValueTypeSet,
};
use edn::parse::{ use edn::parse::ParseError;
ParseError,
};
use edn::query::{ use edn::query::PlainSymbol;
PlainSymbol,
};
pub type Result<T> = std::result::Result<T, AlgebrizerError>; pub type Result<T> = std::result::Result<T, AlgebrizerError>;
@ -42,7 +35,10 @@ pub enum BindingError {
/// Expected `[?x1 … ?xN]` or `[[?x1 … ?xN]]` but got some other number of bindings. Mentat is /// Expected `[?x1 … ?xN]` or `[[?x1 … ?xN]]` but got some other number of bindings. Mentat is
/// deliberately more strict than Datomic: we prefer placeholders to omission. /// deliberately more strict than Datomic: we prefer placeholders to omission.
InvalidNumberOfBindings { number: usize, expected: usize }, InvalidNumberOfBindings {
number: usize,
expected: usize,
},
} }
#[derive(Clone, Debug, Eq, Fail, PartialEq)] #[derive(Clone, Debug, Eq, Fail, PartialEq)]
@ -53,23 +49,38 @@ pub enum AlgebrizerError {
#[fail(display = "unexpected FnArg")] #[fail(display = "unexpected FnArg")]
UnsupportedArgument, UnsupportedArgument,
#[fail(display = "value of type {} provided for var {}, expected {}", _0, _1, _2)] #[fail(
display = "value of type {} provided for var {}, expected {}",
_0, _1, _2
)]
InputTypeDisagreement(PlainSymbol, ValueType, ValueType), InputTypeDisagreement(PlainSymbol, ValueType, ValueType),
#[fail(display = "invalid number of arguments to {}: expected {}, got {}.", _0, _1, _2)] #[fail(
display = "invalid number of arguments to {}: expected {}, got {}.",
_0, _1, _2
)]
InvalidNumberOfArguments(PlainSymbol, usize, usize), InvalidNumberOfArguments(PlainSymbol, usize, usize),
#[fail(display = "invalid argument to {}: expected {} in position {}.", _0, _1, _2)] #[fail(
display = "invalid argument to {}: expected {} in position {}.",
_0, _1, _2
)]
InvalidArgument(PlainSymbol, &'static str, usize), InvalidArgument(PlainSymbol, &'static str, usize),
#[fail(display = "invalid argument to {}: expected one of {:?} in position {}.", _0, _1, _2)] #[fail(
display = "invalid argument to {}: expected one of {:?} in position {}.",
_0, _1, _2
)]
InvalidArgumentType(PlainSymbol, ValueTypeSet, usize), InvalidArgumentType(PlainSymbol, ValueTypeSet, usize),
// TODO: flesh this out. // TODO: flesh this out.
#[fail(display = "invalid expression in ground constant")] #[fail(display = "invalid expression in ground constant")]
InvalidGroundConstant, InvalidGroundConstant,
#[fail(display = "invalid limit {} of type {}: expected natural number.", _0, _1)] #[fail(
display = "invalid limit {} of type {}: expected natural number.",
_0, _1
)]
InvalidLimit(String, ValueType), InvalidLimit(String, ValueType),
#[fail(display = "mismatched bindings in ground")] #[fail(display = "mismatched bindings in ground")]

View file

@ -19,4 +19,4 @@ path = "../core-traits"
path = "../query-algebrizer-traits" path = "../query-algebrizer-traits"
[dev-dependencies] [dev-dependencies]
itertools = "0.7" itertools = "0.8"

View file

@ -8,49 +8,30 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use core_traits::{ use core_traits::{TypedValue, ValueType, ValueTypeSet};
ValueType,
ValueTypeSet,
TypedValue,
};
use mentat_core::{ use mentat_core::{HasSchema, SQLValueType, Schema};
HasSchema,
Schema,
SQLValueType,
};
use edn::query::{ use edn::query::{FnArg, NonIntegerConstant, Variable};
FnArg,
NonIntegerConstant,
Variable,
};
use clauses::{ use clauses::ConjoiningClauses;
ConjoiningClauses,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, Result};
AlgebrizerError,
Result,
};
use types::{ use types::EmptyBecause;
EmptyBecause,
};
macro_rules! coerce_to_typed_value { macro_rules! coerce_to_typed_value {
($var: ident, $val: ident, $types: expr, $type: path, $constructor: path) => { { ($var: ident, $val: ident, $types: expr, $type: path, $constructor: path) => {{
Ok(if !$types.contains($type) { Ok(if !$types.contains($type) {
Impossible(EmptyBecause::TypeMismatch { Impossible(EmptyBecause::TypeMismatch {
var: $var.clone(), var: $var.clone(),
existing: $types, existing: $types,
desired: ValueTypeSet::of_one($type), desired: ValueTypeSet::of_one($type),
}) })
} else { } else {
Val($constructor($val).into()) Val($constructor($val).into())
}) })
} } }};
} }
pub(crate) trait ValueTypes { pub(crate) trait ValueTypes {
@ -60,43 +41,48 @@ pub(crate) trait ValueTypes {
impl ValueTypes for FnArg { impl ValueTypes for FnArg {
fn potential_types(&self, schema: &Schema) -> Result<ValueTypeSet> { fn potential_types(&self, schema: &Schema) -> Result<ValueTypeSet> {
Ok(match self { Ok(match self {
&FnArg::EntidOrInteger(x) => { &FnArg::EntidOrInteger(x) => {
if ValueType::Ref.accommodates_integer(x) { if ValueType::Ref.accommodates_integer(x) {
// TODO: also see if it's a valid entid? // TODO: also see if it's a valid entid?
ValueTypeSet::of_longs() ValueTypeSet::of_longs()
} else { } else {
ValueTypeSet::of_one(ValueType::Long) ValueTypeSet::of_one(ValueType::Long)
} }
}, }
&FnArg::IdentOrKeyword(ref x) => { &FnArg::IdentOrKeyword(ref x) => {
if schema.get_entid(x).is_some() { if schema.get_entid(x).is_some() {
ValueTypeSet::of_keywords() ValueTypeSet::of_keywords()
} else { } else {
ValueTypeSet::of_one(ValueType::Keyword) ValueTypeSet::of_one(ValueType::Keyword)
} }
}, }
&FnArg::Variable(_) => { &FnArg::Variable(_) => ValueTypeSet::any(),
ValueTypeSet::any()
},
&FnArg::Constant(NonIntegerConstant::BigInteger(_)) => { &FnArg::Constant(NonIntegerConstant::BigInteger(_)) => {
// Not yet implemented. // Not yet implemented.
bail!(AlgebrizerError::UnsupportedArgument) bail!(AlgebrizerError::UnsupportedArgument)
}, }
// These don't make sense here. TODO: split FnArg into scalar and non-scalar… // These don't make sense here. TODO: split FnArg into scalar and non-scalar…
&FnArg::Vector(_) | &FnArg::Vector(_) | &FnArg::SrcVar(_) => bail!(AlgebrizerError::UnsupportedArgument),
&FnArg::SrcVar(_) => bail!(AlgebrizerError::UnsupportedArgument),
// These are all straightforward. // These are all straightforward.
&FnArg::Constant(NonIntegerConstant::Boolean(_)) => ValueTypeSet::of_one(ValueType::Boolean), &FnArg::Constant(NonIntegerConstant::Boolean(_)) => {
&FnArg::Constant(NonIntegerConstant::Instant(_)) => ValueTypeSet::of_one(ValueType::Instant), ValueTypeSet::of_one(ValueType::Boolean)
&FnArg::Constant(NonIntegerConstant::Uuid(_)) => ValueTypeSet::of_one(ValueType::Uuid), }
&FnArg::Constant(NonIntegerConstant::Float(_)) => ValueTypeSet::of_one(ValueType::Double), &FnArg::Constant(NonIntegerConstant::Instant(_)) => {
&FnArg::Constant(NonIntegerConstant::Text(_)) => ValueTypeSet::of_one(ValueType::String), ValueTypeSet::of_one(ValueType::Instant)
}) }
&FnArg::Constant(NonIntegerConstant::Uuid(_)) => ValueTypeSet::of_one(ValueType::Uuid),
&FnArg::Constant(NonIntegerConstant::Float(_)) => {
ValueTypeSet::of_one(ValueType::Double)
}
&FnArg::Constant(NonIntegerConstant::Text(_)) => {
ValueTypeSet::of_one(ValueType::String)
}
})
} }
} }
@ -111,7 +97,13 @@ impl ConjoiningClauses {
/// The conversion depends on, and can fail because of: /// The conversion depends on, and can fail because of:
/// - Existing known types of a variable to which this arg will be bound. /// - Existing known types of a variable to which this arg will be bound.
/// - Existing bindings of a variable `FnArg`. /// - Existing bindings of a variable `FnArg`.
pub(crate) fn typed_value_from_arg<'s>(&self, schema: &'s Schema, var: &Variable, arg: FnArg, known_types: ValueTypeSet) -> Result<ValueConversion> { pub(crate) fn typed_value_from_arg<'s>(
&self,
schema: &'s Schema,
var: &Variable,
arg: FnArg,
known_types: ValueTypeSet,
) -> Result<ValueConversion> {
use self::ValueConversion::*; use self::ValueConversion::*;
if known_types.is_empty() { if known_types.is_empty() {
// If this happens, it likely means the pattern has already failed! // If this happens, it likely means the pattern has already failed!
@ -132,22 +124,24 @@ impl ConjoiningClauses {
match arg { match arg {
// Longs are potentially ambiguous: they might be longs or entids. // Longs are potentially ambiguous: they might be longs or entids.
FnArg::EntidOrInteger(x) => { FnArg::EntidOrInteger(x) => {
match (ValueType::Ref.accommodates_integer(x), match (
constrained_types.contains(ValueType::Ref), ValueType::Ref.accommodates_integer(x),
constrained_types.contains(ValueType::Long)) { constrained_types.contains(ValueType::Ref),
constrained_types.contains(ValueType::Long),
) {
(true, true, true) => { (true, true, true) => {
// Ambiguous: this arg could be an entid or a long. // Ambiguous: this arg could be an entid or a long.
// We default to long. // We default to long.
Ok(Val(TypedValue::Long(x))) Ok(Val(TypedValue::Long(x)))
}, }
(true, true, false) => { (true, true, false) => {
// This can only be a ref. // This can only be a ref.
Ok(Val(TypedValue::Ref(x))) Ok(Val(TypedValue::Ref(x)))
}, }
(_, false, true) => { (_, false, true) => {
// This can only be a long. // This can only be a long.
Ok(Val(TypedValue::Long(x))) Ok(Val(TypedValue::Long(x)))
}, }
(false, true, _) => { (false, true, _) => {
// This isn't a valid ref, but that's the type to which this must conform! // This isn't a valid ref, but that's the type to which this must conform!
Ok(Impossible(EmptyBecause::TypeMismatch { Ok(Impossible(EmptyBecause::TypeMismatch {
@ -155,7 +149,7 @@ impl ConjoiningClauses {
existing: known_types, existing: known_types,
desired: ValueTypeSet::of_longs(), desired: ValueTypeSet::of_longs(),
})) }))
}, }
(_, false, false) => { (_, false, false) => {
// Non-overlapping type sets. // Non-overlapping type sets.
Ok(Impossible(EmptyBecause::TypeMismatch { Ok(Impossible(EmptyBecause::TypeMismatch {
@ -163,38 +157,36 @@ impl ConjoiningClauses {
existing: known_types, existing: known_types,
desired: ValueTypeSet::of_longs(), desired: ValueTypeSet::of_longs(),
})) }))
}, }
} }
}, }
// If you definitely want to look up an ident, do it before running the query. // If you definitely want to look up an ident, do it before running the query.
FnArg::IdentOrKeyword(x) => { FnArg::IdentOrKeyword(x) => {
match (constrained_types.contains(ValueType::Ref), match (
constrained_types.contains(ValueType::Keyword)) { constrained_types.contains(ValueType::Ref),
constrained_types.contains(ValueType::Keyword),
) {
(true, true) => { (true, true) => {
// Ambiguous: this could be a keyword or an ident. // Ambiguous: this could be a keyword or an ident.
// Default to keyword. // Default to keyword.
Ok(Val(x.into())) Ok(Val(x.into()))
}, }
(true, false) => { (true, false) => {
// This can only be an ident. Look it up! // This can only be an ident. Look it up!
match schema.get_entid(&x).map(|k| k.into()) { match schema.get_entid(&x).map(|k| k.into()) {
Some(e) => Ok(Val(e)), Some(e) => Ok(Val(e)),
None => Ok(Impossible(EmptyBecause::UnresolvedIdent(x.clone()))), None => Ok(Impossible(EmptyBecause::UnresolvedIdent(x.clone()))),
} }
}, }
(false, true) => { (false, true) => Ok(Val(TypedValue::Keyword(x.into()))),
Ok(Val(TypedValue::Keyword(x.into()))) (false, false) => Ok(Impossible(EmptyBecause::TypeMismatch {
}, var: var.clone(),
(false, false) => { existing: known_types,
Ok(Impossible(EmptyBecause::TypeMismatch { desired: ValueTypeSet::of_keywords(),
var: var.clone(), })),
existing: known_types,
desired: ValueTypeSet::of_keywords(),
}))
},
} }
}, }
FnArg::Variable(in_var) => { FnArg::Variable(in_var) => {
// TODO: technically you could ground an existing variable inside the query…. // TODO: technically you could ground an existing variable inside the query….
@ -209,33 +201,32 @@ impl ConjoiningClauses {
// This is a restriction we will eventually relax: we don't yet have a way // This is a restriction we will eventually relax: we don't yet have a way
// to collect variables as part of a computed table or substitution. // to collect variables as part of a computed table or substitution.
bail!(AlgebrizerError::UnboundVariable((*in_var.0).clone())) bail!(AlgebrizerError::UnboundVariable((*in_var.0).clone()))
}, }
} }
}, }
// This isn't implemented yet. // This isn't implemented yet.
FnArg::Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(), FnArg::Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(),
// These don't make sense here. // These don't make sense here.
FnArg::Vector(_) | FnArg::Vector(_) | FnArg::SrcVar(_) => bail!(AlgebrizerError::InvalidGroundConstant),
FnArg::SrcVar(_) => bail!(AlgebrizerError::InvalidGroundConstant),
// These are all straightforward. // These are all straightforward.
FnArg::Constant(NonIntegerConstant::Boolean(x)) => { FnArg::Constant(NonIntegerConstant::Boolean(x)) => {
coerce_to_typed_value!(var, x, known_types, ValueType::Boolean, TypedValue::Boolean) coerce_to_typed_value!(var, x, known_types, ValueType::Boolean, TypedValue::Boolean)
}, }
FnArg::Constant(NonIntegerConstant::Instant(x)) => { FnArg::Constant(NonIntegerConstant::Instant(x)) => {
coerce_to_typed_value!(var, x, known_types, ValueType::Instant, TypedValue::Instant) coerce_to_typed_value!(var, x, known_types, ValueType::Instant, TypedValue::Instant)
}, }
FnArg::Constant(NonIntegerConstant::Uuid(x)) => { FnArg::Constant(NonIntegerConstant::Uuid(x)) => {
coerce_to_typed_value!(var, x, known_types, ValueType::Uuid, TypedValue::Uuid) coerce_to_typed_value!(var, x, known_types, ValueType::Uuid, TypedValue::Uuid)
}, }
FnArg::Constant(NonIntegerConstant::Float(x)) => { FnArg::Constant(NonIntegerConstant::Float(x)) => {
coerce_to_typed_value!(var, x, known_types, ValueType::Double, TypedValue::Double) coerce_to_typed_value!(var, x, known_types, ValueType::Double, TypedValue::Double)
}, }
FnArg::Constant(NonIntegerConstant::Text(x)) => { FnArg::Constant(NonIntegerConstant::Text(x)) => {
coerce_to_typed_value!(var, x, known_types, ValueType::String, TypedValue::String) coerce_to_typed_value!(var, x, known_types, ValueType::String, TypedValue::String)
}, }
} }
} }
} }

View file

@ -8,46 +8,21 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use core_traits::{ use core_traits::{TypedValue, ValueType};
ValueType,
TypedValue,
};
use mentat_core::{ use mentat_core::HasSchema;
HasSchema,
};
use mentat_core::util::Either; use mentat_core::util::Either;
use edn::query::{ use edn::query::{Binding, FnArg, NonIntegerConstant, SrcVar, VariableOrPlaceholder, WhereFn};
Binding,
FnArg,
NonIntegerConstant,
SrcVar,
VariableOrPlaceholder,
WhereFn,
};
use clauses::{ use clauses::ConjoiningClauses;
ConjoiningClauses,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, BindingError, Result};
AlgebrizerError,
BindingError,
Result,
};
use types::{ use types::{
Column, Column, ColumnConstraint, DatomsColumn, DatomsTable, EmptyBecause, FulltextColumn,
ColumnConstraint, QualifiedAlias, QueryValue, SourceAlias,
DatomsColumn,
DatomsTable,
EmptyBecause,
FulltextColumn,
QualifiedAlias,
QueryValue,
SourceAlias,
}; };
use Known; use Known;
@ -56,17 +31,27 @@ impl ConjoiningClauses {
#[allow(unused_variables)] #[allow(unused_variables)]
pub(crate) fn apply_fulltext(&mut self, known: Known, where_fn: WhereFn) -> Result<()> { pub(crate) fn apply_fulltext(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
if where_fn.args.len() != 3 { if where_fn.args.len() != 3 {
bail!(AlgebrizerError::InvalidNumberOfArguments(where_fn.operator.clone(), where_fn.args.len(), 3)); bail!(AlgebrizerError::InvalidNumberOfArguments(
where_fn.operator.clone(),
where_fn.args.len(),
3
));
} }
if where_fn.binding.is_empty() { if where_fn.binding.is_empty() {
// The binding must introduce at least one bound variable. // The binding must introduce at least one bound variable.
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable)); bail!(AlgebrizerError::InvalidBinding(
where_fn.operator.clone(),
BindingError::NoBoundVariable
));
} }
if !where_fn.binding.is_valid() { if !where_fn.binding.is_valid() {
// The binding must not duplicate bound variables. // The binding must not duplicate bound variables.
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); bail!(AlgebrizerError::InvalidBinding(
where_fn.operator.clone(),
BindingError::RepeatedBoundVariable
));
} }
// We should have exactly four bindings. Destructure them now. // We should have exactly four bindings. Destructure them now.
@ -74,31 +59,45 @@ impl ConjoiningClauses {
Binding::BindRel(bindings) => { Binding::BindRel(bindings) => {
let bindings_count = bindings.len(); let bindings_count = bindings.len();
if bindings_count < 1 || bindings_count > 4 { if bindings_count < 1 || bindings_count > 4 {
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), bail!(AlgebrizerError::InvalidBinding(
where_fn.operator.clone(),
BindingError::InvalidNumberOfBindings { BindingError::InvalidNumberOfBindings {
number: bindings.len(), number: bindings.len(),
expected: 4, expected: 4,
}) }
); ));
} }
bindings bindings
}, }
Binding::BindScalar(_) | Binding::BindScalar(_) | Binding::BindTuple(_) | Binding::BindColl(_) => {
Binding::BindTuple(_) | bail!(AlgebrizerError::InvalidBinding(
Binding::BindColl(_) => bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRel)), where_fn.operator.clone(),
BindingError::ExpectedBindRel
))
}
}; };
let mut bindings = bindings.into_iter(); let mut bindings = bindings.into_iter();
let b_entity = bindings.next().unwrap(); let b_entity = bindings.next().unwrap();
let b_value = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); let b_value = bindings
let b_tx = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); .next()
let b_score = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); .unwrap_or(VariableOrPlaceholder::Placeholder);
let b_tx = bindings
.next()
.unwrap_or(VariableOrPlaceholder::Placeholder);
let b_score = bindings
.next()
.unwrap_or(VariableOrPlaceholder::Placeholder);
let mut args = where_fn.args.into_iter(); let mut args = where_fn.args.into_iter();
// TODO: process source variables. // TODO: process source variables.
match args.next().unwrap() { match args.next().unwrap() {
FnArg::SrcVar(SrcVar::DefaultSrc) => {}, FnArg::SrcVar(SrcVar::DefaultSrc) => {}
_ => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "source variable", 0)), _ => bail!(AlgebrizerError::InvalidArgument(
where_fn.operator.clone(),
"source variable",
0
)),
} }
let schema = known.schema; let schema = known.schema;
@ -117,25 +116,34 @@ impl ConjoiningClauses {
// TODO: allow non-constant attributes. // TODO: allow non-constant attributes.
match self.bound_value(&v) { match self.bound_value(&v) {
Some(TypedValue::Ref(entid)) => Some(entid), Some(TypedValue::Ref(entid)) => Some(entid),
Some(tv) => { Some(tv) => bail!(AlgebrizerError::InputTypeDisagreement(
bail!(AlgebrizerError::InputTypeDisagreement(v.name().clone(), ValueType::Ref, tv.value_type())) v.name().clone(),
}, ValueType::Ref,
None => { tv.value_type()
bail!(AlgebrizerError::UnboundVariable((*v.0).clone())) )),
} None => bail!(AlgebrizerError::UnboundVariable((*v.0).clone())),
} }
}, }
_ => None, _ => None,
}; };
// An unknown ident, or an entity that isn't present in the store, or isn't a fulltext // An unknown ident, or an entity that isn't present in the store, or isn't a fulltext
// attribute, is likely enough to be a coding error that we choose to bail instead of // attribute, is likely enough to be a coding error that we choose to bail instead of
// marking the pattern as known-empty. // marking the pattern as known-empty.
let a = a.ok_or(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "attribute", 1))?; let a = a.ok_or(AlgebrizerError::InvalidArgument(
let attribute = schema.attribute_for_entid(a) where_fn.operator.clone(),
.cloned() "attribute",
.ok_or(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), 1,
"attribute", 1))?; ))?;
let attribute =
schema
.attribute_for_entid(a)
.cloned()
.ok_or(AlgebrizerError::InvalidArgument(
where_fn.operator.clone(),
"attribute",
1,
))?;
if !attribute.fulltext { if !attribute.fulltext {
// We can never get results from a non-fulltext attribute! // We can never get results from a non-fulltext attribute!
@ -149,16 +157,27 @@ impl ConjoiningClauses {
// We do a fulltext lookup by joining the fulltext values table against datoms -- just // We do a fulltext lookup by joining the fulltext values table against datoms -- just
// like applying a pattern, but two tables contribute instead of one. // like applying a pattern, but two tables contribute instead of one.
self.from.push(SourceAlias(DatomsTable::FulltextValues, fulltext_values_alias.clone())); self.from.push(SourceAlias(
self.from.push(SourceAlias(DatomsTable::Datoms, datoms_table_alias.clone())); DatomsTable::FulltextValues,
fulltext_values_alias.clone(),
));
self.from
.push(SourceAlias(DatomsTable::Datoms, datoms_table_alias.clone()));
// TODO: constrain the type in the more general cases (e.g., `a` is a var). // TODO: constrain the type in the more general cases (e.g., `a` is a var).
self.constrain_attribute(datoms_table_alias.clone(), a); self.constrain_attribute(datoms_table_alias.clone(), a);
// Join the datoms table to the fulltext values table. // Join the datoms table to the fulltext values table.
self.wheres.add_intersection(ColumnConstraint::Equals( self.wheres.add_intersection(ColumnConstraint::Equals(
QualifiedAlias(datoms_table_alias.clone(), Column::Fixed(DatomsColumn::Value)), QualifiedAlias(
QueryValue::Column(QualifiedAlias(fulltext_values_alias.clone(), Column::Fulltext(FulltextColumn::Rowid))))); datoms_table_alias.clone(),
Column::Fixed(DatomsColumn::Value),
),
QueryValue::Column(QualifiedAlias(
fulltext_values_alias.clone(),
Column::Fulltext(FulltextColumn::Rowid),
)),
));
// `search` is either text or a variable. // `search` is either text or a variable.
// If it's simple text, great. // If it's simple text, great.
@ -167,18 +186,24 @@ impl ConjoiningClauses {
// - It's not already bound, but it's a defined input of type Text. Not yet implemented: TODO. // - It's not already bound, but it's a defined input of type Text. Not yet implemented: TODO.
// - It's not bound. The query cannot be algebrized. // - It's not bound. The query cannot be algebrized.
let search: Either<TypedValue, QualifiedAlias> = match args.next().unwrap() { let search: Either<TypedValue, QualifiedAlias> = match args.next().unwrap() {
FnArg::Constant(NonIntegerConstant::Text(s)) => { FnArg::Constant(NonIntegerConstant::Text(s)) => Either::Left(TypedValue::String(s)),
Either::Left(TypedValue::String(s))
},
FnArg::Variable(in_var) => { FnArg::Variable(in_var) => {
match self.bound_value(&in_var) { match self.bound_value(&in_var) {
Some(t @ TypedValue::String(_)) => Either::Left(t), Some(t @ TypedValue::String(_)) => Either::Left(t),
Some(_) => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "string", 2)), Some(_) => bail!(AlgebrizerError::InvalidArgument(
where_fn.operator.clone(),
"string",
2
)),
None => { None => {
// Regardless of whether we'll be providing a string later, or the value // Regardless of whether we'll be providing a string later, or the value
// comes from a column, it must be a string. // comes from a column, it must be a string.
if self.known_type(&in_var) != Some(ValueType::String) { if self.known_type(&in_var) != Some(ValueType::String) {
bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "string", 2)) bail!(AlgebrizerError::InvalidArgument(
where_fn.operator.clone(),
"string",
2
))
} }
if self.input_variables.contains(&in_var) { if self.input_variables.contains(&in_var) {
@ -188,18 +213,24 @@ impl ConjoiningClauses {
} else { } else {
// It must be bound earlier in the query. We already established that // It must be bound earlier in the query. We already established that
// it must be a string column. // it must be a string column.
if let Some(binding) = self.column_bindings if let Some(binding) = self
.get(&in_var) .column_bindings
.and_then(|bindings| bindings.get(0).cloned()) { .get(&in_var)
.and_then(|bindings| bindings.get(0).cloned())
{
Either::Right(binding) Either::Right(binding)
} else { } else {
bail!(AlgebrizerError::UnboundVariable((*in_var.0).clone())) bail!(AlgebrizerError::UnboundVariable((*in_var.0).clone()))
} }
} }
}, }
} }
}, }
_ => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "string", 2)), _ => bail!(AlgebrizerError::InvalidArgument(
where_fn.operator.clone(),
"string",
2
)),
}; };
let qv = match search { let qv = match search {
@ -207,9 +238,13 @@ impl ConjoiningClauses {
Either::Right(qa) => QueryValue::Column(qa), Either::Right(qa) => QueryValue::Column(qa),
}; };
let constraint = ColumnConstraint::Matches(QualifiedAlias(fulltext_values_alias.clone(), let constraint = ColumnConstraint::Matches(
Column::Fulltext(FulltextColumn::Text)), QualifiedAlias(
qv); fulltext_values_alias.clone(),
Column::Fulltext(FulltextColumn::Text),
),
qv,
);
self.wheres.add_intersection(constraint); self.wheres.add_intersection(constraint);
if let VariableOrPlaceholder::Variable(ref var) = b_entity { if let VariableOrPlaceholder::Variable(ref var) = b_entity {
@ -219,7 +254,12 @@ impl ConjoiningClauses {
return Ok(()); return Ok(());
} }
self.bind_column_to_var(schema, datoms_table_alias.clone(), DatomsColumn::Entity, var.clone()); self.bind_column_to_var(
schema,
datoms_table_alias.clone(),
DatomsColumn::Entity,
var.clone(),
);
} }
if let VariableOrPlaceholder::Variable(ref var) = b_value { if let VariableOrPlaceholder::Variable(ref var) = b_value {
@ -229,7 +269,12 @@ impl ConjoiningClauses {
return Ok(()); return Ok(());
} }
self.bind_column_to_var(schema, fulltext_values_alias.clone(), Column::Fulltext(FulltextColumn::Text), var.clone()); self.bind_column_to_var(
schema,
fulltext_values_alias.clone(),
Column::Fulltext(FulltextColumn::Text),
var.clone(),
);
} }
if let VariableOrPlaceholder::Variable(ref var) = b_tx { if let VariableOrPlaceholder::Variable(ref var) = b_tx {
@ -239,7 +284,12 @@ impl ConjoiningClauses {
return Ok(()); return Ok(());
} }
self.bind_column_to_var(schema, datoms_table_alias.clone(), DatomsColumn::Tx, var.clone()); self.bind_column_to_var(
schema,
datoms_table_alias.clone(),
DatomsColumn::Tx,
var.clone(),
);
} }
if let VariableOrPlaceholder::Variable(ref var) = b_score { if let VariableOrPlaceholder::Variable(ref var) = b_score {
@ -248,7 +298,10 @@ impl ConjoiningClauses {
// We do not allow the score to be bound. // We do not allow the score to be bound.
if self.value_bindings.contains_key(var) || self.input_variables.contains(var) { if self.value_bindings.contains_key(var) || self.input_variables.contains(var) {
bail!(AlgebrizerError::InvalidBinding(var.name(), BindingError::UnexpectedBinding)); bail!(AlgebrizerError::InvalidBinding(
var.name(),
BindingError::UnexpectedBinding
));
} }
// We bind the value ourselves. This handily takes care of substituting into existing uses. // We bind the value ourselves. This handily takes care of substituting into existing uses.
@ -264,27 +317,13 @@ impl ConjoiningClauses {
mod testing { mod testing {
use super::*; use super::*;
use core_traits::{ use core_traits::{Attribute, ValueType};
Attribute,
ValueType,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::query::{ use edn::query::{Binding, FnArg, Keyword, PlainSymbol, Variable};
Binding,
FnArg,
Keyword,
PlainSymbol,
Variable,
};
use clauses::{ use clauses::{add_attribute, associate_ident};
add_attribute,
associate_ident,
};
#[test] #[test]
fn test_apply_fulltext() { fn test_apply_fulltext() {
@ -292,35 +331,49 @@ mod testing {
let mut schema = Schema::default(); let mut schema = Schema::default();
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 101); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 101);
add_attribute(&mut schema, 101, Attribute { add_attribute(
value_type: ValueType::String, &mut schema,
fulltext: false, 101,
..Default::default() Attribute {
}); value_type: ValueType::String,
fulltext: false,
..Default::default()
},
);
associate_ident(&mut schema, Keyword::namespaced("foo", "fts"), 100); associate_ident(&mut schema, Keyword::namespaced("foo", "fts"), 100);
add_attribute(&mut schema, 100, Attribute { add_attribute(
value_type: ValueType::String, &mut schema,
index: true, 100,
fulltext: true, Attribute {
..Default::default() value_type: ValueType::String,
}); index: true,
fulltext: true,
..Default::default()
},
);
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let op = PlainSymbol::plain("fulltext"); let op = PlainSymbol::plain("fulltext");
cc.apply_fulltext(known, WhereFn { cc.apply_fulltext(
operator: op, known,
args: vec![ WhereFn {
FnArg::SrcVar(SrcVar::DefaultSrc), operator: op,
FnArg::IdentOrKeyword(Keyword::namespaced("foo", "fts")), args: vec![
FnArg::Constant("needle".into()), FnArg::SrcVar(SrcVar::DefaultSrc),
], FnArg::IdentOrKeyword(Keyword::namespaced("foo", "fts")),
binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")), FnArg::Constant("needle".into()),
VariableOrPlaceholder::Variable(Variable::from_valid_name("?value")), ],
VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")), binding: Binding::BindRel(vec![
VariableOrPlaceholder::Variable(Variable::from_valid_name("?score"))]), VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")),
}).expect("to be able to apply_fulltext"); VariableOrPlaceholder::Variable(Variable::from_valid_name("?value")),
VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")),
VariableOrPlaceholder::Variable(Variable::from_valid_name("?score")),
]),
},
)
.expect("to be able to apply_fulltext");
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
@ -331,54 +384,136 @@ mod testing {
let clauses = cc.wheres; let clauses = cc.wheres;
assert_eq!(clauses.len(), 3); assert_eq!(clauses.len(), 3);
assert_eq!(clauses.0[0], ColumnConstraint::Equals(QualifiedAlias("datoms01".to_string(), Column::Fixed(DatomsColumn::Attribute)), assert_eq!(
QueryValue::Entid(100)).into()); clauses.0[0],
assert_eq!(clauses.0[1], ColumnConstraint::Equals(QualifiedAlias("datoms01".to_string(), Column::Fixed(DatomsColumn::Value)), ColumnConstraint::Equals(
QueryValue::Column(QualifiedAlias("fulltext_values00".to_string(), Column::Fulltext(FulltextColumn::Rowid)))).into()); QualifiedAlias(
assert_eq!(clauses.0[2], ColumnConstraint::Matches(QualifiedAlias("fulltext_values00".to_string(), Column::Fulltext(FulltextColumn::Text)), "datoms01".to_string(),
QueryValue::TypedValue("needle".into())).into()); Column::Fixed(DatomsColumn::Attribute)
),
QueryValue::Entid(100)
)
.into()
);
assert_eq!(
clauses.0[1],
ColumnConstraint::Equals(
QualifiedAlias("datoms01".to_string(), Column::Fixed(DatomsColumn::Value)),
QueryValue::Column(QualifiedAlias(
"fulltext_values00".to_string(),
Column::Fulltext(FulltextColumn::Rowid)
))
)
.into()
);
assert_eq!(
clauses.0[2],
ColumnConstraint::Matches(
QualifiedAlias(
"fulltext_values00".to_string(),
Column::Fulltext(FulltextColumn::Text)
),
QueryValue::TypedValue("needle".into())
)
.into()
);
let bindings = cc.column_bindings; let bindings = cc.column_bindings;
assert_eq!(bindings.len(), 3); assert_eq!(bindings.len(), 3);
assert_eq!(bindings.get(&Variable::from_valid_name("?entity")).expect("column binding for ?entity").clone(), assert_eq!(
vec![QualifiedAlias("datoms01".to_string(), Column::Fixed(DatomsColumn::Entity))]); bindings
assert_eq!(bindings.get(&Variable::from_valid_name("?value")).expect("column binding for ?value").clone(), .get(&Variable::from_valid_name("?entity"))
vec![QualifiedAlias("fulltext_values00".to_string(), Column::Fulltext(FulltextColumn::Text))]); .expect("column binding for ?entity")
assert_eq!(bindings.get(&Variable::from_valid_name("?tx")).expect("column binding for ?tx").clone(), .clone(),
vec![QualifiedAlias("datoms01".to_string(), Column::Fixed(DatomsColumn::Tx))]); vec![QualifiedAlias(
"datoms01".to_string(),
Column::Fixed(DatomsColumn::Entity)
)]
);
assert_eq!(
bindings
.get(&Variable::from_valid_name("?value"))
.expect("column binding for ?value")
.clone(),
vec![QualifiedAlias(
"fulltext_values00".to_string(),
Column::Fulltext(FulltextColumn::Text)
)]
);
assert_eq!(
bindings
.get(&Variable::from_valid_name("?tx"))
.expect("column binding for ?tx")
.clone(),
vec![QualifiedAlias(
"datoms01".to_string(),
Column::Fixed(DatomsColumn::Tx)
)]
);
// Score is a value binding. // Score is a value binding.
let values = cc.value_bindings; let values = cc.value_bindings;
assert_eq!(values.get(&Variable::from_valid_name("?score")).expect("column binding for ?score").clone(), assert_eq!(
TypedValue::Double(0.0.into())); values
.get(&Variable::from_valid_name("?score"))
.expect("column binding for ?score")
.clone(),
TypedValue::Double(0.0.into())
);
let known_types = cc.known_types; let known_types = cc.known_types;
assert_eq!(known_types.len(), 4); assert_eq!(known_types.len(), 4);
assert_eq!(known_types.get(&Variable::from_valid_name("?entity")).expect("known types for ?entity").clone(), assert_eq!(
vec![ValueType::Ref].into_iter().collect()); known_types
assert_eq!(known_types.get(&Variable::from_valid_name("?value")).expect("known types for ?value").clone(), .get(&Variable::from_valid_name("?entity"))
vec![ValueType::String].into_iter().collect()); .expect("known types for ?entity")
assert_eq!(known_types.get(&Variable::from_valid_name("?tx")).expect("known types for ?tx").clone(), .clone(),
vec![ValueType::Ref].into_iter().collect()); vec![ValueType::Ref].into_iter().collect()
assert_eq!(known_types.get(&Variable::from_valid_name("?score")).expect("known types for ?score").clone(), );
vec![ValueType::Double].into_iter().collect()); assert_eq!(
known_types
.get(&Variable::from_valid_name("?value"))
.expect("known types for ?value")
.clone(),
vec![ValueType::String].into_iter().collect()
);
assert_eq!(
known_types
.get(&Variable::from_valid_name("?tx"))
.expect("known types for ?tx")
.clone(),
vec![ValueType::Ref].into_iter().collect()
);
assert_eq!(
known_types
.get(&Variable::from_valid_name("?score"))
.expect("known types for ?score")
.clone(),
vec![ValueType::Double].into_iter().collect()
);
let mut cc = ConjoiningClauses::default(); let mut cc = ConjoiningClauses::default();
let op = PlainSymbol::plain("fulltext"); let op = PlainSymbol::plain("fulltext");
cc.apply_fulltext(known, WhereFn { cc.apply_fulltext(
operator: op, known,
args: vec![ WhereFn {
FnArg::SrcVar(SrcVar::DefaultSrc), operator: op,
FnArg::IdentOrKeyword(Keyword::namespaced("foo", "bar")), args: vec![
FnArg::Constant("needle".into()), FnArg::SrcVar(SrcVar::DefaultSrc),
], FnArg::IdentOrKeyword(Keyword::namespaced("foo", "bar")),
binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")), FnArg::Constant("needle".into()),
VariableOrPlaceholder::Variable(Variable::from_valid_name("?value")), ],
VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")), binding: Binding::BindRel(vec![
VariableOrPlaceholder::Variable(Variable::from_valid_name("?score"))]), VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")),
}).expect("to be able to apply_fulltext"); VariableOrPlaceholder::Variable(Variable::from_valid_name("?value")),
VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")),
VariableOrPlaceholder::Variable(Variable::from_valid_name("?score")),
]),
},
)
.expect("to be able to apply_fulltext");
// It's not a fulltext attribute, so the CC cannot yield results. // It's not a fulltext attribute, so the CC cannot yield results.
assert!(cc.is_known_empty()); assert!(cc.is_known_empty());

View file

@ -8,43 +8,19 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use core_traits::{ use core_traits::{TypedValue, ValueType, ValueTypeSet};
ValueType,
ValueTypeSet,
TypedValue,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::query::{ use edn::query::{Binding, FnArg, Variable, VariableOrPlaceholder, WhereFn};
Binding,
FnArg,
Variable,
VariableOrPlaceholder,
WhereFn,
};
use clauses::{ use clauses::{ConjoiningClauses, PushComputed};
ConjoiningClauses,
PushComputed,
};
use clauses::convert::ValueConversion; use clauses::convert::ValueConversion;
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, BindingError, Result};
AlgebrizerError,
BindingError,
Result,
};
use types::{ use types::{ComputedTable, EmptyBecause, SourceAlias, VariableColumn};
ComputedTable,
EmptyBecause,
SourceAlias,
VariableColumn,
};
use Known; use Known;
@ -53,7 +29,13 @@ impl ConjoiningClauses {
/// the provided types. /// the provided types.
/// Construct a computed table to yield this relation. /// Construct a computed table to yield this relation.
/// This function will panic if some invariants are not met. /// This function will panic if some invariants are not met.
fn collect_named_bindings<'s>(&mut self, schema: &'s Schema, names: Vec<Variable>, types: Vec<ValueType>, values: Vec<TypedValue>) { fn collect_named_bindings<'s>(
&mut self,
schema: &'s Schema,
names: Vec<Variable>,
types: Vec<ValueType>,
values: Vec<TypedValue>,
) {
if values.is_empty() { if values.is_empty() {
return; return;
} }
@ -61,7 +43,7 @@ impl ConjoiningClauses {
assert!(!names.is_empty()); assert!(!names.is_empty());
assert_eq!(names.len(), types.len()); assert_eq!(names.len(), types.len());
assert!(values.len() >= names.len()); assert!(values.len() >= names.len());
assert_eq!(values.len() % names.len(), 0); // It's an exact multiple. assert_eq!(values.len() % names.len(), 0); // It's an exact multiple.
let named_values = ComputedTable::NamedValues { let named_values = ComputedTable::NamedValues {
names: names.clone(), names: names.clone(),
@ -74,13 +56,23 @@ impl ConjoiningClauses {
// Stitch the computed table into column_bindings, so we get cross-linking. // Stitch the computed table into column_bindings, so we get cross-linking.
for (name, ty) in names.iter().zip(types.into_iter()) { for (name, ty) in names.iter().zip(types.into_iter()) {
self.constrain_var_to_type(name.clone(), ty); self.constrain_var_to_type(name.clone(), ty);
self.bind_column_to_var(schema, alias.clone(), VariableColumn::Variable(name.clone()), name.clone()); self.bind_column_to_var(
schema,
alias.clone(),
VariableColumn::Variable(name.clone()),
name.clone(),
);
} }
self.from.push(SourceAlias(table, alias)); self.from.push(SourceAlias(table, alias));
} }
fn apply_ground_place<'s>(&mut self, schema: &'s Schema, var: VariableOrPlaceholder, arg: FnArg) -> Result<()> { fn apply_ground_place<'s>(
&mut self,
schema: &'s Schema,
var: VariableOrPlaceholder,
arg: FnArg,
) -> Result<()> {
match var { match var {
VariableOrPlaceholder::Placeholder => Ok(()), VariableOrPlaceholder::Placeholder => Ok(()),
VariableOrPlaceholder::Variable(var) => self.apply_ground_var(schema, var, arg), VariableOrPlaceholder::Variable(var) => self.apply_ground_var(schema, var, arg),
@ -89,14 +81,19 @@ impl ConjoiningClauses {
/// Constrain the CC to associate the given var with the given ground argument. /// Constrain the CC to associate the given var with the given ground argument.
/// Marks known-empty on failure. /// Marks known-empty on failure.
fn apply_ground_var<'s>(&mut self, schema: &'s Schema, var: Variable, arg: FnArg) -> Result<()> { fn apply_ground_var<'s>(
&mut self,
schema: &'s Schema,
var: Variable,
arg: FnArg,
) -> Result<()> {
let known_types = self.known_type_set(&var); let known_types = self.known_type_set(&var);
match self.typed_value_from_arg(schema, &var, arg, known_types)? { match self.typed_value_from_arg(schema, &var, arg, known_types)? {
ValueConversion::Val(value) => self.apply_ground_value(var, value), ValueConversion::Val(value) => self.apply_ground_value(var, value),
ValueConversion::Impossible(because) => { ValueConversion::Impossible(because) => {
self.mark_known_empty(because); self.mark_known_empty(because);
Ok(()) Ok(())
}, }
} }
} }
@ -109,7 +106,7 @@ impl ConjoiningClauses {
existing: existing.clone(), existing: existing.clone(),
desired: value, desired: value,
}); });
return Ok(()) return Ok(());
} }
} else { } else {
self.bind_value(&var, value.clone()); self.bind_value(&var, value.clone());
@ -120,19 +117,29 @@ impl ConjoiningClauses {
pub(crate) fn apply_ground(&mut self, known: Known, where_fn: WhereFn) -> Result<()> { pub(crate) fn apply_ground(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
if where_fn.args.len() != 1 { if where_fn.args.len() != 1 {
bail!(AlgebrizerError::InvalidNumberOfArguments(where_fn.operator.clone(), where_fn.args.len(), 1)); bail!(AlgebrizerError::InvalidNumberOfArguments(
where_fn.operator.clone(),
where_fn.args.len(),
1
));
} }
let mut args = where_fn.args.into_iter(); let mut args = where_fn.args.into_iter();
if where_fn.binding.is_empty() { if where_fn.binding.is_empty() {
// The binding must introduce at least one bound variable. // The binding must introduce at least one bound variable.
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable)); bail!(AlgebrizerError::InvalidBinding(
where_fn.operator.clone(),
BindingError::NoBoundVariable
));
} }
if !where_fn.binding.is_valid() { if !where_fn.binding.is_valid() {
// The binding must not duplicate bound variables. // The binding must not duplicate bound variables.
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); bail!(AlgebrizerError::InvalidBinding(
where_fn.operator.clone(),
BindingError::RepeatedBoundVariable
));
} }
let schema = known.schema; let schema = known.schema;
@ -141,8 +148,7 @@ impl ConjoiningClauses {
// we can immediately substitute the value as a known value in the CC, additionally // we can immediately substitute the value as a known value in the CC, additionally
// generating a WHERE clause if columns have already been bound. // generating a WHERE clause if columns have already been bound.
match (where_fn.binding, args.next().unwrap()) { match (where_fn.binding, args.next().unwrap()) {
(Binding::BindScalar(var), constant) => (Binding::BindScalar(var), constant) => self.apply_ground_var(schema, var, constant),
self.apply_ground_var(schema, var, constant),
(Binding::BindTuple(places), FnArg::Vector(children)) => { (Binding::BindTuple(places), FnArg::Vector(children)) => {
// Just the same, but we bind more than one column at a time. // Just the same, but we bind more than one column at a time.
@ -151,10 +157,10 @@ impl ConjoiningClauses {
bail!(AlgebrizerError::GroundBindingsMismatch) bail!(AlgebrizerError::GroundBindingsMismatch)
} }
for (place, arg) in places.into_iter().zip(children.into_iter()) { for (place, arg) in places.into_iter().zip(children.into_iter()) {
self.apply_ground_place(schema, place, arg)? // TODO: short-circuit on impossible. self.apply_ground_place(schema, place, arg)? // TODO: short-circuit on impossible.
} }
Ok(()) Ok(())
}, }
// Collection bindings and rel bindings are similar in that they are both // Collection bindings and rel bindings are similar in that they are both
// implemented as a subquery with a projection list and a set of values. // implemented as a subquery with a projection list and a set of values.
@ -170,30 +176,32 @@ impl ConjoiningClauses {
// Check that every value has the same type. // Check that every value has the same type.
let mut accumulated_types = ValueTypeSet::none(); let mut accumulated_types = ValueTypeSet::none();
let mut skip: Option<EmptyBecause> = None; let mut skip: Option<EmptyBecause> = None;
let values = children.into_iter() let values = children
.filter_map(|arg| -> Option<Result<TypedValue>> { .into_iter()
// We need to get conversion errors out. .filter_map(|arg| -> Option<Result<TypedValue>> {
// We also want to mark known-empty on impossibilty, but // We need to get conversion errors out.
// still detect serious errors. // We also want to mark known-empty on impossibilty, but
match self.typed_value_from_arg(schema, &var, arg, known_types) { // still detect serious errors.
Ok(ValueConversion::Val(tv)) => { match self.typed_value_from_arg(schema, &var, arg, known_types) {
if accumulated_types.insert(tv.value_type()) && Ok(ValueConversion::Val(tv)) => {
!accumulated_types.is_unit() { if accumulated_types.insert(tv.value_type())
// Values not all of the same type. && !accumulated_types.is_unit()
Some(Err(AlgebrizerError::InvalidGroundConstant.into())) {
} else { // Values not all of the same type.
Some(Ok(tv)) Some(Err(AlgebrizerError::InvalidGroundConstant.into()))
} } else {
}, Some(Ok(tv))
Ok(ValueConversion::Impossible(because)) => { }
// Skip this value. }
skip = Some(because); Ok(ValueConversion::Impossible(because)) => {
None // Skip this value.
}, skip = Some(because);
Err(e) => Some(Err(e.into())), None
} }
}) Err(e) => Some(Err(e.into())),
.collect::<Result<Vec<TypedValue>>>()?; }
})
.collect::<Result<Vec<TypedValue>>>()?;
if values.is_empty() { if values.is_empty() {
let because = skip.expect("we skipped all rows for a reason"); let because = skip.expect("we skipped all rows for a reason");
@ -207,7 +215,7 @@ impl ConjoiningClauses {
self.collect_named_bindings(schema, names, types, values); self.collect_named_bindings(schema, names, types, values);
Ok(()) Ok(())
}, }
(Binding::BindRel(places), FnArg::Vector(rows)) => { (Binding::BindRel(places), FnArg::Vector(rows)) => {
if rows.is_empty() { if rows.is_empty() {
@ -216,17 +224,20 @@ impl ConjoiningClauses {
// Grab the known types to which these args must conform, and track // Grab the known types to which these args must conform, and track
// the places that won't be bound in the output. // the places that won't be bound in the output.
let template: Vec<Option<(Variable, ValueTypeSet)>> = let template: Vec<Option<(Variable, ValueTypeSet)>> = places
places.iter() .iter()
.map(|x| match x { .map(|x| match x {
&VariableOrPlaceholder::Placeholder => None, &VariableOrPlaceholder::Placeholder => None,
&VariableOrPlaceholder::Variable(ref v) => Some((v.clone(), self.known_type_set(v))), &VariableOrPlaceholder::Variable(ref v) => {
}) Some((v.clone(), self.known_type_set(v)))
.collect(); }
})
.collect();
// The expected 'width' of the matrix is the number of named variables. // The expected 'width' of the matrix is the number of named variables.
let full_width = places.len(); let full_width = places.len();
let names: Vec<Variable> = places.into_iter().filter_map(|x| x.into_var()).collect(); let names: Vec<Variable> =
places.into_iter().filter_map(|x| x.into_var()).collect();
let expected_width = names.len(); let expected_width = names.len();
let expected_rows = rows.len(); let expected_rows = rows.len();
@ -267,7 +278,7 @@ impl ConjoiningClauses {
// Skip this row. It cannot produce bindings. // Skip this row. It cannot produce bindings.
skip = Some(because); skip = Some(because);
break; break;
}, }
} }
} }
} }
@ -279,7 +290,10 @@ impl ConjoiningClauses {
} }
// Accumulate the values into the matrix and the types into the type set. // Accumulate the values into the matrix and the types into the type set.
for (val, acc) in vals.into_iter().zip(accumulated_types_for_columns.iter_mut()) { for (val, acc) in vals
.into_iter()
.zip(accumulated_types_for_columns.iter_mut())
{
let inserted = acc.insert(val.value_type()); let inserted = acc.insert(val.value_type());
if inserted && !acc.is_unit() { if inserted && !acc.is_unit() {
// Heterogeneous types. // Heterogeneous types.
@ -287,8 +301,7 @@ impl ConjoiningClauses {
} }
matrix.push(val); matrix.push(val);
} }
}
},
_ => bail!(AlgebrizerError::InvalidGroundConstant), _ => bail!(AlgebrizerError::InvalidGroundConstant),
} }
} }
@ -309,12 +322,13 @@ impl ConjoiningClauses {
// type tags. If and when we want to algebrize in two phases and allow for // type tags. If and when we want to algebrize in two phases and allow for
// late-binding input variables, we'll probably be able to loosen this restriction // late-binding input variables, we'll probably be able to loosen this restriction
// with little penalty. // with little penalty.
let types = accumulated_types_for_columns.into_iter() let types = accumulated_types_for_columns
.map(|x| x.exemplar().unwrap()) .into_iter()
.collect(); .map(|x| x.exemplar().unwrap())
.collect();
self.collect_named_bindings(schema, names, types, matrix); self.collect_named_bindings(schema, names, types, matrix);
Ok(()) Ok(())
}, }
(_, _) => bail!(AlgebrizerError::InvalidGroundConstant), (_, _) => bail!(AlgebrizerError::InvalidGroundConstant),
} }
} }
@ -324,23 +338,11 @@ impl ConjoiningClauses {
mod testing { mod testing {
use super::*; use super::*;
use core_traits::{ use core_traits::{Attribute, ValueType};
Attribute,
ValueType,
};
use edn::query::{ use edn::query::{Binding, FnArg, Keyword, PlainSymbol, Variable};
Binding,
FnArg,
Keyword,
PlainSymbol,
Variable,
};
use clauses::{ use clauses::{add_attribute, associate_ident};
add_attribute,
associate_ident,
};
#[test] #[test]
fn test_apply_ground() { fn test_apply_ground() {
@ -350,25 +352,31 @@ mod testing {
let mut schema = Schema::default(); let mut schema = Schema::default();
associate_ident(&mut schema, Keyword::namespaced("foo", "fts"), 100); associate_ident(&mut schema, Keyword::namespaced("foo", "fts"), 100);
add_attribute(&mut schema, 100, Attribute { add_attribute(
value_type: ValueType::String, &mut schema,
index: true, 100,
fulltext: true, Attribute {
..Default::default() value_type: ValueType::String,
}); index: true,
fulltext: true,
..Default::default()
},
);
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
// It's awkward enough to write these expansions that we give the details for the simplest // It's awkward enough to write these expansions that we give the details for the simplest
// case only. See the tests of the translator for more extensive (albeit looser) coverage. // case only. See the tests of the translator for more extensive (albeit looser) coverage.
let op = PlainSymbol::plain("ground"); let op = PlainSymbol::plain("ground");
cc.apply_ground(known, WhereFn { cc.apply_ground(
operator: op, known,
args: vec![ WhereFn {
FnArg::EntidOrInteger(10), operator: op,
], args: vec![FnArg::EntidOrInteger(10)],
binding: Binding::BindScalar(vz.clone()), binding: Binding::BindScalar(vz.clone()),
}).expect("to be able to apply_ground"); },
)
.expect("to be able to apply_ground");
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
@ -380,16 +388,20 @@ mod testing {
assert_eq!(clauses.len(), 0); assert_eq!(clauses.len(), 0);
let column_bindings = cc.column_bindings; let column_bindings = cc.column_bindings;
assert_eq!(column_bindings.len(), 0); // Scalar doesn't need this. assert_eq!(column_bindings.len(), 0); // Scalar doesn't need this.
let known_types = cc.known_types; let known_types = cc.known_types;
assert_eq!(known_types.len(), 1); assert_eq!(known_types.len(), 1);
assert_eq!(known_types.get(&vz).expect("to know the type of ?z"), assert_eq!(
&ValueTypeSet::of_one(ValueType::Long)); known_types.get(&vz).expect("to know the type of ?z"),
&ValueTypeSet::of_one(ValueType::Long)
);
let value_bindings = cc.value_bindings; let value_bindings = cc.value_bindings;
assert_eq!(value_bindings.len(), 1); assert_eq!(value_bindings.len(), 1);
assert_eq!(value_bindings.get(&vz).expect("to have a value for ?z"), assert_eq!(
&TypedValue::Long(10)); // We default to Long instead of entid. value_bindings.get(&vz).expect("to have a value for ?z"),
&TypedValue::Long(10)
); // We default to Long instead of entid.
} }
} }

View file

@ -10,19 +10,11 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use core_traits::{ use core_traits::{TypedValue, ValueType};
ValueType,
TypedValue,
};
use edn::query::{ use edn::query::Variable;
Variable,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, Result};
AlgebrizerError,
Result,
};
/// Define the inputs to a query. This is in two parts: a set of values known now, and a set of /// Define the inputs to a query. This is in two parts: a set of values known now, and a set of
/// types known now. /// types known now.
@ -59,13 +51,18 @@ impl QueryInputs {
pub fn with_values(values: BTreeMap<Variable, TypedValue>) -> QueryInputs { pub fn with_values(values: BTreeMap<Variable, TypedValue>) -> QueryInputs {
QueryInputs { QueryInputs {
types: values.iter().map(|(var, val)| (var.clone(), val.value_type())).collect(), types: values
.iter()
.map(|(var, val)| (var.clone(), val.value_type()))
.collect(),
values: values, values: values,
} }
} }
pub fn new(mut types: BTreeMap<Variable, ValueType>, pub fn new(
values: BTreeMap<Variable, TypedValue>) -> Result<QueryInputs> { mut types: BTreeMap<Variable, ValueType>,
values: BTreeMap<Variable, TypedValue>,
) -> Result<QueryInputs> {
// Make sure that the types of the values agree with those in types, and collect. // Make sure that the types of the values agree with those in types, and collect.
for (var, v) in values.iter() { for (var, v) in values.iter() {
let t = v.value_type(); let t = v.value_type();
@ -76,6 +73,9 @@ impl QueryInputs {
} }
} }
} }
Ok(QueryInputs { types: types, values: values }) Ok(QueryInputs {
types: types,
values: values,
})
} }
} }

View file

@ -10,89 +10,42 @@
use std::cmp; use std::cmp;
use std::collections::{ use std::collections::{BTreeMap, BTreeSet, VecDeque};
BTreeMap,
BTreeSet,
VecDeque,
};
use std::collections::btree_map::{ use std::collections::btree_map::Entry;
Entry,
};
use std::fmt::{ use std::fmt::{Debug, Formatter};
Debug,
Formatter,
};
use core_traits::{ use core_traits::{Attribute, Entid, KnownEntid, TypedValue, ValueType, ValueTypeSet};
Attribute,
Entid,
KnownEntid,
ValueType,
ValueTypeSet,
TypedValue,
};
use mentat_core::{ use mentat_core::{Cloned, HasSchema, Schema};
Cloned,
HasSchema,
Schema,
};
use mentat_core::counter::RcCounter; use mentat_core::counter::RcCounter;
use edn::query::{ use edn::query::{Element, FindSpec, Keyword, PatternNonValuePlace, Pull, Variable, WhereClause};
Element,
FindSpec,
Keyword,
Pull,
Variable,
WhereClause,
PatternNonValuePlace,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, Result};
AlgebrizerError,
Result,
};
use types::{ use types::{
ColumnConstraint, Column, ColumnConstraint, ColumnIntersection, ComputedTable, DatomsColumn, DatomsTable,
ColumnIntersection, EmptyBecause, EvolvedNonValuePlace, EvolvedPattern, EvolvedValuePlace, FulltextColumn,
ComputedTable, PlaceOrEmpty, QualifiedAlias, QueryValue, SourceAlias, TableAlias,
Column,
DatomsColumn,
DatomsTable,
EmptyBecause,
EvolvedNonValuePlace,
EvolvedPattern,
EvolvedValuePlace,
FulltextColumn,
PlaceOrEmpty,
QualifiedAlias,
QueryValue,
SourceAlias,
TableAlias,
}; };
mod convert; // Converting args to values. mod convert; // Converting args to values.
mod inputs; mod inputs;
mod or;
mod not; mod not;
mod or;
mod pattern; mod pattern;
mod predicate; mod predicate;
mod resolve; mod resolve;
mod ground;
mod fulltext; mod fulltext;
mod ground;
mod tx_log_api; mod tx_log_api;
mod where_fn; mod where_fn;
use validate::{ use validate::{validate_not_join, validate_or_join};
validate_not_join,
validate_or_join,
};
pub use self::inputs::QueryInputs; pub use self::inputs::QueryInputs;
@ -227,16 +180,16 @@ pub struct ConjoiningClauses {
impl PartialEq for ConjoiningClauses { impl PartialEq for ConjoiningClauses {
fn eq(&self, other: &ConjoiningClauses) -> bool { fn eq(&self, other: &ConjoiningClauses) -> bool {
self.empty_because.eq(&other.empty_because) && self.empty_because.eq(&other.empty_because)
self.from.eq(&other.from) && && self.from.eq(&other.from)
self.computed_tables.eq(&other.computed_tables) && && self.computed_tables.eq(&other.computed_tables)
self.wheres.eq(&other.wheres) && && self.wheres.eq(&other.wheres)
self.column_bindings.eq(&other.column_bindings) && && self.column_bindings.eq(&other.column_bindings)
self.input_variables.eq(&other.input_variables) && && self.input_variables.eq(&other.input_variables)
self.value_bindings.eq(&other.value_bindings) && && self.value_bindings.eq(&other.value_bindings)
self.known_types.eq(&other.known_types) && && self.known_types.eq(&other.known_types)
self.extracted_types.eq(&other.extracted_types) && && self.extracted_types.eq(&other.extracted_types)
self.required_types.eq(&other.required_types) && self.required_types.eq(&other.required_types)
} }
} }
@ -278,9 +231,7 @@ impl Default for ConjoiningClauses {
} }
} }
pub struct VariableIterator<'a>( pub struct VariableIterator<'a>(::std::collections::btree_map::Keys<'a, Variable, TypedValue>);
::std::collections::btree_map::Keys<'a, Variable, TypedValue>,
);
impl<'a> Iterator for VariableIterator<'a> { impl<'a> Iterator for VariableIterator<'a> {
type Item = &'a Variable; type Item = &'a Variable;
@ -303,17 +254,26 @@ impl ConjoiningClauses {
#[cfg(test)] #[cfg(test)]
pub fn with_inputs<T>(in_variables: BTreeSet<Variable>, inputs: T) -> ConjoiningClauses pub fn with_inputs<T>(in_variables: BTreeSet<Variable>, inputs: T) -> ConjoiningClauses
where T: Into<Option<QueryInputs>> { where
T: Into<Option<QueryInputs>>,
{
ConjoiningClauses::with_inputs_and_alias_counter(in_variables, inputs, RcCounter::new()) ConjoiningClauses::with_inputs_and_alias_counter(in_variables, inputs, RcCounter::new())
} }
pub(crate) fn with_inputs_and_alias_counter<T>(in_variables: BTreeSet<Variable>, pub(crate) fn with_inputs_and_alias_counter<T>(
inputs: T, in_variables: BTreeSet<Variable>,
alias_counter: RcCounter) -> ConjoiningClauses inputs: T,
where T: Into<Option<QueryInputs>> { alias_counter: RcCounter,
) -> ConjoiningClauses
where
T: Into<Option<QueryInputs>>,
{
match inputs.into() { match inputs.into() {
None => ConjoiningClauses::with_alias_counter(alias_counter), None => ConjoiningClauses::with_alias_counter(alias_counter),
Some(QueryInputs { mut types, mut values }) => { Some(QueryInputs {
mut types,
mut values,
}) => {
// Discard any bindings not mentioned in our :in clause. // Discard any bindings not mentioned in our :in clause.
types.keep_intersected_keys(&in_variables); types.keep_intersected_keys(&in_variables);
values.keep_intersected_keys(&in_variables); values.keep_intersected_keys(&in_variables);
@ -326,11 +286,13 @@ impl ConjoiningClauses {
}; };
// Pre-fill our type mappings with the types of the input bindings. // Pre-fill our type mappings with the types of the input bindings.
cc.known_types cc.known_types.extend(
.extend(types.iter() types
.map(|(k, v)| (k.clone(), ValueTypeSet::of_one(*v)))); .iter()
.map(|(k, v)| (k.clone(), ValueTypeSet::of_one(*v))),
);
cc cc
}, }
} }
} }
} }
@ -340,11 +302,13 @@ impl ConjoiningClauses {
pub(crate) fn derive_types_from_find_spec(&mut self, find_spec: &FindSpec) { pub(crate) fn derive_types_from_find_spec(&mut self, find_spec: &FindSpec) {
for spec in find_spec.columns() { for spec in find_spec.columns() {
match spec { match spec {
&Element::Pull(Pull { ref var, patterns: _ }) => { &Element::Pull(Pull {
ref var,
patterns: _,
}) => {
self.constrain_var_to_type(var.clone(), ValueType::Ref); self.constrain_var_to_type(var.clone(), ValueType::Ref);
}, }
_ => { _ => {}
},
} }
} }
} }
@ -391,14 +355,18 @@ impl ConjoiningClauses {
// If so, generate a constraint against the primary column. // If so, generate a constraint against the primary column.
if let Some(vec) = self.column_bindings.get(var) { if let Some(vec) = self.column_bindings.get(var) {
if let Some(col) = vec.first() { if let Some(col) = vec.first() {
self.wheres.add_intersection(ColumnConstraint::Equals(col.clone(), QueryValue::TypedValue(value.clone()))); self.wheres.add_intersection(ColumnConstraint::Equals(
col.clone(),
QueryValue::TypedValue(value.clone()),
));
} }
} }
// Are we also trying to figure out the type of the value when the query runs? // Are we also trying to figure out the type of the value when the query runs?
// If so, constrain that! // If so, constrain that!
if let Some(qa) = self.extracted_types.get(&var) { if let Some(qa) = self.extracted_types.get(&var) {
self.wheres.add_intersection(ColumnConstraint::has_unit_type(qa.0.clone(), vt)); self.wheres
.add_intersection(ColumnConstraint::has_unit_type(qa.0.clone(), vt));
} }
// Finally, store the binding for future use. // Finally, store the binding for future use.
@ -439,10 +407,19 @@ impl ConjoiningClauses {
} }
pub fn known_type_set(&self, var: &Variable) -> ValueTypeSet { pub fn known_type_set(&self, var: &Variable) -> ValueTypeSet {
self.known_types.get(var).cloned().unwrap_or(ValueTypeSet::any()) self.known_types
.get(var)
.cloned()
.unwrap_or(ValueTypeSet::any())
} }
pub(crate) fn bind_column_to_var<C: Into<Column>>(&mut self, schema: &Schema, table: TableAlias, column: C, var: Variable) { pub(crate) fn bind_column_to_var<C: Into<Column>>(
&mut self,
schema: &Schema,
table: TableAlias,
column: C,
var: Variable,
) {
let column = column.into(); let column = column.into();
// Do we have an external binding for this? // Do we have an external binding for this?
if let Some(bound_val) = self.bound_value(&var) { if let Some(bound_val) = self.bound_value(&var) {
@ -454,18 +431,18 @@ impl ConjoiningClauses {
// We don't need to handle expansion of attributes here. The subquery that // We don't need to handle expansion of attributes here. The subquery that
// produces the variable projection will do so. // produces the variable projection will do so.
self.constrain_column_to_constant(table, column, bound_val); self.constrain_column_to_constant(table, column, bound_val);
}, }
Column::Transactions(_) => { Column::Transactions(_) => {
self.constrain_column_to_constant(table, column, bound_val); self.constrain_column_to_constant(table, column, bound_val);
}, }
Column::Fulltext(FulltextColumn::Rowid) | Column::Fulltext(FulltextColumn::Rowid)
Column::Fulltext(FulltextColumn::Text) => { | Column::Fulltext(FulltextColumn::Text) => {
// We never expose `rowid` via queries. We do expose `text`, but only // We never expose `rowid` via queries. We do expose `text`, but only
// indirectly, by joining against `datoms`. Therefore, these are meaningless. // indirectly, by joining against `datoms`. Therefore, these are meaningless.
unimplemented!() unimplemented!()
}, }
Column::Fixed(DatomsColumn::ValueTypeTag) => { Column::Fixed(DatomsColumn::ValueTypeTag) => {
// I'm pretty sure this is meaningless right now, because we will never bind // I'm pretty sure this is meaningless right now, because we will never bind
@ -477,18 +454,18 @@ impl ConjoiningClauses {
// [(= (typeof ?y) :db.valueType/double)]] // [(= (typeof ?y) :db.valueType/double)]]
// ``` // ```
unimplemented!(); unimplemented!();
}, }
// TODO: recognize when the valueType might be a ref and also translate entids there. // TODO: recognize when the valueType might be a ref and also translate entids there.
Column::Fixed(DatomsColumn::Value) => { Column::Fixed(DatomsColumn::Value) => {
self.constrain_column_to_constant(table, column, bound_val); self.constrain_column_to_constant(table, column, bound_val);
}, }
// These columns can only be entities, so attempt to translate keywords. If we can't // These columns can only be entities, so attempt to translate keywords. If we can't
// get an entity out of the bound value, the pattern cannot produce results. // get an entity out of the bound value, the pattern cannot produce results.
Column::Fixed(DatomsColumn::Attribute) | Column::Fixed(DatomsColumn::Attribute)
Column::Fixed(DatomsColumn::Entity) | | Column::Fixed(DatomsColumn::Entity)
Column::Fixed(DatomsColumn::Tx) => { | Column::Fixed(DatomsColumn::Tx) => {
match bound_val { match bound_val {
TypedValue::Keyword(ref kw) => { TypedValue::Keyword(ref kw) => {
if let Some(entid) = self.entid_for_ident(schema, kw) { if let Some(entid) = self.entid_for_ident(schema, kw) {
@ -500,14 +477,14 @@ impl ConjoiningClauses {
// attribute then we should have already marked the pattern as empty. // attribute then we should have already marked the pattern as empty.
self.mark_known_empty(EmptyBecause::UnresolvedIdent(kw.cloned())); self.mark_known_empty(EmptyBecause::UnresolvedIdent(kw.cloned()));
} }
}, }
TypedValue::Ref(entid) => { TypedValue::Ref(entid) => {
self.constrain_column_to_entity(table, column, entid); self.constrain_column_to_entity(table, column, entid);
}, }
_ => { _ => {
// One can't bind an e, a, or tx to something other than an entity. // One can't bind an e, a, or tx to something other than an entity.
self.mark_known_empty(EmptyBecause::InvalidBinding(column, bound_val)); self.mark_known_empty(EmptyBecause::InvalidBinding(column, bound_val));
}, }
} }
} }
} }
@ -521,10 +498,9 @@ impl ConjoiningClauses {
// If this is a value, and we don't already know its type or where // If this is a value, and we don't already know its type or where
// to get its type, record that we can get it from this table. // to get its type, record that we can get it from this table.
let needs_type_extraction = let needs_type_extraction = !late_binding && // Never need to extract for bound vars.
!late_binding && // Never need to extract for bound vars.
self.known_type(&var).is_none() && // Don't need to extract if we know a single type. self.known_type(&var).is_none() && // Don't need to extract if we know a single type.
!self.extracted_types.contains_key(&var); // We're already extracting the type. !self.extracted_types.contains_key(&var); // We're already extracting the type.
let alias = QualifiedAlias(table, column); let alias = QualifiedAlias(table, column);
@ -536,23 +512,42 @@ impl ConjoiningClauses {
} }
} }
self.column_bindings.entry(var).or_insert(vec![]).push(alias); self.column_bindings
.entry(var)
.or_insert(vec![])
.push(alias);
} }
pub(crate) fn constrain_column_to_constant<C: Into<Column>>(&mut self, table: TableAlias, column: C, constant: TypedValue) { pub(crate) fn constrain_column_to_constant<C: Into<Column>>(
&mut self,
table: TableAlias,
column: C,
constant: TypedValue,
) {
match constant { match constant {
// Be a little more explicit. // Be a little more explicit.
TypedValue::Ref(entid) => self.constrain_column_to_entity(table, column, entid), TypedValue::Ref(entid) => self.constrain_column_to_entity(table, column, entid),
_ => { _ => {
let column = column.into(); let column = column.into();
self.wheres.add_intersection(ColumnConstraint::Equals(QualifiedAlias(table, column), QueryValue::TypedValue(constant))) self.wheres.add_intersection(ColumnConstraint::Equals(
}, QualifiedAlias(table, column),
QueryValue::TypedValue(constant),
))
}
} }
} }
pub(crate) fn constrain_column_to_entity<C: Into<Column>>(&mut self, table: TableAlias, column: C, entity: Entid) { pub(crate) fn constrain_column_to_entity<C: Into<Column>>(
&mut self,
table: TableAlias,
column: C,
entity: Entid,
) {
let column = column.into(); let column = column.into();
self.wheres.add_intersection(ColumnConstraint::Equals(QualifiedAlias(table, column), QueryValue::Entid(entity))) self.wheres.add_intersection(ColumnConstraint::Equals(
QualifiedAlias(table, column),
QueryValue::Entid(entity),
))
} }
pub(crate) fn constrain_attribute(&mut self, table: TableAlias, attribute: Entid) { pub(crate) fn constrain_attribute(&mut self, table: TableAlias, attribute: Entid) {
@ -562,7 +557,8 @@ impl ConjoiningClauses {
pub(crate) fn constrain_value_to_numeric(&mut self, table: TableAlias, value: i64) { pub(crate) fn constrain_value_to_numeric(&mut self, table: TableAlias, value: i64) {
self.wheres.add_intersection(ColumnConstraint::Equals( self.wheres.add_intersection(ColumnConstraint::Equals(
QualifiedAlias(table, Column::Fixed(DatomsColumn::Value)), QualifiedAlias(table, Column::Fixed(DatomsColumn::Value)),
QueryValue::PrimitiveLong(value))) QueryValue::PrimitiveLong(value),
))
} }
/// Mark the given value as a long. /// Mark the given value as a long.
@ -575,11 +571,19 @@ impl ConjoiningClauses {
self.narrow_types_for_var(variable, ValueTypeSet::of_numeric_types()); self.narrow_types_for_var(variable, ValueTypeSet::of_numeric_types());
} }
pub(crate) fn can_constrain_var_to_type(&self, var: &Variable, this_type: ValueType) -> Option<EmptyBecause> { pub(crate) fn can_constrain_var_to_type(
&self,
var: &Variable,
this_type: ValueType,
) -> Option<EmptyBecause> {
self.can_constrain_var_to_types(var, ValueTypeSet::of_one(this_type)) self.can_constrain_var_to_types(var, ValueTypeSet::of_one(this_type))
} }
fn can_constrain_var_to_types(&self, var: &Variable, these_types: ValueTypeSet) -> Option<EmptyBecause> { fn can_constrain_var_to_types(
&self,
var: &Variable,
these_types: ValueTypeSet,
) -> Option<EmptyBecause> {
if let Some(existing) = self.known_types.get(var) { if let Some(existing) = self.known_types.get(var) {
if existing.intersection(&these_types).is_empty() { if existing.intersection(&these_types).is_empty() {
return Some(EmptyBecause::TypeMismatch { return Some(EmptyBecause::TypeMismatch {
@ -603,7 +607,11 @@ impl ConjoiningClauses {
if let Some(existing) = self.known_types.insert(var.clone(), this_type_set) { if let Some(existing) = self.known_types.insert(var.clone(), this_type_set) {
// There was an existing mapping. Does this type match? // There was an existing mapping. Does this type match?
if !existing.contains(this_type) { if !existing.contains(this_type) {
self.mark_known_empty(EmptyBecause::TypeMismatch { var, existing, desired: this_type_set }); self.mark_known_empty(EmptyBecause::TypeMismatch {
var,
existing,
desired: this_type_set,
});
} }
} }
} }
@ -627,7 +635,7 @@ impl ConjoiningClauses {
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
entry.insert(types); entry.insert(types);
return; return;
}, }
Entry::Occupied(mut entry) => { Entry::Occupied(mut entry) => {
// We have an existing requirement. The new requirement will be // We have an existing requirement. The new requirement will be
// the intersection, but we'll `mark_known_empty` if that's empty. // the intersection, but we'll `mark_known_empty` if that's empty.
@ -644,7 +652,7 @@ impl ConjoiningClauses {
existing: existing, existing: existing,
desired: types, desired: types,
} }
}, }
}; };
self.mark_known_empty(empty_because); self.mark_known_empty(empty_because);
} }
@ -664,21 +672,22 @@ impl ConjoiningClauses {
self.extracted_types.remove(e.key()); self.extracted_types.remove(e.key());
} }
e.insert(new_types); e.insert(new_types);
}, }
Entry::Occupied(mut e) => { Entry::Occupied(mut e) => {
let new; let new;
// Scoped borrow of `e`. // Scoped borrow of `e`.
{ {
let existing_types = e.get(); let existing_types = e.get();
if existing_types.is_empty() && // The set is empty: no types are possible. if existing_types.is_empty() && // The set is empty: no types are possible.
self.empty_because.is_some() { self.empty_because.is_some()
{
panic!("Uh oh: we failed this pattern, probably because {:?} couldn't match, but now we're broadening its type.", panic!("Uh oh: we failed this pattern, probably because {:?} couldn't match, but now we're broadening its type.",
e.key()); e.key());
} }
new = existing_types.union(&new_types); new = existing_types.union(&new_types);
} }
e.insert(new); e.insert(new);
}, }
} }
} }
} }
@ -699,18 +708,20 @@ impl ConjoiningClauses {
match self.known_types.entry(var) { match self.known_types.entry(var) {
Entry::Vacant(e) => { Entry::Vacant(e) => {
e.insert(types); e.insert(types);
}, }
Entry::Occupied(mut e) => { Entry::Occupied(mut e) => {
let intersected: ValueTypeSet = types.intersection(e.get()); let intersected: ValueTypeSet = types.intersection(e.get());
if intersected.is_empty() { if intersected.is_empty() {
let reason = EmptyBecause::TypeMismatch { var: e.key().clone(), let reason = EmptyBecause::TypeMismatch {
existing: e.get().clone(), var: e.key().clone(),
desired: types }; existing: e.get().clone(),
desired: types,
};
empty_because = Some(reason); empty_because = Some(reason);
} }
// Always insert, even if it's empty! // Always insert, even if it's empty!
e.insert(intersected); e.insert(intersected);
}, }
} }
if let Some(e) = empty_because { if let Some(e) = empty_because {
@ -754,39 +765,47 @@ impl ConjoiningClauses {
if self.empty_because.is_some() { if self.empty_because.is_some() {
return; return;
} }
println!("CC known empty: {:?}.", &why); // TODO: proper logging. println!("CC known empty: {:?}.", &why); // TODO: proper logging.
self.empty_because = Some(why); self.empty_because = Some(why);
} }
fn entid_for_ident<'s, 'a>(&self, schema: &'s Schema, ident: &'a Keyword) -> Option<KnownEntid> { fn entid_for_ident<'s, 'a>(
&self,
schema: &'s Schema,
ident: &'a Keyword,
) -> Option<KnownEntid> {
schema.get_entid(&ident) schema.get_entid(&ident)
} }
fn table_for_attribute_and_value<'s, 'a>(&self, attribute: &'s Attribute, value: &'a EvolvedValuePlace) -> ::std::result::Result<DatomsTable, EmptyBecause> { fn table_for_attribute_and_value<'s, 'a>(
&self,
attribute: &'s Attribute,
value: &'a EvolvedValuePlace,
) -> ::std::result::Result<DatomsTable, EmptyBecause> {
if attribute.fulltext { if attribute.fulltext {
match value { match value {
&EvolvedValuePlace::Placeholder => &EvolvedValuePlace::Placeholder => Ok(DatomsTable::Datoms), // We don't need the value.
Ok(DatomsTable::Datoms), // We don't need the value.
// TODO: an existing non-string binding can cause this pattern to fail. // TODO: an existing non-string binding can cause this pattern to fail.
&EvolvedValuePlace::Variable(_) => &EvolvedValuePlace::Variable(_) => Ok(DatomsTable::FulltextDatoms),
Ok(DatomsTable::FulltextDatoms),
&EvolvedValuePlace::Value(TypedValue::String(_)) => &EvolvedValuePlace::Value(TypedValue::String(_)) => Ok(DatomsTable::FulltextDatoms),
Ok(DatomsTable::FulltextDatoms),
_ => { _ => {
// We can't succeed if there's a non-string constant value for a fulltext // We can't succeed if there's a non-string constant value for a fulltext
// field. // field.
Err(EmptyBecause::NonStringFulltextValue) Err(EmptyBecause::NonStringFulltextValue)
}, }
} }
} else { } else {
Ok(DatomsTable::Datoms) Ok(DatomsTable::Datoms)
} }
} }
fn table_for_unknown_attribute<'s, 'a>(&self, value: &'a EvolvedValuePlace) -> ::std::result::Result<DatomsTable, EmptyBecause> { fn table_for_unknown_attribute<'s, 'a>(
&self,
value: &'a EvolvedValuePlace,
) -> ::std::result::Result<DatomsTable, EmptyBecause> {
// If the value is known to be non-textual, we can simply use the regular datoms // If the value is known to be non-textual, we can simply use the regular datoms
// table (TODO: and exclude on `index_fulltext`!). // table (TODO: and exclude on `index_fulltext`!).
// //
@ -795,72 +814,87 @@ impl ConjoiningClauses {
// //
// If the value is a variable or string, we must use `all_datoms`, or do the join // If the value is a variable or string, we must use `all_datoms`, or do the join
// ourselves, because we'll need to either extract or compare on the string. // ourselves, because we'll need to either extract or compare on the string.
Ok( Ok(match value {
match value { // TODO: see if the variable is projected, aggregated, or compared elsewhere in
// TODO: see if the variable is projected, aggregated, or compared elsewhere in // the query. If it's not, we don't need to use all_datoms here.
// the query. If it's not, we don't need to use all_datoms here. &EvolvedValuePlace::Variable(ref v) => {
&EvolvedValuePlace::Variable(ref v) => { // If `required_types` and `known_types` don't exclude strings,
// If `required_types` and `known_types` don't exclude strings, // we need to query `all_datoms`.
// we need to query `all_datoms`. if self
if self.required_types.get(v).map_or(true, |s| s.contains(ValueType::String)) && .required_types
self.known_types.get(v).map_or(true, |s| s.contains(ValueType::String)) { .get(v)
DatomsTable::AllDatoms .map_or(true, |s| s.contains(ValueType::String))
} else { && self
DatomsTable::Datoms .known_types
} .get(v)
.map_or(true, |s| s.contains(ValueType::String))
{
DatomsTable::AllDatoms
} else {
DatomsTable::Datoms
} }
&EvolvedValuePlace::Value(TypedValue::String(_)) => }
DatomsTable::AllDatoms, &EvolvedValuePlace::Value(TypedValue::String(_)) => DatomsTable::AllDatoms,
_ => _ => DatomsTable::Datoms,
DatomsTable::Datoms, })
})
} }
/// Decide which table to use for the provided attribute and value. /// Decide which table to use for the provided attribute and value.
/// If the attribute input or value binding doesn't name an attribute, or doesn't name an /// If the attribute input or value binding doesn't name an attribute, or doesn't name an
/// attribute that is congruent with the supplied value, we return an `EmptyBecause`. /// attribute that is congruent with the supplied value, we return an `EmptyBecause`.
/// The caller is responsible for marking the CC as known-empty if this is a fatal failure. /// The caller is responsible for marking the CC as known-empty if this is a fatal failure.
fn table_for_places<'s, 'a>(&self, schema: &'s Schema, attribute: &'a EvolvedNonValuePlace, value: &'a EvolvedValuePlace) -> ::std::result::Result<DatomsTable, EmptyBecause> { fn table_for_places<'s, 'a>(
&self,
schema: &'s Schema,
attribute: &'a EvolvedNonValuePlace,
value: &'a EvolvedValuePlace,
) -> ::std::result::Result<DatomsTable, EmptyBecause> {
match attribute { match attribute {
&EvolvedNonValuePlace::Entid(id) => &EvolvedNonValuePlace::Entid(id) => schema
schema.attribute_for_entid(id) .attribute_for_entid(id)
.ok_or_else(|| EmptyBecause::InvalidAttributeEntid(id)) .ok_or_else(|| EmptyBecause::InvalidAttributeEntid(id))
.and_then(|attribute| self.table_for_attribute_and_value(attribute, value)), .and_then(|attribute| self.table_for_attribute_and_value(attribute, value)),
// TODO: In a prepared context, defer this decision until a second algebrizing phase. // TODO: In a prepared context, defer this decision until a second algebrizing phase.
// #278. // #278.
&EvolvedNonValuePlace::Placeholder => &EvolvedNonValuePlace::Placeholder => self.table_for_unknown_attribute(value),
self.table_for_unknown_attribute(value),
&EvolvedNonValuePlace::Variable(ref v) => { &EvolvedNonValuePlace::Variable(ref v) => {
// See if we have a binding for the variable. // See if we have a binding for the variable.
match self.bound_value(v) { match self.bound_value(v) {
// TODO: In a prepared context, defer this decision until a second algebrizing phase. // TODO: In a prepared context, defer this decision until a second algebrizing phase.
// #278. // #278.
None => None => self.table_for_unknown_attribute(value),
self.table_for_unknown_attribute(value),
Some(TypedValue::Ref(id)) => Some(TypedValue::Ref(id)) =>
// Recurse: it's easy. // Recurse: it's easy.
self.table_for_places(schema, &EvolvedNonValuePlace::Entid(id), value), {
self.table_for_places(schema, &EvolvedNonValuePlace::Entid(id), value)
}
Some(TypedValue::Keyword(ref kw)) => Some(TypedValue::Keyword(ref kw)) =>
// Don't recurse: avoid needing to clone the keyword. // Don't recurse: avoid needing to clone the keyword.
schema.attribute_for_ident(kw) {
.ok_or_else(|| EmptyBecause::InvalidAttributeIdent(kw.cloned())) schema
.and_then(|(attribute, _entid)| self.table_for_attribute_and_value(attribute, value)), .attribute_for_ident(kw)
.ok_or_else(|| EmptyBecause::InvalidAttributeIdent(kw.cloned()))
.and_then(|(attribute, _entid)| {
self.table_for_attribute_and_value(attribute, value)
})
}
Some(v) => { Some(v) => {
// This pattern cannot match: the caller has bound a non-entity value to an // This pattern cannot match: the caller has bound a non-entity value to an
// attribute place. // attribute place.
Err(EmptyBecause::InvalidBinding(Column::Fixed(DatomsColumn::Attribute), v.clone())) Err(EmptyBecause::InvalidBinding(
}, Column::Fixed(DatomsColumn::Attribute),
v.clone(),
))
}
} }
}, }
} }
} }
pub(crate) fn next_alias_for_table(&mut self, table: DatomsTable) -> TableAlias { pub(crate) fn next_alias_for_table(&mut self, table: DatomsTable) -> TableAlias {
match table { match table {
DatomsTable::Computed(u) => DatomsTable::Computed(u) => format!("{}{:02}", table.name(), u),
format!("{}{:02}", table.name(), u), _ => format!("{}{:02}", table.name(), self.alias_counter.next()),
_ =>
format!("{}{:02}", table.name(), self.alias_counter.next()),
} }
} }
@ -868,7 +902,11 @@ impl ConjoiningClauses {
/// This is a mutating method because it mutates the aliaser function! /// This is a mutating method because it mutates the aliaser function!
/// Note that if this function decides that a pattern cannot match, it will flip /// Note that if this function decides that a pattern cannot match, it will flip
/// `empty_because`. /// `empty_because`.
fn alias_table<'s, 'a>(&mut self, schema: &'s Schema, pattern: &'a EvolvedPattern) -> Option<SourceAlias> { fn alias_table<'s, 'a>(
&mut self,
schema: &'s Schema,
pattern: &'a EvolvedPattern,
) -> Option<SourceAlias> {
self.table_for_places(schema, &pattern.attribute, &pattern.value) self.table_for_places(schema, &pattern.attribute, &pattern.value)
.map_err(|reason| { .map_err(|reason| {
self.mark_known_empty(reason); self.mark_known_empty(reason);
@ -877,7 +915,11 @@ impl ConjoiningClauses {
.ok() .ok()
} }
fn get_attribute_for_value<'s>(&self, schema: &'s Schema, value: &TypedValue) -> Option<&'s Attribute> { fn get_attribute_for_value<'s>(
&self,
schema: &'s Schema,
value: &TypedValue,
) -> Option<&'s Attribute> {
match value { match value {
// We know this one is known if the attribute lookup succeeds… // We know this one is known if the attribute lookup succeeds…
&TypedValue::Ref(id) => schema.attribute_for_entid(id), &TypedValue::Ref(id) => schema.attribute_for_entid(id),
@ -886,29 +928,41 @@ impl ConjoiningClauses {
} }
} }
fn get_attribute<'s, 'a>(&self, schema: &'s Schema, pattern: &'a EvolvedPattern) -> Option<&'s Attribute> { fn get_attribute<'s, 'a>(
&self,
schema: &'s Schema,
pattern: &'a EvolvedPattern,
) -> Option<&'s Attribute> {
match pattern.attribute { match pattern.attribute {
EvolvedNonValuePlace::Entid(id) => EvolvedNonValuePlace::Entid(id) =>
// We know this one is known if the attribute lookup succeeds… // We know this one is known if the attribute lookup succeeds…
schema.attribute_for_entid(id), {
schema.attribute_for_entid(id)
}
EvolvedNonValuePlace::Variable(ref var) => EvolvedNonValuePlace::Variable(ref var) =>
// If the pattern has a variable, we've already determined that the binding -- if // If the pattern has a variable, we've already determined that the binding -- if
// any -- is acceptable and yields a table. Here, simply look to see if it names // any -- is acceptable and yields a table. Here, simply look to see if it names
// an attribute so we can find out the type. // an attribute so we can find out the type.
self.value_bindings.get(var) {
.and_then(|val| self.get_attribute_for_value(schema, val)), self.value_bindings
.get(var)
.and_then(|val| self.get_attribute_for_value(schema, val))
}
EvolvedNonValuePlace::Placeholder => None, EvolvedNonValuePlace::Placeholder => None,
} }
} }
fn get_value_type<'s, 'a>(&self, schema: &'s Schema, pattern: &'a EvolvedPattern) -> Option<ValueType> { fn get_value_type<'s, 'a>(
&self,
schema: &'s Schema,
pattern: &'a EvolvedPattern,
) -> Option<ValueType> {
self.get_attribute(schema, pattern).map(|a| a.value_type) self.get_attribute(schema, pattern).map(|a| a.value_type)
} }
} }
/// Expansions. /// Expansions.
impl ConjoiningClauses { impl ConjoiningClauses {
/// Take the contents of `column_bindings` and generate inter-constraints for the appropriate /// Take the contents of `column_bindings` and generate inter-constraints for the appropriate
/// columns into `wheres`. /// columns into `wheres`.
/// ///
@ -933,7 +987,10 @@ impl ConjoiningClauses {
// TODO: if both primary and secondary are .v, should we make sure // TODO: if both primary and secondary are .v, should we make sure
// the type tag columns also match? // the type tag columns also match?
// We don't do so in the ClojureScript version. // We don't do so in the ClojureScript version.
self.wheres.add_intersection(ColumnConstraint::Equals(primary.clone(), QueryValue::Column(secondary.clone()))); self.wheres.add_intersection(ColumnConstraint::Equals(
primary.clone(),
QueryValue::Column(secondary.clone()),
));
} }
} }
} }
@ -962,7 +1019,7 @@ impl ConjoiningClauses {
/// This step also updates `known_types` to match. /// This step also updates `known_types` to match.
pub(crate) fn process_required_types(&mut self) -> Result<()> { pub(crate) fn process_required_types(&mut self) -> Result<()> {
if self.empty_because.is_some() { if self.empty_because.is_some() {
return Ok(()) return Ok(());
} }
// We can't call `mark_known_empty` inside the loop since it would be a // We can't call `mark_known_empty` inside the loop since it would be a
@ -1011,9 +1068,10 @@ impl ConjoiningClauses {
// Update known types. // Update known types.
self.narrow_types_for_var(var.clone(), types); self.narrow_types_for_var(var.clone(), types);
let qa = self.extracted_types let qa = self
.get(&var) .extracted_types
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()))?; .get(&var)
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()))?;
self.wheres.add_intersection(ColumnConstraint::HasTypes { self.wheres.add_intersection(ColumnConstraint::HasTypes {
value: qa.0.clone(), value: qa.0.clone(),
value_types: types, value_types: types,
@ -1053,14 +1111,18 @@ impl ConjoiningClauses {
} }
impl ConjoiningClauses { impl ConjoiningClauses {
fn apply_evolved_patterns(&mut self, known: Known, mut patterns: VecDeque<EvolvedPattern>) -> Result<()> { fn apply_evolved_patterns(
&mut self,
known: Known,
mut patterns: VecDeque<EvolvedPattern>,
) -> Result<()> {
while let Some(pattern) = patterns.pop_front() { while let Some(pattern) = patterns.pop_front() {
match self.evolve_pattern(known, pattern) { match self.evolve_pattern(known, pattern) {
PlaceOrEmpty::Place(re_evolved) => self.apply_pattern(known, re_evolved), PlaceOrEmpty::Place(re_evolved) => self.apply_pattern(known, re_evolved),
PlaceOrEmpty::Empty(because) => { PlaceOrEmpty::Empty(because) => {
self.mark_known_empty(because); self.mark_known_empty(because);
patterns.clear(); patterns.clear();
}, }
} }
} }
Ok(()) Ok(())
@ -1072,13 +1134,17 @@ impl ConjoiningClauses {
} }
} }
pub(crate) fn apply_clauses(&mut self, known: Known, where_clauses: Vec<WhereClause>) -> Result<()> { pub(crate) fn apply_clauses(
&mut self,
known: Known,
where_clauses: Vec<WhereClause>,
) -> Result<()> {
// We apply (top level) type predicates first as an optimization. // We apply (top level) type predicates first as an optimization.
for clause in where_clauses.iter() { for clause in where_clauses.iter() {
match clause { match clause {
&WhereClause::TypeAnnotation(ref anno) => { &WhereClause::TypeAnnotation(ref anno) => {
self.apply_type_anno(anno)?; self.apply_type_anno(anno)?;
}, }
// Patterns are common, so let's grab as much type information from // Patterns are common, so let's grab as much type information from
// them as we can. // them as we can.
@ -1086,11 +1152,11 @@ impl ConjoiningClauses {
self.mark_as_ref(&p.entity); self.mark_as_ref(&p.entity);
self.mark_as_ref(&p.attribute); self.mark_as_ref(&p.attribute);
self.mark_as_ref(&p.tx); self.mark_as_ref(&p.tx);
}, }
// TODO: if we wish we can include other kinds of clauses in this type // TODO: if we wish we can include other kinds of clauses in this type
// extraction phase. // extraction phase.
_ => {}, _ => {}
} }
} }
@ -1105,13 +1171,11 @@ impl ConjoiningClauses {
continue; continue;
} }
match clause { match clause {
WhereClause::Pattern(p) => { WhereClause::Pattern(p) => match self.make_evolved_pattern(known, p) {
match self.make_evolved_pattern(known, p) { PlaceOrEmpty::Place(evolved) => patterns.push_back(evolved),
PlaceOrEmpty::Place(evolved) => patterns.push_back(evolved), PlaceOrEmpty::Empty(because) => {
PlaceOrEmpty::Empty(because) => { self.mark_known_empty(because);
self.mark_known_empty(because); return Ok(());
return Ok(());
}
} }
}, },
_ => { _ => {
@ -1120,7 +1184,7 @@ impl ConjoiningClauses {
patterns = VecDeque::with_capacity(remaining); patterns = VecDeque::with_capacity(remaining);
} }
self.apply_clause(known, clause)?; self.apply_clause(known, clause)?;
}, }
} }
} }
self.apply_evolved_patterns(known, patterns) self.apply_evolved_patterns(known, patterns)
@ -1136,24 +1200,18 @@ impl ConjoiningClauses {
PlaceOrEmpty::Empty(because) => self.mark_known_empty(because), PlaceOrEmpty::Empty(because) => self.mark_known_empty(because),
} }
Ok(()) Ok(())
}, }
WhereClause::Pred(p) => { WhereClause::Pred(p) => self.apply_predicate(known, p),
self.apply_predicate(known, p) WhereClause::WhereFn(f) => self.apply_where_fn(known, f),
},
WhereClause::WhereFn(f) => {
self.apply_where_fn(known, f)
},
WhereClause::OrJoin(o) => { WhereClause::OrJoin(o) => {
validate_or_join(&o)?; validate_or_join(&o)?;
self.apply_or_join(known, o) self.apply_or_join(known, o)
}, }
WhereClause::NotJoin(n) => { WhereClause::NotJoin(n) => {
validate_not_join(&n)?; validate_not_join(&n)?;
self.apply_not_join(known, n) self.apply_not_join(known, n)
}, }
WhereClause::TypeAnnotation(anno) => { WhereClause::TypeAnnotation(anno) => self.apply_type_anno(&anno),
self.apply_type_anno(&anno)
},
_ => unimplemented!(), _ => unimplemented!(),
} }
} }

View file

@ -8,23 +8,13 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use edn::query::{ use edn::query::{ContainsVariables, NotJoin, UnifyVars};
ContainsVariables,
NotJoin,
UnifyVars,
};
use clauses::ConjoiningClauses; use clauses::ConjoiningClauses;
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, Result};
AlgebrizerError,
Result,
};
use types::{ use types::{ColumnConstraint, ComputedTable};
ColumnConstraint,
ComputedTable,
};
use Known; use Known;
@ -78,7 +68,8 @@ impl ConjoiningClauses {
let subquery = ComputedTable::Subquery(template); let subquery = ComputedTable::Subquery(template);
self.wheres.add_intersection(ColumnConstraint::NotExists(subquery)); self.wheres
.add_intersection(ColumnConstraint::NotExists(subquery));
Ok(()) Ok(())
} }
@ -90,51 +81,22 @@ mod testing {
use super::*; use super::*;
use core_traits::{ use core_traits::{Attribute, TypedValue, ValueType, ValueTypeSet};
Attribute,
TypedValue,
ValueType,
ValueTypeSet,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::query::{ use edn::query::{Keyword, PlainSymbol, Variable};
Keyword,
PlainSymbol,
Variable
};
use clauses::{ use clauses::{add_attribute, associate_ident, QueryInputs};
QueryInputs,
add_attribute,
associate_ident,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::AlgebrizerError;
AlgebrizerError,
};
use types::{ use types::{
ColumnAlternation, ColumnAlternation, ColumnConstraint, ColumnConstraintOrAlternation, ColumnIntersection,
ColumnConstraint, DatomsColumn, DatomsTable, Inequality, QualifiedAlias, QueryValue, SourceAlias,
ColumnConstraintOrAlternation,
ColumnIntersection,
DatomsColumn,
DatomsTable,
Inequality,
QualifiedAlias,
QueryValue,
SourceAlias,
}; };
use { use {algebrize, algebrize_with_inputs, parse_find_string};
algebrize,
algebrize_with_inputs,
parse_find_string,
};
fn alg(schema: &Schema, input: &str) -> ConjoiningClauses { fn alg(schema: &Schema, input: &str) -> ConjoiningClauses {
let known = Known::for_schema(schema); let known = Known::for_schema(schema);
@ -145,7 +107,9 @@ mod testing {
fn alg_with_inputs(schema: &Schema, input: &str, inputs: QueryInputs) -> ConjoiningClauses { fn alg_with_inputs(schema: &Schema, input: &str, inputs: QueryInputs) -> ConjoiningClauses {
let known = Known::for_schema(schema); let known = Known::for_schema(schema);
let parsed = parse_find_string(input).expect("parse failed"); let parsed = parse_find_string(input).expect("parse failed");
algebrize_with_inputs(known, parsed, 0, inputs).expect("algebrize failed").cc algebrize_with_inputs(known, parsed, 0, inputs)
.expect("algebrize failed")
.cc
} }
fn prepopulated_schema() -> Schema { fn prepopulated_schema() -> Schema {
@ -155,41 +119,51 @@ mod testing {
associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67); associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67);
associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68); associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68);
associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69); associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69);
add_attribute(&mut schema, add_attribute(
65, &mut schema,
Attribute { 65,
value_type: ValueType::String, Attribute {
multival: false, value_type: ValueType::String,
..Default::default() multival: false,
}); ..Default::default()
add_attribute(&mut schema, },
66, );
Attribute { add_attribute(
value_type: ValueType::String, &mut schema,
multival: true, 66,
..Default::default() Attribute {
}); value_type: ValueType::String,
add_attribute(&mut schema, multival: true,
67, ..Default::default()
Attribute { },
value_type: ValueType::String, );
multival: true, add_attribute(
..Default::default() &mut schema,
}); 67,
add_attribute(&mut schema, Attribute {
68, value_type: ValueType::String,
Attribute { multival: true,
value_type: ValueType::Long, ..Default::default()
multival: false, },
..Default::default() );
}); add_attribute(
add_attribute(&mut schema, &mut schema,
69, 68,
Attribute { Attribute {
value_type: ValueType::Long, value_type: ValueType::Long,
multival: false, multival: false,
..Default::default() ..Default::default()
}); },
);
add_attribute(
&mut schema,
69,
Attribute {
value_type: ValueType::Long,
multival: false,
..Default::default()
},
);
schema schema
} }
@ -234,24 +208,58 @@ mod testing {
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
let mut subquery = ConjoiningClauses::default(); let mut subquery = ConjoiningClauses::default();
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1), subquery.from = vec![
SourceAlias(DatomsTable::Datoms, d2)]; SourceAlias(DatomsTable::Datoms, d1),
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]); SourceAlias(DatomsTable::Datoms, d2),
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), parent)), ];
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), ambar)), subquery
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), knows.clone())), .column_bindings
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), daphne)), .insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))), subquery.wheres = ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone())))]); ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d1a.clone(),
parent,
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), ambar)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d2a.clone(),
knows.clone(),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d2v.clone(),
daphne,
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d1e.clone()),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d2e.clone()),
)),
]);
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref)); subquery
.known_types
.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![ assert_eq!(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows.clone())), cc.wheres,
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), john)), ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
])); d0a.clone(),
knows.clone()
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0v.clone(),
john
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(
ComputedTable::Subquery(subquery)
)),
])
);
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e])); assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e]));
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]); assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]);
} }
@ -302,31 +310,72 @@ mod testing {
let mut subquery = ConjoiningClauses::default(); let mut subquery = ConjoiningClauses::default();
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d3)]; subquery.from = vec![SourceAlias(DatomsTable::Datoms, d3)];
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d3e.clone()]); subquery
subquery.column_bindings.insert(vy.clone(), vec![d0v.clone(), d3v.clone()]); .column_bindings
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d3a.clone(), parent)), .insert(vx.clone(), vec![d0e.clone(), d3e.clone()]);
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d3e.clone()))), subquery
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), QueryValue::Column(d3v.clone())))]); .column_bindings
.insert(vy.clone(), vec![d0v.clone(), d3v.clone()]);
subquery.wheres = ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d3a.clone(),
parent,
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d3e.clone()),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0v.clone(),
QueryValue::Column(d3v.clone()),
)),
]);
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref)); subquery
subquery.known_types.insert(vy.clone(), ValueTypeSet::of_one(ValueType::String)); .known_types
.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
subquery
.known_types
.insert(vy.clone(), ValueTypeSet::of_one(ValueType::String));
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
let expected_wheres = ColumnIntersection(vec![ let expected_wheres = ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), age.clone())), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), eleven)), d1a.clone(),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), name.clone())), age.clone(),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), john)), )),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))), d1v.clone(),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone()))), eleven,
]); )),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d2a.clone(),
name.clone(),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), john)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(
ComputedTable::Subquery(subquery),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d1e.clone()),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d2e.clone()),
)),
]);
assert_eq!(cc.wheres, expected_wheres); assert_eq!(cc.wheres, expected_wheres);
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e, d2e])); assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e, d2e]));
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0), assert_eq!(
SourceAlias(DatomsTable::Datoms, d1), cc.from,
SourceAlias(DatomsTable::Datoms, d2)]); vec![
SourceAlias(DatomsTable::Datoms, d0),
SourceAlias(DatomsTable::Datoms, d1),
SourceAlias(DatomsTable::Datoms, d2)
]
);
} }
// Not with a pattern and a predicate. // Not with a pattern and a predicate.
@ -366,28 +415,62 @@ mod testing {
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
let mut subquery = ConjoiningClauses::default(); let mut subquery = ConjoiningClauses::default();
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1), subquery.from = vec![
SourceAlias(DatomsTable::Datoms, d2)]; SourceAlias(DatomsTable::Datoms, d1),
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]); SourceAlias(DatomsTable::Datoms, d2),
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())), ];
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john.clone())), subquery
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), knows.clone())), .column_bindings
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), daphne.clone())), .insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))), subquery.wheres = ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone())))]); ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d1a.clone(),
knows.clone(),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d1v.clone(),
john.clone(),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d2a.clone(),
knows.clone(),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d2v.clone(),
daphne.clone(),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d1e.clone()),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d2e.clone()),
)),
]);
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref)); subquery
.known_types
.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![ assert_eq!(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), age.clone())), cc.wheres,
ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0a.clone(),
age.clone()
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Inequality { ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Inequality {
operator: Inequality::LessThan, operator: Inequality::LessThan,
left: QueryValue::Column(d0v.clone()), left: QueryValue::Column(d0v.clone()),
right: QueryValue::TypedValue(TypedValue::Long(30)), right: QueryValue::TypedValue(TypedValue::Long(30)),
}), }),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(
])); ComputedTable::Subquery(subquery)
)),
])
);
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e])); assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e]));
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]); assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]);
} }
@ -429,32 +512,76 @@ mod testing {
let ambar = QueryValue::TypedValue(TypedValue::typed_string("Ámbar")); let ambar = QueryValue::TypedValue(TypedValue::typed_string("Ámbar"));
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
let mut subquery = ConjoiningClauses::default(); let mut subquery = ConjoiningClauses::default();
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1), subquery.from = vec![
SourceAlias(DatomsTable::Datoms, d2)]; SourceAlias(DatomsTable::Datoms, d1),
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]); SourceAlias(DatomsTable::Datoms, d2),
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![ ];
ColumnIntersection(vec![ subquery
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())), .column_bindings
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john))]), .insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
ColumnIntersection(vec![ subquery.wheres = ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())), ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), ambar))]), ColumnIntersection(vec![
])), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), parent)), d1a.clone(),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), daphne)), knows.clone(),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))), )),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone())))]); ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d1v.clone(),
john,
)),
]),
ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d1a.clone(),
knows.clone(),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d1v.clone(),
ambar,
)),
]),
])),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d2a.clone(),
parent,
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d2v.clone(),
daphne,
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d1e.clone()),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d2e.clone()),
)),
]);
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref)); subquery
.known_types
.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![ assert_eq!(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)), cc.wheres,
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), bill)), ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
])); d0a.clone(),
knows
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0v.clone(),
bill
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(
ComputedTable::Subquery(subquery)
)),
])
);
} }
// not-join with an input variable // not-join with an input variable
@ -467,9 +594,10 @@ mod testing {
:where [?x :foo/knows "Bill"] :where [?x :foo/knows "Bill"]
(not [?x :foo/knows ?y])]"#; (not [?x :foo/knows ?y])]"#;
let inputs = QueryInputs::with_value_sequence(vec![ let inputs = QueryInputs::with_value_sequence(vec![(
(Variable::from_valid_name("?y"), "John".into()) Variable::from_valid_name("?y"),
]); "John".into(),
)]);
let cc = alg_with_inputs(&schema, query, inputs); let cc = alg_with_inputs(&schema, query, inputs);
let vx = Variable::from_valid_name("?x"); let vx = Variable::from_valid_name("?x");
@ -492,25 +620,52 @@ mod testing {
let mut subquery = ConjoiningClauses::default(); let mut subquery = ConjoiningClauses::default();
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1)]; subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1)];
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone()]); subquery
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())), .column_bindings
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john)), .insert(vx.clone(), vec![d0e.clone(), d1e.clone()]);
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone())))]); subquery.wheres = ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d1a.clone(),
knows.clone(),
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d1e.clone()),
)),
]);
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref)); subquery
subquery.known_types.insert(vy.clone(), ValueTypeSet::of_one(ValueType::String)); .known_types
.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
subquery
.known_types
.insert(vy.clone(), ValueTypeSet::of_one(ValueType::String));
let mut input_vars: BTreeSet<Variable> = BTreeSet::default(); let mut input_vars: BTreeSet<Variable> = BTreeSet::default();
input_vars.insert(vy.clone()); input_vars.insert(vy.clone());
subquery.input_variables = input_vars; subquery.input_variables = input_vars;
subquery.value_bindings.insert(vy.clone(), TypedValue::typed_string("John")); subquery
.value_bindings
.insert(vy.clone(), TypedValue::typed_string("John"));
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![ assert_eq!(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)), cc.wheres,
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), bill)), ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
])); d0a.clone(),
knows
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0v.clone(),
bill
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(
ComputedTable::Subquery(subquery)
)),
])
);
} }
// Test that if any single clause in the `not` fails to resolve the whole clause is considered empty // Test that if any single clause in the `not` fails to resolve the whole clause is considered empty
@ -525,9 +680,10 @@ mod testing {
[?x :foo/nope "Daphne"])]"#; [?x :foo/nope "Daphne"])]"#;
let cc = alg(&schema, query); let cc = alg(&schema, query);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
compare_ccs(cc, compare_ccs(
alg(&schema, cc,
r#"[:find ?x :where [?x :foo/knows "Bill"]]"#)); alg(&schema, r#"[:find ?x :where [?x :foo/knows "Bill"]]"#),
);
} }
/// Test that if all the attributes in an `not` fail to resolve, the `cc` isn't considered empty. /// Test that if all the attributes in an `not` fail to resolve, the `cc` isn't considered empty.
@ -541,9 +697,10 @@ mod testing {
[?x :foo/nope "Daphne"])]"#; [?x :foo/nope "Daphne"])]"#;
let cc = alg(&schema, query); let cc = alg(&schema, query);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
compare_ccs(cc, compare_ccs(
alg(&schema, r#"[:find ?x :where [?x :foo/knows "John"]]"#)); cc,
alg(&schema, r#"[:find ?x :where [?x :foo/knows "John"]]"#),
);
} }
#[test] #[test]
@ -557,7 +714,9 @@ mod testing {
let parsed = parse_find_string(query).expect("parse failed"); let parsed = parse_find_string(query).expect("parse failed");
let err = algebrize(known, parsed).expect_err("algebrization should have failed"); let err = algebrize(known, parsed).expect_err("algebrization should have failed");
match err { match err {
AlgebrizerError::UnboundVariable(var) => { assert_eq!(var, PlainSymbol("?x".to_string())); }, AlgebrizerError::UnboundVariable(var) => {
assert_eq!(var, PlainSymbol("?x".to_string()));
}
x => panic!("expected Unbound Variable error, got {:?}", x), x => panic!("expected Unbound Variable error, got {:?}", x),
} }
} }

View file

@ -9,46 +9,22 @@
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use std::collections::btree_map::Entry; use std::collections::btree_map::Entry;
use std::collections::{ use std::collections::{BTreeMap, BTreeSet};
BTreeMap,
BTreeSet,
};
use core_traits::{ use core_traits::ValueTypeSet;
ValueTypeSet,
};
use edn::query::{ use edn::query::{
OrJoin, OrJoin, OrWhereClause, Pattern, PatternNonValuePlace, PatternValuePlace, UnifyVars, Variable,
OrWhereClause,
Pattern,
PatternValuePlace,
PatternNonValuePlace,
UnifyVars,
Variable,
WhereClause, WhereClause,
}; };
use clauses::{ use clauses::{ConjoiningClauses, PushComputed};
ConjoiningClauses,
PushComputed,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::Result;
Result,
};
use types::{ use types::{
ColumnConstraintOrAlternation, ColumnAlternation, ColumnConstraintOrAlternation, ColumnIntersection, ComputedTable,
ColumnAlternation, DatomsTable, EmptyBecause, EvolvedPattern, PlaceOrEmpty, QualifiedAlias, SourceAlias,
ColumnIntersection,
ComputedTable,
DatomsTable,
EmptyBecause,
EvolvedPattern,
PlaceOrEmpty,
QualifiedAlias,
SourceAlias,
VariableColumn, VariableColumn,
}; };
@ -59,10 +35,10 @@ fn _simply_matches_place(left: &PatternNonValuePlace, right: &PatternNonValuePla
match (left, right) { match (left, right) {
(&PatternNonValuePlace::Variable(ref a), &PatternNonValuePlace::Variable(ref b)) => a == b, (&PatternNonValuePlace::Variable(ref a), &PatternNonValuePlace::Variable(ref b)) => a == b,
(&PatternNonValuePlace::Placeholder, &PatternNonValuePlace::Placeholder) => true, (&PatternNonValuePlace::Placeholder, &PatternNonValuePlace::Placeholder) => true,
(&PatternNonValuePlace::Entid(_), &PatternNonValuePlace::Entid(_)) => true, (&PatternNonValuePlace::Entid(_), &PatternNonValuePlace::Entid(_)) => true,
(&PatternNonValuePlace::Entid(_), &PatternNonValuePlace::Ident(_)) => true, (&PatternNonValuePlace::Entid(_), &PatternNonValuePlace::Ident(_)) => true,
(&PatternNonValuePlace::Ident(_), &PatternNonValuePlace::Ident(_)) => true, (&PatternNonValuePlace::Ident(_), &PatternNonValuePlace::Ident(_)) => true,
(&PatternNonValuePlace::Ident(_), &PatternNonValuePlace::Entid(_)) => true, (&PatternNonValuePlace::Ident(_), &PatternNonValuePlace::Entid(_)) => true,
_ => false, _ => false,
} }
} }
@ -101,7 +77,7 @@ impl ConjoiningClauses {
OrWhereClause::And(clauses) => { OrWhereClause::And(clauses) => {
self.apply_clauses(known, clauses)?; self.apply_clauses(known, clauses)?;
Ok(()) Ok(())
}, }
} }
} }
@ -117,7 +93,7 @@ impl ConjoiningClauses {
1 if or_join.is_fully_unified() => { 1 if or_join.is_fully_unified() => {
let clause = or_join.clauses.pop().expect("there's a clause"); let clause = or_join.clauses.pop().expect("there's a clause");
self.apply_or_where_clause(known, clause) self.apply_or_where_clause(known, clause)
}, }
// Either there's only one clause pattern, and it's not fully unified, or we // Either there's only one clause pattern, and it's not fully unified, or we
// have multiple clauses. // have multiple clauses.
// In the former case we can't just apply it: it includes a variable that we don't want // In the former case we can't just apply it: it includes a variable that we don't want
@ -222,12 +198,10 @@ impl ConjoiningClauses {
let table = match self.make_evolved_attribute(&known, p.attribute.clone()) { let table = match self.make_evolved_attribute(&known, p.attribute.clone()) {
Place((aaa, value_type)) => { Place((aaa, value_type)) => {
match self.make_evolved_value(&known, value_type, p.value.clone()) { match self.make_evolved_value(&known, value_type, p.value.clone()) {
Place(v) => { Place(v) => self.table_for_places(known.schema, &aaa, &v),
self.table_for_places(known.schema, &aaa, &v)
},
Empty(e) => Err(e), Empty(e) => Err(e),
} }
}, }
Empty(e) => Err(e), Empty(e) => Err(e),
}; };
@ -237,20 +211,19 @@ impl ConjoiningClauses {
// Do not accumulate this pattern at all. Add lightness! // Do not accumulate this pattern at all. Add lightness!
continue; continue;
}, }
Ok(table) => { Ok(table) => {
// Check the shape of the pattern against a previous pattern. // Check the shape of the pattern against a previous pattern.
let same_shape = let same_shape = if let Some(template) = patterns.get(0) {
if let Some(template) = patterns.get(0) { template.source == p.source && // or-arms all use the same source anyway.
template.source == p.source && // or-arms all use the same source anyway.
_simply_matches_place(&template.entity, &p.entity) && _simply_matches_place(&template.entity, &p.entity) &&
_simply_matches_place(&template.attribute, &p.attribute) && _simply_matches_place(&template.attribute, &p.attribute) &&
_simply_matches_value_place(&template.value, &p.value) && _simply_matches_value_place(&template.value, &p.value) &&
_simply_matches_place(&template.tx, &p.tx) _simply_matches_place(&template.tx, &p.tx)
} else { } else {
// No previous pattern. // No previous pattern.
true true
}; };
// All of our clauses that _do_ yield a table -- that are possible -- // All of our clauses that _do_ yield a table -- that are possible --
// must use the same table in order for this to be a simple `or`! // must use the same table in order for this to be a simple `or`!
@ -286,10 +259,7 @@ impl ConjoiningClauses {
.chain(clauses) .chain(clauses)
.collect(); .collect();
return DeconstructedOrJoin::Complex(OrJoin::new( return DeconstructedOrJoin::Complex(OrJoin::new(UnifyVars::Implicit, reconstructed));
UnifyVars::Implicit,
reconstructed,
));
} }
// If we got here without returning, then `patterns` is what we're working with. // If we got here without returning, then `patterns` is what we're working with.
@ -298,7 +268,7 @@ impl ConjoiningClauses {
0 => { 0 => {
assert!(empty_because.is_some()); assert!(empty_because.is_some());
DeconstructedOrJoin::KnownEmpty(empty_because.unwrap()) DeconstructedOrJoin::KnownEmpty(empty_because.unwrap())
}, }
1 => DeconstructedOrJoin::UnitPattern(patterns.pop().unwrap()), 1 => DeconstructedOrJoin::UnitPattern(patterns.pop().unwrap()),
_ => DeconstructedOrJoin::Simple(patterns, mentioned_vars), _ => DeconstructedOrJoin::Simple(patterns, mentioned_vars),
} }
@ -309,43 +279,42 @@ impl ConjoiningClauses {
DeconstructedOrJoin::KnownSuccess => { DeconstructedOrJoin::KnownSuccess => {
// The pattern came to us empty -- `(or)`. Do nothing. // The pattern came to us empty -- `(or)`. Do nothing.
Ok(()) Ok(())
}, }
DeconstructedOrJoin::KnownEmpty(reason) => { DeconstructedOrJoin::KnownEmpty(reason) => {
// There were no arms of the join that could be mapped to a table. // There were no arms of the join that could be mapped to a table.
// The entire `or`, and thus the CC, cannot yield results. // The entire `or`, and thus the CC, cannot yield results.
self.mark_known_empty(reason); self.mark_known_empty(reason);
Ok(()) Ok(())
}, }
DeconstructedOrJoin::Unit(clause) => { DeconstructedOrJoin::Unit(clause) => {
// There was only one clause. We're unifying all variables, so we can just apply here. // There was only one clause. We're unifying all variables, so we can just apply here.
self.apply_or_where_clause(known, clause) self.apply_or_where_clause(known, clause)
}, }
DeconstructedOrJoin::UnitPattern(pattern) => { DeconstructedOrJoin::UnitPattern(pattern) => {
// Same, but simpler. // Same, but simpler.
match self.make_evolved_pattern(known, pattern) { match self.make_evolved_pattern(known, pattern) {
PlaceOrEmpty::Empty(e) => { PlaceOrEmpty::Empty(e) => {
self.mark_known_empty(e); self.mark_known_empty(e);
}, }
PlaceOrEmpty::Place(pattern) => { PlaceOrEmpty::Place(pattern) => {
self.apply_pattern(known, pattern); self.apply_pattern(known, pattern);
}, }
}; };
Ok(()) Ok(())
}, }
DeconstructedOrJoin::Simple(patterns, mentioned_vars) => { DeconstructedOrJoin::Simple(patterns, mentioned_vars) => {
// Hooray! Fully unified and plain ol' patterns that all use the same table. // Hooray! Fully unified and plain ol' patterns that all use the same table.
// Go right ahead and produce a set of constraint alternations that we can collect, // Go right ahead and produce a set of constraint alternations that we can collect,
// using a single table alias. // using a single table alias.
self.apply_simple_or_join(known, patterns, mentioned_vars) self.apply_simple_or_join(known, patterns, mentioned_vars)
}, }
DeconstructedOrJoin::Complex(or_join) => { DeconstructedOrJoin::Complex(or_join) => {
// Do this the hard way. // Do this the hard way.
self.apply_complex_or_join(known, or_join) self.apply_complex_or_join(known, or_join)
}, }
} }
} }
/// A simple `or` join is effectively a single pattern in which an individual column's bindings /// A simple `or` join is effectively a single pattern in which an individual column's bindings
/// are not a single value. Rather than a pattern like /// are not a single value. Rather than a pattern like
/// ///
@ -373,27 +342,30 @@ impl ConjoiningClauses {
/// OR (datoms00.a = 98 AND datoms00.v = 'Peter') /// OR (datoms00.a = 98 AND datoms00.v = 'Peter')
/// ``` /// ```
/// ///
fn apply_simple_or_join(&mut self, fn apply_simple_or_join(
known: Known, &mut self,
patterns: Vec<Pattern>, known: Known,
mentioned_vars: BTreeSet<Variable>) patterns: Vec<Pattern>,
-> Result<()> { mentioned_vars: BTreeSet<Variable>,
) -> Result<()> {
if self.is_known_empty() { if self.is_known_empty() {
return Ok(()) return Ok(());
} }
assert!(patterns.len() >= 2); assert!(patterns.len() >= 2);
let patterns: Vec<EvolvedPattern> = patterns.into_iter().filter_map(|pattern| { let patterns: Vec<EvolvedPattern> = patterns
match self.make_evolved_pattern(known, pattern) { .into_iter()
PlaceOrEmpty::Empty(_e) => { .filter_map(|pattern| {
// Never mind. match self.make_evolved_pattern(known, pattern) {
None PlaceOrEmpty::Empty(_e) => {
}, // Never mind.
PlaceOrEmpty::Place(p) => Some(p), None
} }
}).collect(); PlaceOrEmpty::Place(p) => Some(p),
}
})
.collect();
// Begin by building a base CC that we'll use to produce constraints from each pattern. // Begin by building a base CC that we'll use to produce constraints from each pattern.
// Populate this base CC with whatever variables are already known from the CC to which // Populate this base CC with whatever variables are already known from the CC to which
@ -405,7 +377,9 @@ impl ConjoiningClauses {
// We expect this to always work: if it doesn't, it means we should never have got to this // We expect this to always work: if it doesn't, it means we should never have got to this
// point. // point.
let source_alias = self.alias_table(known.schema, &patterns[0]).expect("couldn't get table"); let source_alias = self
.alias_table(known.schema, &patterns[0])
.expect("couldn't get table");
// This is where we'll collect everything we eventually add to the destination CC. // This is where we'll collect everything we eventually add to the destination CC.
let mut folded = ConjoiningClauses::default(); let mut folded = ConjoiningClauses::default();
@ -432,24 +406,26 @@ impl ConjoiningClauses {
// :where [?a :some/int ?x] // :where [?a :some/int ?x]
// [_ :some/otherint ?x]] // [_ :some/otherint ?x]]
// ``` // ```
let mut receptacles = let mut receptacles = patterns
patterns.into_iter() .into_iter()
.map(|pattern| { .map(|pattern| {
let mut receptacle = template.make_receptacle(); let mut receptacle = template.make_receptacle();
receptacle.apply_pattern_clause_for_alias(known, &pattern, &source_alias); receptacle.apply_pattern_clause_for_alias(known, &pattern, &source_alias);
receptacle receptacle
}) })
.peekable(); .peekable();
// Let's see if we can grab a reason if every pattern failed. // Let's see if we can grab a reason if every pattern failed.
// If every pattern failed, we can just take the first! // If every pattern failed, we can just take the first!
let reason = receptacles.peek() let reason = receptacles
.map(|r| r.empty_because.clone()) .peek()
.unwrap_or(None); .map(|r| r.empty_because.clone())
.unwrap_or(None);
// Filter out empties. // Filter out empties.
let mut receptacles = receptacles.filter(|receptacle| !receptacle.is_known_empty()) let mut receptacles = receptacles
.peekable(); .filter(|receptacle| !receptacle.is_known_empty())
.peekable();
// We need to copy the column bindings from one of the receptacles. Because this is a simple // We need to copy the column bindings from one of the receptacles. Because this is a simple
// or, we know that they're all the same. // or, we know that they're all the same.
@ -460,10 +436,10 @@ impl ConjoiningClauses {
match self.column_bindings.entry(v.clone()) { match self.column_bindings.entry(v.clone()) {
Entry::Vacant(e) => { Entry::Vacant(e) => {
e.insert(cols.clone()); e.insert(cols.clone());
}, }
Entry::Occupied(mut e) => { Entry::Occupied(mut e) => {
e.get_mut().append(&mut cols.clone()); e.get_mut().append(&mut cols.clone());
}, }
} }
} }
} else { } else {
@ -483,7 +459,10 @@ impl ConjoiningClauses {
// we might know that if `?x` is an integer then `?y` is a string, or vice versa, but at // we might know that if `?x` is an integer then `?y` is a string, or vice versa, but at
// this point we'll simply state that `?x` and `?y` can both be integers or strings. // this point we'll simply state that `?x` and `?y` can both be integers or strings.
fn vec_for_iterator<T, I, U>(iter: &I) -> Vec<T> where I: Iterator<Item=U> { fn vec_for_iterator<T, I, U>(iter: &I) -> Vec<T>
where
I: Iterator<Item = U>,
{
match iter.size_hint().1 { match iter.size_hint().1 {
None => Vec::new(), None => Vec::new(),
Some(expected) => Vec::with_capacity(expected), Some(expected) => Vec::with_capacity(expected),
@ -593,10 +572,10 @@ impl ConjoiningClauses {
match clause { match clause {
OrWhereClause::And(clauses) => { OrWhereClause::And(clauses) => {
receptacle.apply_clauses(known, clauses)?; receptacle.apply_clauses(known, clauses)?;
}, }
OrWhereClause::Clause(clause) => { OrWhereClause::Clause(clause) => {
receptacle.apply_clause(known, clause)?; receptacle.apply_clause(known, clause)?;
}, }
} }
if receptacle.is_known_empty() { if receptacle.is_known_empty() {
empty_because = receptacle.empty_because; empty_because = receptacle.empty_because;
@ -681,10 +660,11 @@ impl ConjoiningClauses {
// Note that we start with the first clause's type information. // Note that we start with the first clause's type information.
{ {
let mut clauses = acc.iter(); let mut clauses = acc.iter();
let mut additional_types = clauses.next() let mut additional_types = clauses
.expect("there to be at least one clause") .next()
.known_types .expect("there to be at least one clause")
.clone(); .known_types
.clone();
for cc in clauses { for cc in clauses {
union_types(&mut additional_types, &cc.known_types); union_types(&mut additional_types, &cc.known_types);
} }
@ -702,10 +682,18 @@ impl ConjoiningClauses {
// Stitch the computed table into column_bindings, so we get cross-linking. // Stitch the computed table into column_bindings, so we get cross-linking.
let schema = known.schema; let schema = known.schema;
for var in var_associations.into_iter() { for var in var_associations.into_iter() {
self.bind_column_to_var(schema, alias.clone(), VariableColumn::Variable(var.clone()), var); self.bind_column_to_var(
schema,
alias.clone(),
VariableColumn::Variable(var.clone()),
var,
);
} }
for var in type_associations.into_iter() { for var in type_associations.into_iter() {
self.extracted_types.insert(var.clone(), QualifiedAlias::new(alias.clone(), VariableColumn::VariableTypeTag(var))); self.extracted_types.insert(
var.clone(),
QualifiedAlias::new(alias.clone(), VariableColumn::VariableTypeTag(var)),
);
} }
self.from.push(SourceAlias(table, alias)); self.from.push(SourceAlias(table, alias));
Ok(()) Ok(())
@ -713,8 +701,10 @@ impl ConjoiningClauses {
} }
/// Helper to fold together a set of type maps. /// Helper to fold together a set of type maps.
fn union_types(into: &mut BTreeMap<Variable, ValueTypeSet>, fn union_types(
additional_types: &BTreeMap<Variable, ValueTypeSet>) { into: &mut BTreeMap<Variable, ValueTypeSet>,
additional_types: &BTreeMap<Variable, ValueTypeSet>,
) {
// We want the exclusive disjunction -- any variable not mentioned in both sets -- to default // We want the exclusive disjunction -- any variable not mentioned in both sets -- to default
// to ValueTypeSet::Any. // to ValueTypeSet::Any.
// This is necessary because we lazily populate known_types, so sometimes the type set will // This is necessary because we lazily populate known_types, so sometimes the type set will
@ -727,9 +717,10 @@ fn union_types(into: &mut BTreeMap<Variable, ValueTypeSet>,
{ {
let i: BTreeSet<&Variable> = into.keys().collect(); let i: BTreeSet<&Variable> = into.keys().collect();
let a: BTreeSet<&Variable> = additional_types.keys().collect(); let a: BTreeSet<&Variable> = additional_types.keys().collect();
any = i.symmetric_difference(&a) any = i
.map(|v| ((*v).clone(), ValueTypeSet::any())) .symmetric_difference(&a)
.collect(); .map(|v| ((*v).clone(), ValueTypeSet::any()))
.collect();
} }
// Collect the additional types. // Collect the additional types.
@ -737,11 +728,11 @@ fn union_types(into: &mut BTreeMap<Variable, ValueTypeSet>,
match into.entry(var.clone()) { match into.entry(var.clone()) {
Entry::Vacant(e) => { Entry::Vacant(e) => {
e.insert(new_types.clone()); e.insert(new_types.clone());
}, }
Entry::Occupied(mut e) => { Entry::Occupied(mut e) => {
let new = e.get().union(&new_types); let new = e.get().union(&new_types);
e.insert(new); e.insert(new);
}, }
} }
} }
@ -753,41 +744,20 @@ fn union_types(into: &mut BTreeMap<Variable, ValueTypeSet>,
mod testing { mod testing {
use super::*; use super::*;
use core_traits::{ use core_traits::{Attribute, TypedValue, ValueType};
Attribute,
ValueType,
TypedValue,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::query::{ use edn::query::{Keyword, Variable};
Keyword,
Variable,
};
use clauses::{ use clauses::{add_attribute, associate_ident};
add_attribute,
associate_ident,
};
use types::{ use types::{
ColumnConstraint, ColumnConstraint, DatomsColumn, DatomsTable, Inequality, QualifiedAlias, QueryValue,
DatomsColumn,
DatomsTable,
Inequality,
QualifiedAlias,
QueryValue,
SourceAlias, SourceAlias,
}; };
use { use {algebrize, algebrize_with_counter, parse_find_string};
algebrize,
algebrize_with_counter,
parse_find_string,
};
fn alg(known: Known, input: &str) -> ConjoiningClauses { fn alg(known: Known, input: &str) -> ConjoiningClauses {
let parsed = parse_find_string(input).expect("parse failed"); let parsed = parse_find_string(input).expect("parse failed");
@ -798,7 +768,9 @@ mod testing {
/// simpler version. /// simpler version.
fn alg_c(known: Known, counter: usize, input: &str) -> ConjoiningClauses { fn alg_c(known: Known, counter: usize, input: &str) -> ConjoiningClauses {
let parsed = parse_find_string(input).expect("parse failed"); let parsed = parse_find_string(input).expect("parse failed");
algebrize_with_counter(known, parsed, counter).expect("algebrize failed").cc algebrize_with_counter(known, parsed, counter)
.expect("algebrize failed")
.cc
} }
fn compare_ccs(left: ConjoiningClauses, right: ConjoiningClauses) { fn compare_ccs(left: ConjoiningClauses, right: ConjoiningClauses) {
@ -813,31 +785,51 @@ mod testing {
associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67); associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67);
associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68); associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68);
associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69); associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69);
add_attribute(&mut schema, 65, Attribute { add_attribute(
value_type: ValueType::String, &mut schema,
multival: false, 65,
..Default::default() Attribute {
}); value_type: ValueType::String,
add_attribute(&mut schema, 66, Attribute { multival: false,
value_type: ValueType::String, ..Default::default()
multival: true, },
..Default::default() );
}); add_attribute(
add_attribute(&mut schema, 67, Attribute { &mut schema,
value_type: ValueType::String, 66,
multival: true, Attribute {
..Default::default() value_type: ValueType::String,
}); multival: true,
add_attribute(&mut schema, 68, Attribute { ..Default::default()
value_type: ValueType::Long, },
multival: false, );
..Default::default() add_attribute(
}); &mut schema,
add_attribute(&mut schema, 69, Attribute { 67,
value_type: ValueType::Long, Attribute {
multival: false, value_type: ValueType::String,
..Default::default() multival: true,
}); ..Default::default()
},
);
add_attribute(
&mut schema,
68,
Attribute {
value_type: ValueType::Long,
multival: false,
..Default::default()
},
);
add_attribute(
&mut schema,
69,
Attribute {
value_type: ValueType::Long,
multival: false,
..Default::default()
},
);
schema schema
} }
@ -853,7 +845,12 @@ mod testing {
[?x :foo/nope3 "Daphne"])]"#; [?x :foo/nope3 "Daphne"])]"#;
let cc = alg(known, query); let cc = alg(known, query);
assert!(cc.is_known_empty()); assert!(cc.is_known_empty());
assert_eq!(cc.empty_because, Some(EmptyBecause::UnresolvedIdent(Keyword::namespaced("foo", "nope3")))); assert_eq!(
cc.empty_because,
Some(EmptyBecause::UnresolvedIdent(Keyword::namespaced(
"foo", "nope3"
)))
);
} }
/// Test that if only one of the attributes in an `or` resolves, it's equivalent to a simple query. /// Test that if only one of the attributes in an `or` resolves, it's equivalent to a simple query.
@ -868,7 +865,10 @@ mod testing {
[?x :foo/nope "Daphne"])]"#; [?x :foo/nope "Daphne"])]"#;
let cc = alg(known, query); let cc = alg(known, query);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
compare_ccs(cc, alg(known, r#"[:find ?x :where [?x :foo/parent "Ámbar"]]"#)); compare_ccs(
cc,
alg(known, r#"[:find ?x :where [?x :foo/parent "Ámbar"]]"#),
);
} }
// Simple alternation. // Simple alternation.
@ -894,19 +894,43 @@ mod testing {
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![ assert_eq!(
ColumnConstraintOrAlternation::Alternation( cc.wheres,
ColumnIntersection(vec![ColumnConstraintOrAlternation::Alternation(
ColumnAlternation(vec![ ColumnAlternation(vec![
ColumnIntersection(vec![ ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows.clone())), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), john))]), d0a.clone(),
knows.clone()
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0v.clone(),
john
))
]),
ColumnIntersection(vec![ ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), parent)), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), ambar))]), d0a.clone(),
parent
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0v.clone(),
ambar
))
]),
ColumnIntersection(vec![ ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), daphne))]), d0a.clone(),
]))])); knows
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0v.clone(),
daphne
))
]),
])
)])
);
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e])); assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e]));
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]); assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]);
} }
@ -940,26 +964,60 @@ mod testing {
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![ assert_eq!(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), name.clone())), cc.wheres,
ColumnConstraintOrAlternation::Alternation( ColumnIntersection(vec![
ColumnAlternation(vec![ ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0a.clone(),
name.clone()
)),
ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![
ColumnIntersection(vec![ ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john))]), d1a.clone(),
knows.clone()
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d1v.clone(),
john
))
]),
ColumnIntersection(vec![ ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), parent)), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), ambar))]), d1a.clone(),
parent
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d1v.clone(),
ambar
))
]),
ColumnIntersection(vec![ ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows)), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), daphne))]), d1a.clone(),
])), knows
// The outer pattern joins against the `or`. )),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
])); d1v.clone(),
daphne
))
]),
])),
// The outer pattern joins against the `or`.
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d1e.clone())
)),
])
);
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e])); assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e]));
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0), assert_eq!(
SourceAlias(DatomsTable::Datoms, d1)]); cc.from,
vec![
SourceAlias(DatomsTable::Datoms, d0),
SourceAlias(DatomsTable::Datoms, d1)
]
);
} }
// Alternation with a pattern and a predicate. // Alternation with a pattern and a predicate.
@ -990,28 +1048,55 @@ mod testing {
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![ assert_eq!(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), age.clone())), cc.wheres,
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Inequality { ColumnIntersection(vec![
operator: Inequality::LessThan, ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
left: QueryValue::Column(d0v.clone()), d0a.clone(),
right: QueryValue::TypedValue(TypedValue::Long(30)), age.clone()
}), )),
ColumnConstraintOrAlternation::Alternation( ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Inequality {
ColumnAlternation(vec![ operator: Inequality::LessThan,
left: QueryValue::Column(d0v.clone()),
right: QueryValue::TypedValue(TypedValue::Long(30)),
}),
ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![
ColumnIntersection(vec![ ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john))]), d1a.clone(),
knows.clone()
)),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d1v.clone(),
john
))
]),
ColumnIntersection(vec![ ColumnIntersection(vec![
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows)), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), daphne))]), d1a.clone(),
])), knows
// The outer pattern joins against the `or`. )),
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
])); d1v.clone(),
daphne
))
]),
])),
// The outer pattern joins against the `or`.
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(d1e.clone())
)),
])
);
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e])); assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e]));
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0), assert_eq!(
SourceAlias(DatomsTable::Datoms, d1)]); cc.from,
vec![
SourceAlias(DatomsTable::Datoms, d0),
SourceAlias(DatomsTable::Datoms, d1)
]
);
} }
// These two are not equivalent: // These two are not equivalent:
@ -1036,18 +1121,32 @@ mod testing {
let knows = QueryValue::Entid(66); let knows = QueryValue::Entid(66);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.wheres, ColumnIntersection(vec![ assert_eq!(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows.clone())), cc.wheres,
// The outer pattern joins against the `or` on the entity, but not value -- ?y means ColumnIntersection(vec![
// different things in each place. ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(c0x.clone()))), d0a.clone(),
])); knows.clone()
)),
// The outer pattern joins against the `or` on the entity, but not value -- ?y means
// different things in each place.
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
d0e.clone(),
QueryValue::Column(c0x.clone())
)),
])
);
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, c0x])); assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, c0x]));
// ?y does not have a binding in the `or-join` pattern. // ?y does not have a binding in the `or-join` pattern.
assert_eq!(cc.column_bindings.get(&vy), Some(&vec![d0v])); assert_eq!(cc.column_bindings.get(&vy), Some(&vec![d0v]));
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0), assert_eq!(
SourceAlias(DatomsTable::Computed(0), c0)]); cc.from,
vec![
SourceAlias(DatomsTable::Datoms, d0),
SourceAlias(DatomsTable::Computed(0), c0)
]
);
} }
// These two are equivalent: // These two are equivalent:
@ -1057,14 +1156,13 @@ mod testing {
fn test_unit_or_does_flatten() { fn test_unit_or_does_flatten() {
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let or_query = r#"[:find ?x let or_query = r#"[:find ?x
:where [?x :foo/knows ?y] :where [?x :foo/knows ?y]
(or [?x :foo/parent ?y])]"#; (or [?x :foo/parent ?y])]"#;
let flat_query = r#"[:find ?x let flat_query = r#"[:find ?x
:where [?x :foo/knows ?y] :where [?x :foo/knows ?y]
[?x :foo/parent ?y]]"#; [?x :foo/parent ?y]]"#;
compare_ccs(alg(known, or_query), compare_ccs(alg(known, or_query), alg(known, flat_query));
alg(known, flat_query));
} }
// Elision of `and`. // Elision of `and`.
@ -1072,14 +1170,13 @@ mod testing {
fn test_unit_or_and_does_flatten() { fn test_unit_or_and_does_flatten() {
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let or_query = r#"[:find ?x let or_query = r#"[:find ?x
:where (or (and [?x :foo/parent ?y] :where (or (and [?x :foo/parent ?y]
[?x :foo/age 7]))]"#; [?x :foo/age 7]))]"#;
let flat_query = r#"[:find ?x let flat_query = r#"[:find ?x
:where [?x :foo/parent ?y] :where [?x :foo/parent ?y]
[?x :foo/age 7]]"#; [?x :foo/age 7]]"#;
compare_ccs(alg(known, or_query), compare_ccs(alg(known, or_query), alg(known, flat_query));
alg(known, flat_query));
} }
// Alternation with `and`. // Alternation with `and`.
@ -1101,31 +1198,45 @@ mod testing {
let cc = alg(known, query); let cc = alg(known, query);
let mut tables = cc.computed_tables.into_iter(); let mut tables = cc.computed_tables.into_iter();
match (tables.next(), tables.next()) { match (tables.next(), tables.next()) {
(Some(ComputedTable::Union { projection, type_extraction, arms }), None) => { (
assert_eq!(projection, vec![Variable::from_valid_name("?x")].into_iter().collect()); Some(ComputedTable::Union {
projection,
type_extraction,
arms,
}),
None,
) => {
assert_eq!(
projection,
vec![Variable::from_valid_name("?x")].into_iter().collect()
);
assert!(type_extraction.is_empty()); assert!(type_extraction.is_empty());
let mut arms = arms.into_iter(); let mut arms = arms.into_iter();
match (arms.next(), arms.next(), arms.next()) { match (arms.next(), arms.next(), arms.next()) {
(Some(and), Some(pattern), None) => { (Some(and), Some(pattern), None) => {
let expected_and = alg_c(known, let expected_and = alg_c(
0, // The first pattern to be processed. known,
r#"[:find ?x :where [?x :foo/knows "John"] [?x :foo/parent "Ámbar"]]"#); 0, // The first pattern to be processed.
r#"[:find ?x :where [?x :foo/knows "John"] [?x :foo/parent "Ámbar"]]"#,
);
compare_ccs(and, expected_and); compare_ccs(and, expected_and);
let expected_pattern = alg_c(known, let expected_pattern = alg_c(
2, // Two aliases taken by the other arm. known,
r#"[:find ?x :where [?x :foo/knows "Daphne"]]"#); 2, // Two aliases taken by the other arm.
r#"[:find ?x :where [?x :foo/knows "Daphne"]]"#,
);
compare_ccs(pattern, expected_pattern); compare_ccs(pattern, expected_pattern);
}, }
_ => { _ => {
panic!("Expected two arms"); panic!("Expected two arms");
} }
} }
}, }
_ => { _ => {
panic!("Didn't get two inner tables."); panic!("Didn't get two inner tables.");
}, }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -8,37 +8,19 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use core_traits::{ use core_traits::{ValueType, ValueTypeSet};
ValueType,
ValueTypeSet,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::query::{ use edn::query::{FnArg, PlainSymbol, Predicate, TypeAnnotation};
FnArg,
PlainSymbol,
Predicate,
TypeAnnotation,
};
use clauses::ConjoiningClauses; use clauses::ConjoiningClauses;
use clauses::convert::ValueTypes; use clauses::convert::ValueTypes;
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, Result};
AlgebrizerError,
Result,
};
use types::{ use types::{ColumnConstraint, EmptyBecause, Inequality, QueryValue};
ColumnConstraint,
EmptyBecause,
Inequality,
QueryValue,
};
use Known; use Known;
@ -71,8 +53,14 @@ impl ConjoiningClauses {
/// to be a specific ValueType. /// to be a specific ValueType.
pub(crate) fn apply_type_anno(&mut self, anno: &TypeAnnotation) -> Result<()> { pub(crate) fn apply_type_anno(&mut self, anno: &TypeAnnotation) -> Result<()> {
match ValueType::from_keyword(&anno.value_type) { match ValueType::from_keyword(&anno.value_type) {
Some(value_type) => self.add_type_requirement(anno.variable.clone(), ValueTypeSet::of_one(value_type)), Some(value_type) => {
None => bail!(AlgebrizerError::InvalidArgumentType(PlainSymbol::plain("type"), ValueTypeSet::any(), 2)), self.add_type_requirement(anno.variable.clone(), ValueTypeSet::of_one(value_type))
}
None => bail!(AlgebrizerError::InvalidArgumentType(
PlainSymbol::plain("type"),
ValueTypeSet::any(),
2
)),
} }
Ok(()) Ok(())
} }
@ -81,9 +69,18 @@ impl ConjoiningClauses {
/// - Resolves variables and converts types to those more amenable to SQL. /// - Resolves variables and converts types to those more amenable to SQL.
/// - Ensures that the predicate functions name a known operator. /// - Ensures that the predicate functions name a known operator.
/// - Accumulates an `Inequality` constraint into the `wheres` list. /// - Accumulates an `Inequality` constraint into the `wheres` list.
pub(crate) fn apply_inequality(&mut self, known: Known, comparison: Inequality, predicate: Predicate) -> Result<()> { pub(crate) fn apply_inequality(
&mut self,
known: Known,
comparison: Inequality,
predicate: Predicate,
) -> Result<()> {
if predicate.args.len() != 2 { if predicate.args.len() != 2 {
bail!(AlgebrizerError::InvalidNumberOfArguments(predicate.operator.clone(), predicate.args.len(), 2)); bail!(AlgebrizerError::InvalidNumberOfArguments(
predicate.operator.clone(),
predicate.args.len(),
2
));
} }
// Go from arguments -- parser output -- to columns or values. // Go from arguments -- parser output -- to columns or values.
@ -93,20 +90,29 @@ impl ConjoiningClauses {
let left = args.next().expect("two args"); let left = args.next().expect("two args");
let right = args.next().expect("two args"); let right = args.next().expect("two args");
// The types we're handling here must be the intersection of the possible types of the arguments, // The types we're handling here must be the intersection of the possible types of the arguments,
// the known types of any variables, and the types supported by our inequality operators. // the known types of any variables, and the types supported by our inequality operators.
let supported_types = comparison.supported_types(); let supported_types = comparison.supported_types();
let mut left_types = self.potential_types(known.schema, &left)? let mut left_types = self
.intersection(&supported_types); .potential_types(known.schema, &left)?
.intersection(&supported_types);
if left_types.is_empty() { if left_types.is_empty() {
bail!(AlgebrizerError::InvalidArgumentType(predicate.operator.clone(), supported_types, 0)); bail!(AlgebrizerError::InvalidArgumentType(
predicate.operator.clone(),
supported_types,
0
));
} }
let mut right_types = self.potential_types(known.schema, &right)? let mut right_types = self
.intersection(&supported_types); .potential_types(known.schema, &right)?
.intersection(&supported_types);
if right_types.is_empty() { if right_types.is_empty() {
bail!(AlgebrizerError::InvalidArgumentType(predicate.operator.clone(), supported_types, 1)); bail!(AlgebrizerError::InvalidArgumentType(
predicate.operator.clone(),
supported_types,
1
));
} }
// We would like to allow longs to compare to doubles. // We would like to allow longs to compare to doubles.
@ -135,7 +141,8 @@ impl ConjoiningClauses {
left: left_types, left: left_types,
right: right_types, right: right_types,
} }
}); },
);
return Ok(()); return Ok(());
} }
@ -153,7 +160,11 @@ impl ConjoiningClauses {
left_v = self.resolve_ref_argument(known.schema, &predicate.operator, 0, left)?; left_v = self.resolve_ref_argument(known.schema, &predicate.operator, 0, left)?;
right_v = self.resolve_ref_argument(known.schema, &predicate.operator, 1, right)?; right_v = self.resolve_ref_argument(known.schema, &predicate.operator, 1, right)?;
} else { } else {
bail!(AlgebrizerError::InvalidArgumentType(predicate.operator.clone(), supported_types, 0)); bail!(AlgebrizerError::InvalidArgumentType(
predicate.operator.clone(),
supported_types,
0
));
} }
// These arguments must be variables or instant/numeric constants. // These arguments must be variables or instant/numeric constants.
@ -167,15 +178,13 @@ impl ConjoiningClauses {
impl Inequality { impl Inequality {
fn to_constraint(&self, left: QueryValue, right: QueryValue) -> ColumnConstraint { fn to_constraint(&self, left: QueryValue, right: QueryValue) -> ColumnConstraint {
match *self { match *self {
Inequality::TxAfter | Inequality::TxAfter | Inequality::TxBefore => {
Inequality::TxBefore => {
// TODO: both ends of the range must be inside the tx partition! // TODO: both ends of the range must be inside the tx partition!
// If we know the partition map -- and at this point we do, it's just // If we know the partition map -- and at this point we do, it's just
// not passed to this function -- then we can generate two constraints, // not passed to this function -- then we can generate two constraints,
// or clamp a fixed value. // or clamp a fixed value.
}, }
_ => { _ => {}
},
} }
ColumnConstraint::Inequality { ColumnConstraint::Inequality {
@ -190,36 +199,16 @@ impl Inequality {
mod testing { mod testing {
use super::*; use super::*;
use core_traits::attribute::{ use core_traits::attribute::Unique;
Unique, use core_traits::{Attribute, TypedValue, ValueType};
};
use core_traits::{
Attribute,
TypedValue,
ValueType,
};
use edn::query::{ use edn::query::{
FnArg, FnArg, Keyword, Pattern, PatternNonValuePlace, PatternValuePlace, PlainSymbol, Variable,
Keyword,
Pattern,
PatternNonValuePlace,
PatternValuePlace,
PlainSymbol,
Variable,
}; };
use clauses::{ use clauses::{add_attribute, associate_ident, ident};
add_attribute,
associate_ident,
ident,
};
use types::{ use types::{ColumnConstraint, EmptyBecause, QueryValue};
ColumnConstraint,
EmptyBecause,
QueryValue,
};
#[test] #[test]
/// Apply two patterns: a pattern and a numeric predicate. /// Apply two patterns: a pattern and a numeric predicate.
@ -230,30 +219,45 @@ mod testing {
let mut schema = Schema::default(); let mut schema = Schema::default();
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99);
add_attribute(&mut schema, 99, Attribute { add_attribute(
value_type: ValueType::Long, &mut schema,
..Default::default() 99,
}); Attribute {
value_type: ValueType::Long,
..Default::default()
},
);
let x = Variable::from_valid_name("?x"); let x = Variable::from_valid_name("?x");
let y = Variable::from_valid_name("?y"); let y = Variable::from_valid_name("?y");
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
cc.apply_parsed_pattern(known, Pattern { cc.apply_parsed_pattern(
source: None, known,
entity: PatternNonValuePlace::Variable(x.clone()), Pattern {
attribute: PatternNonValuePlace::Placeholder, source: None,
value: PatternValuePlace::Variable(y.clone()), entity: PatternNonValuePlace::Variable(x.clone()),
tx: PatternNonValuePlace::Placeholder, attribute: PatternNonValuePlace::Placeholder,
}); value: PatternValuePlace::Variable(y.clone()),
tx: PatternNonValuePlace::Placeholder,
},
);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
let op = PlainSymbol::plain("<"); let op = PlainSymbol::plain("<");
let comp = Inequality::from_datalog_operator(op.name()).unwrap(); let comp = Inequality::from_datalog_operator(op.name()).unwrap();
assert!(cc.apply_inequality(known, comp, Predicate { assert!(cc
operator: op, .apply_inequality(
args: vec![ known,
FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(10), comp,
]}).is_ok()); Predicate {
operator: op,
args: vec![
FnArg::Variable(Variable::from_valid_name("?y")),
FnArg::EntidOrInteger(10),
]
}
)
.is_ok());
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
@ -263,17 +267,21 @@ mod testing {
// After processing those two clauses, we know that ?y must be numeric, but not exactly // After processing those two clauses, we know that ?y must be numeric, but not exactly
// which type it must be. // which type it must be.
assert_eq!(None, cc.known_type(&y)); // Not just one. assert_eq!(None, cc.known_type(&y)); // Not just one.
let expected = ValueTypeSet::of_numeric_types(); let expected = ValueTypeSet::of_numeric_types();
assert_eq!(Some(&expected), cc.known_types.get(&y)); assert_eq!(Some(&expected), cc.known_types.get(&y));
let clauses = cc.wheres; let clauses = cc.wheres;
assert_eq!(clauses.len(), 1); assert_eq!(clauses.len(), 1);
assert_eq!(clauses.0[0], ColumnConstraint::Inequality { assert_eq!(
operator: Inequality::LessThan, clauses.0[0],
left: QueryValue::Column(cc.column_bindings.get(&y).unwrap()[0].clone()), ColumnConstraint::Inequality {
right: QueryValue::TypedValue(TypedValue::Long(10)), operator: Inequality::LessThan,
}.into()); left: QueryValue::Column(cc.column_bindings.get(&y).unwrap()[0].clone()),
right: QueryValue::TypedValue(TypedValue::Long(10)),
}
.into()
);
} }
#[test] #[test]
@ -286,54 +294,78 @@ mod testing {
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99);
associate_ident(&mut schema, Keyword::namespaced("foo", "roz"), 98); associate_ident(&mut schema, Keyword::namespaced("foo", "roz"), 98);
add_attribute(&mut schema, 99, Attribute { add_attribute(
value_type: ValueType::Long, &mut schema,
..Default::default() 99,
}); Attribute {
add_attribute(&mut schema, 98, Attribute { value_type: ValueType::Long,
value_type: ValueType::String, ..Default::default()
unique: Some(Unique::Identity), },
..Default::default() );
}); add_attribute(
&mut schema,
98,
Attribute {
value_type: ValueType::String,
unique: Some(Unique::Identity),
..Default::default()
},
);
let x = Variable::from_valid_name("?x"); let x = Variable::from_valid_name("?x");
let y = Variable::from_valid_name("?y"); let y = Variable::from_valid_name("?y");
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
cc.apply_parsed_pattern(known, Pattern { cc.apply_parsed_pattern(
source: None, known,
entity: PatternNonValuePlace::Variable(x.clone()), Pattern {
attribute: PatternNonValuePlace::Placeholder, source: None,
value: PatternValuePlace::Variable(y.clone()), entity: PatternNonValuePlace::Variable(x.clone()),
tx: PatternNonValuePlace::Placeholder, attribute: PatternNonValuePlace::Placeholder,
}); value: PatternValuePlace::Variable(y.clone()),
tx: PatternNonValuePlace::Placeholder,
},
);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
let op = PlainSymbol::plain(">="); let op = PlainSymbol::plain(">=");
let comp = Inequality::from_datalog_operator(op.name()).unwrap(); let comp = Inequality::from_datalog_operator(op.name()).unwrap();
assert!(cc.apply_inequality(known, comp, Predicate { assert!(cc
operator: op, .apply_inequality(
args: vec![ known,
FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(10), comp,
]}).is_ok()); Predicate {
operator: op,
args: vec![
FnArg::Variable(Variable::from_valid_name("?y")),
FnArg::EntidOrInteger(10),
]
}
)
.is_ok());
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
cc.apply_parsed_pattern(known, Pattern { cc.apply_parsed_pattern(
source: None, known,
entity: PatternNonValuePlace::Variable(x.clone()), Pattern {
attribute: ident("foo", "roz"), source: None,
value: PatternValuePlace::Variable(y.clone()), entity: PatternNonValuePlace::Variable(x.clone()),
tx: PatternNonValuePlace::Placeholder, attribute: ident("foo", "roz"),
}); value: PatternValuePlace::Variable(y.clone()),
tx: PatternNonValuePlace::Placeholder,
},
);
// Finally, expand column bindings to get the overlaps for ?x. // Finally, expand column bindings to get the overlaps for ?x.
cc.expand_column_bindings(); cc.expand_column_bindings();
assert!(cc.is_known_empty()); assert!(cc.is_known_empty());
assert_eq!(cc.empty_because.unwrap(), assert_eq!(
EmptyBecause::TypeMismatch { cc.empty_because.unwrap(),
var: y.clone(), EmptyBecause::TypeMismatch {
existing: ValueTypeSet::of_numeric_types(), var: y.clone(),
desired: ValueTypeSet::of_one(ValueType::String), existing: ValueTypeSet::of_numeric_types(),
}); desired: ValueTypeSet::of_one(ValueType::String),
}
);
} }
} }

View file

@ -8,33 +8,17 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use core_traits::{ use core_traits::{TypedValue, ValueType};
ValueType,
TypedValue,
};
use mentat_core::{ use mentat_core::{HasSchema, Schema};
HasSchema,
Schema,
};
use edn::query::{ use edn::query::{FnArg, NonIntegerConstant, PlainSymbol};
FnArg,
NonIntegerConstant,
PlainSymbol,
};
use clauses::ConjoiningClauses; use clauses::ConjoiningClauses;
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, Result};
AlgebrizerError,
Result,
};
use types::{ use types::{EmptyBecause, QueryValue};
EmptyBecause,
QueryValue,
};
/// Argument resolution. /// Argument resolution.
impl ConjoiningClauses { impl ConjoiningClauses {
@ -43,7 +27,12 @@ impl ConjoiningClauses {
/// Additionally, do two things: /// Additionally, do two things:
/// - Mark the pattern as known-empty if any argument is known non-numeric. /// - Mark the pattern as known-empty if any argument is known non-numeric.
/// - Mark any variables encountered as numeric. /// - Mark any variables encountered as numeric.
pub(crate) fn resolve_numeric_argument(&mut self, function: &PlainSymbol, position: usize, arg: FnArg) -> Result<QueryValue> { pub(crate) fn resolve_numeric_argument(
&mut self,
function: &PlainSymbol,
position: usize,
arg: FnArg,
) -> Result<QueryValue> {
use self::FnArg::*; use self::FnArg::*;
match arg { match arg {
FnArg::Variable(var) => { FnArg::Variable(var) => {
@ -80,45 +69,62 @@ impl ConjoiningClauses {
} }
/// Just like `resolve_numeric_argument`, but for `ValueType::Instant`. /// Just like `resolve_numeric_argument`, but for `ValueType::Instant`.
pub(crate) fn resolve_instant_argument(&mut self, function: &PlainSymbol, position: usize, arg: FnArg) -> Result<QueryValue> { pub(crate) fn resolve_instant_argument(
&mut self,
function: &PlainSymbol,
position: usize,
arg: FnArg,
) -> Result<QueryValue> {
use self::FnArg::*; use self::FnArg::*;
match arg { match arg {
FnArg::Variable(var) => { FnArg::Variable(var) => match self.bound_value(&var) {
match self.bound_value(&var) { Some(TypedValue::Instant(v)) => Ok(QueryValue::TypedValue(TypedValue::Instant(v))),
Some(TypedValue::Instant(v)) => Ok(QueryValue::TypedValue(TypedValue::Instant(v))), Some(v) => bail!(AlgebrizerError::InputTypeDisagreement(
Some(v) => bail!(AlgebrizerError::InputTypeDisagreement(var.name().clone(), ValueType::Instant, v.value_type())), var.name().clone(),
None => { ValueType::Instant,
self.constrain_var_to_type(var.clone(), ValueType::Instant); v.value_type()
self.column_bindings )),
.get(&var) None => {
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone()))) self.constrain_var_to_type(var.clone(), ValueType::Instant);
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into()) self.column_bindings
}, .get(&var)
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone())))
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into())
} }
}, },
Constant(NonIntegerConstant::Instant(v)) => { Constant(NonIntegerConstant::Instant(v)) => {
Ok(QueryValue::TypedValue(TypedValue::Instant(v))) Ok(QueryValue::TypedValue(TypedValue::Instant(v)))
}, }
// TODO: should we allow integers if they seem to be timestamps? It's ambiguous… // TODO: should we allow integers if they seem to be timestamps? It's ambiguous…
EntidOrInteger(_) | EntidOrInteger(_)
IdentOrKeyword(_) | | IdentOrKeyword(_)
SrcVar(_) | | SrcVar(_)
Constant(NonIntegerConstant::Boolean(_)) | | Constant(NonIntegerConstant::Boolean(_))
Constant(NonIntegerConstant::Float(_)) | | Constant(NonIntegerConstant::Float(_))
Constant(NonIntegerConstant::Text(_)) | | Constant(NonIntegerConstant::Text(_))
Constant(NonIntegerConstant::Uuid(_)) | | Constant(NonIntegerConstant::Uuid(_))
Constant(NonIntegerConstant::BigInteger(_)) | | Constant(NonIntegerConstant::BigInteger(_))
Vector(_) => { | Vector(_) => {
self.mark_known_empty(EmptyBecause::NonInstantArgument); self.mark_known_empty(EmptyBecause::NonInstantArgument);
bail!(AlgebrizerError::InvalidArgumentType(function.clone(), ValueType::Instant.into(), position)) bail!(AlgebrizerError::InvalidArgumentType(
}, function.clone(),
ValueType::Instant.into(),
position
))
}
} }
} }
/// Take a function argument and turn it into a `QueryValue` suitable for use in a concrete /// Take a function argument and turn it into a `QueryValue` suitable for use in a concrete
/// constraint. /// constraint.
pub(crate) fn resolve_ref_argument(&mut self, schema: &Schema, function: &PlainSymbol, position: usize, arg: FnArg) -> Result<QueryValue> { pub(crate) fn resolve_ref_argument(
&mut self,
schema: &Schema,
function: &PlainSymbol,
position: usize,
arg: FnArg,
) -> Result<QueryValue> {
use self::FnArg::*; use self::FnArg::*;
match arg { match arg {
FnArg::Variable(var) => { FnArg::Variable(var) => {
@ -132,31 +138,39 @@ impl ConjoiningClauses {
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone()))) .and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone())))
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into()) .ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into())
} }
}, }
EntidOrInteger(i) => Ok(QueryValue::TypedValue(TypedValue::Ref(i))), EntidOrInteger(i) => Ok(QueryValue::TypedValue(TypedValue::Ref(i))),
IdentOrKeyword(i) => { IdentOrKeyword(i) => schema
schema.get_entid(&i) .get_entid(&i)
.map(|known_entid| QueryValue::Entid(known_entid.into())) .map(|known_entid| QueryValue::Entid(known_entid.into()))
.ok_or_else(|| AlgebrizerError::UnrecognizedIdent(i.to_string()).into()) .ok_or_else(|| AlgebrizerError::UnrecognizedIdent(i.to_string()).into()),
}, Constant(NonIntegerConstant::Boolean(_))
Constant(NonIntegerConstant::Boolean(_)) | | Constant(NonIntegerConstant::Float(_))
Constant(NonIntegerConstant::Float(_)) | | Constant(NonIntegerConstant::Text(_))
Constant(NonIntegerConstant::Text(_)) | | Constant(NonIntegerConstant::Uuid(_))
Constant(NonIntegerConstant::Uuid(_)) | | Constant(NonIntegerConstant::Instant(_))
Constant(NonIntegerConstant::Instant(_)) | | Constant(NonIntegerConstant::BigInteger(_))
Constant(NonIntegerConstant::BigInteger(_)) | | SrcVar(_)
SrcVar(_) | | Vector(_) => {
Vector(_) => {
self.mark_known_empty(EmptyBecause::NonEntityArgument); self.mark_known_empty(EmptyBecause::NonEntityArgument);
bail!(AlgebrizerError::InvalidArgumentType(function.clone(), ValueType::Ref.into(), position)) bail!(AlgebrizerError::InvalidArgumentType(
}, function.clone(),
ValueType::Ref.into(),
position
))
}
} }
} }
/// Take a transaction ID function argument and turn it into a `QueryValue` suitable for use in /// Take a transaction ID function argument and turn it into a `QueryValue` suitable for use in
/// a concrete constraint. /// a concrete constraint.
pub(crate) fn resolve_tx_argument(&mut self, schema: &Schema, function: &PlainSymbol, position: usize, arg: FnArg) -> Result<QueryValue> { pub(crate) fn resolve_tx_argument(
&mut self,
schema: &Schema,
function: &PlainSymbol,
position: usize,
arg: FnArg,
) -> Result<QueryValue> {
// Under the hood there's nothing special about a transaction ID -- it's just another ref. // Under the hood there's nothing special about a transaction ID -- it's just another ref.
// In the future, we might handle instants specially. // In the future, we might handle instants specially.
self.resolve_ref_argument(schema, function, position, arg) self.resolve_ref_argument(schema, function, position, arg)
@ -168,27 +182,34 @@ impl ConjoiningClauses {
fn resolve_argument(&self, arg: FnArg) -> Result<QueryValue> { fn resolve_argument(&self, arg: FnArg) -> Result<QueryValue> {
use self::FnArg::*; use self::FnArg::*;
match arg { match arg {
FnArg::Variable(var) => { FnArg::Variable(var) => match self.bound_value(&var) {
match self.bound_value(&var) { Some(v) => Ok(QueryValue::TypedValue(v)),
Some(v) => Ok(QueryValue::TypedValue(v)), None => self
None => { .column_bindings
self.column_bindings .get(&var)
.get(&var) .and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone())))
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone()))) .ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into()),
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into())
},
}
}, },
EntidOrInteger(i) => Ok(QueryValue::PrimitiveLong(i)), EntidOrInteger(i) => Ok(QueryValue::PrimitiveLong(i)),
IdentOrKeyword(_) => unimplemented!(), // TODO IdentOrKeyword(_) => unimplemented!(), // TODO
Constant(NonIntegerConstant::Boolean(val)) => Ok(QueryValue::TypedValue(TypedValue::Boolean(val))), Constant(NonIntegerConstant::Boolean(val)) => {
Constant(NonIntegerConstant::Float(f)) => Ok(QueryValue::TypedValue(TypedValue::Double(f))), Ok(QueryValue::TypedValue(TypedValue::Boolean(val)))
Constant(NonIntegerConstant::Text(s)) => Ok(QueryValue::TypedValue(TypedValue::typed_string(s.as_str()))), }
Constant(NonIntegerConstant::Uuid(u)) => Ok(QueryValue::TypedValue(TypedValue::Uuid(u))), Constant(NonIntegerConstant::Float(f)) => {
Constant(NonIntegerConstant::Instant(u)) => Ok(QueryValue::TypedValue(TypedValue::Instant(u))), Ok(QueryValue::TypedValue(TypedValue::Double(f)))
}
Constant(NonIntegerConstant::Text(s)) => {
Ok(QueryValue::TypedValue(TypedValue::typed_string(s.as_str())))
}
Constant(NonIntegerConstant::Uuid(u)) => {
Ok(QueryValue::TypedValue(TypedValue::Uuid(u)))
}
Constant(NonIntegerConstant::Instant(u)) => {
Ok(QueryValue::TypedValue(TypedValue::Instant(u)))
}
Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(), Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(),
SrcVar(_) => unimplemented!(), SrcVar(_) => unimplemented!(),
Vector(_) => unimplemented!(), // TODO Vector(_) => unimplemented!(), // TODO
} }
} }
} }

View file

@ -8,36 +8,16 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use core_traits::{ use core_traits::ValueType;
ValueType,
};
use edn::query::{ use edn::query::{Binding, FnArg, SrcVar, VariableOrPlaceholder, WhereFn};
Binding,
FnArg,
SrcVar,
VariableOrPlaceholder,
WhereFn,
};
use clauses::{ use clauses::ConjoiningClauses;
ConjoiningClauses,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, BindingError, Result};
AlgebrizerError,
BindingError,
Result,
};
use types::{ use types::{
Column, Column, ColumnConstraint, DatomsTable, Inequality, QualifiedAlias, QueryValue, SourceAlias,
ColumnConstraint,
DatomsTable,
Inequality,
QualifiedAlias,
QueryValue,
SourceAlias,
TransactionsColumn, TransactionsColumn,
}; };
@ -60,17 +40,27 @@ impl ConjoiningClauses {
// transactions that impact one of the given attributes. // transactions that impact one of the given attributes.
pub(crate) fn apply_tx_ids(&mut self, known: Known, where_fn: WhereFn) -> Result<()> { pub(crate) fn apply_tx_ids(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
if where_fn.args.len() != 3 { if where_fn.args.len() != 3 {
bail!(AlgebrizerError::InvalidNumberOfArguments(where_fn.operator.clone(), where_fn.args.len(), 3)); bail!(AlgebrizerError::InvalidNumberOfArguments(
where_fn.operator.clone(),
where_fn.args.len(),
3
));
} }
if where_fn.binding.is_empty() { if where_fn.binding.is_empty() {
// The binding must introduce at least one bound variable. // The binding must introduce at least one bound variable.
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable)); bail!(AlgebrizerError::InvalidBinding(
where_fn.operator.clone(),
BindingError::NoBoundVariable
));
} }
if !where_fn.binding.is_valid() { if !where_fn.binding.is_valid() {
// The binding must not duplicate bound variables. // The binding must not duplicate bound variables.
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); bail!(AlgebrizerError::InvalidBinding(
where_fn.operator.clone(),
BindingError::RepeatedBoundVariable
));
} }
// We should have exactly one binding. Destructure it now. // We should have exactly one binding. Destructure it now.
@ -78,38 +68,49 @@ impl ConjoiningClauses {
Binding::BindRel(bindings) => { Binding::BindRel(bindings) => {
let bindings_count = bindings.len(); let bindings_count = bindings.len();
if bindings_count != 1 { if bindings_count != 1 {
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), bail!(AlgebrizerError::InvalidBinding(
BindingError::InvalidNumberOfBindings { where_fn.operator.clone(),
number: bindings_count, BindingError::InvalidNumberOfBindings {
expected: 1, number: bindings_count,
})); expected: 1,
}
));
} }
match bindings.into_iter().next().unwrap() { match bindings.into_iter().next().unwrap() {
VariableOrPlaceholder::Placeholder => unreachable!("binding.is_empty()!"), VariableOrPlaceholder::Placeholder => unreachable!("binding.is_empty()!"),
VariableOrPlaceholder::Variable(v) => v, VariableOrPlaceholder::Variable(v) => v,
} }
}, }
Binding::BindColl(v) => v, Binding::BindColl(v) => v,
Binding::BindScalar(_) | Binding::BindScalar(_) | Binding::BindTuple(_) => {
Binding::BindTuple(_) => { bail!(AlgebrizerError::InvalidBinding(
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRelOrBindColl)) where_fn.operator.clone(),
}, BindingError::ExpectedBindRelOrBindColl
))
}
}; };
let mut args = where_fn.args.into_iter(); let mut args = where_fn.args.into_iter();
// TODO: process source variables. // TODO: process source variables.
match args.next().unwrap() { match args.next().unwrap() {
FnArg::SrcVar(SrcVar::DefaultSrc) => {}, FnArg::SrcVar(SrcVar::DefaultSrc) => {}
_ => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "source variable", 0)), _ => bail!(AlgebrizerError::InvalidArgument(
where_fn.operator.clone(),
"source variable",
0
)),
} }
let tx1 = self.resolve_tx_argument(&known.schema, &where_fn.operator, 1, args.next().unwrap())?; let tx1 =
let tx2 = self.resolve_tx_argument(&known.schema, &where_fn.operator, 2, args.next().unwrap())?; self.resolve_tx_argument(&known.schema, &where_fn.operator, 1, args.next().unwrap())?;
let tx2 =
self.resolve_tx_argument(&known.schema, &where_fn.operator, 2, args.next().unwrap())?;
let transactions = self.next_alias_for_table(DatomsTable::Transactions); let transactions = self.next_alias_for_table(DatomsTable::Transactions);
self.from.push(SourceAlias(DatomsTable::Transactions, transactions.clone())); self.from
.push(SourceAlias(DatomsTable::Transactions, transactions.clone()));
// Bound variable must be a ref. // Bound variable must be a ref.
self.constrain_var_to_type(tx_var.clone(), ValueType::Ref); self.constrain_var_to_type(tx_var.clone(), ValueType::Ref);
@ -117,18 +118,29 @@ impl ConjoiningClauses {
return Ok(()); return Ok(());
} }
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Tx, tx_var.clone()); self.bind_column_to_var(
known.schema,
transactions.clone(),
TransactionsColumn::Tx,
tx_var.clone(),
);
let after_constraint = ColumnConstraint::Inequality { let after_constraint = ColumnConstraint::Inequality {
operator: Inequality::LessThanOrEquals, operator: Inequality::LessThanOrEquals,
left: tx1, left: tx1,
right: QueryValue::Column(QualifiedAlias(transactions.clone(), Column::Transactions(TransactionsColumn::Tx))), right: QueryValue::Column(QualifiedAlias(
transactions.clone(),
Column::Transactions(TransactionsColumn::Tx),
)),
}; };
self.wheres.add_intersection(after_constraint); self.wheres.add_intersection(after_constraint);
let before_constraint = ColumnConstraint::Inequality { let before_constraint = ColumnConstraint::Inequality {
operator: Inequality::LessThan, operator: Inequality::LessThan,
left: QueryValue::Column(QualifiedAlias(transactions.clone(), Column::Transactions(TransactionsColumn::Tx))), left: QueryValue::Column(QualifiedAlias(
transactions.clone(),
Column::Transactions(TransactionsColumn::Tx),
)),
right: tx2, right: tx2,
}; };
self.wheres.add_intersection(before_constraint); self.wheres.add_intersection(before_constraint);
@ -138,17 +150,27 @@ impl ConjoiningClauses {
pub(crate) fn apply_tx_data(&mut self, known: Known, where_fn: WhereFn) -> Result<()> { pub(crate) fn apply_tx_data(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
if where_fn.args.len() != 2 { if where_fn.args.len() != 2 {
bail!(AlgebrizerError::InvalidNumberOfArguments(where_fn.operator.clone(), where_fn.args.len(), 2)); bail!(AlgebrizerError::InvalidNumberOfArguments(
where_fn.operator.clone(),
where_fn.args.len(),
2
));
} }
if where_fn.binding.is_empty() { if where_fn.binding.is_empty() {
// The binding must introduce at least one bound variable. // The binding must introduce at least one bound variable.
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable)); bail!(AlgebrizerError::InvalidBinding(
where_fn.operator.clone(),
BindingError::NoBoundVariable
));
} }
if !where_fn.binding.is_valid() { if !where_fn.binding.is_valid() {
// The binding must not duplicate bound variables. // The binding must not duplicate bound variables.
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); bail!(AlgebrizerError::InvalidBinding(
where_fn.operator.clone(),
BindingError::RepeatedBoundVariable
));
} }
// We should have at most five bindings. Destructure them now. // We should have at most five bindings. Destructure them now.
@ -156,42 +178,67 @@ impl ConjoiningClauses {
Binding::BindRel(bindings) => { Binding::BindRel(bindings) => {
let bindings_count = bindings.len(); let bindings_count = bindings.len();
if bindings_count < 1 || bindings_count > 5 { if bindings_count < 1 || bindings_count > 5 {
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), bail!(AlgebrizerError::InvalidBinding(
BindingError::InvalidNumberOfBindings { where_fn.operator.clone(),
number: bindings.len(), BindingError::InvalidNumberOfBindings {
expected: 5, number: bindings.len(),
})); expected: 5,
}
));
} }
bindings bindings
}, }
Binding::BindScalar(_) | Binding::BindScalar(_) | Binding::BindTuple(_) | Binding::BindColl(_) => {
Binding::BindTuple(_) | bail!(AlgebrizerError::InvalidBinding(
Binding::BindColl(_) => bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRel)), where_fn.operator.clone(),
BindingError::ExpectedBindRel
))
}
}; };
let mut bindings = bindings.into_iter(); let mut bindings = bindings.into_iter();
let b_e = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); let b_e = bindings
let b_a = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); .next()
let b_v = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); .unwrap_or(VariableOrPlaceholder::Placeholder);
let b_tx = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); let b_a = bindings
let b_op = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); .next()
.unwrap_or(VariableOrPlaceholder::Placeholder);
let b_v = bindings
.next()
.unwrap_or(VariableOrPlaceholder::Placeholder);
let b_tx = bindings
.next()
.unwrap_or(VariableOrPlaceholder::Placeholder);
let b_op = bindings
.next()
.unwrap_or(VariableOrPlaceholder::Placeholder);
let mut args = where_fn.args.into_iter(); let mut args = where_fn.args.into_iter();
// TODO: process source variables. // TODO: process source variables.
match args.next().unwrap() { match args.next().unwrap() {
FnArg::SrcVar(SrcVar::DefaultSrc) => {}, FnArg::SrcVar(SrcVar::DefaultSrc) => {}
_ => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "source variable", 0)), _ => bail!(AlgebrizerError::InvalidArgument(
where_fn.operator.clone(),
"source variable",
0
)),
} }
let tx = self.resolve_tx_argument(&known.schema, &where_fn.operator, 1, args.next().unwrap())?; let tx =
self.resolve_tx_argument(&known.schema, &where_fn.operator, 1, args.next().unwrap())?;
let transactions = self.next_alias_for_table(DatomsTable::Transactions); let transactions = self.next_alias_for_table(DatomsTable::Transactions);
self.from.push(SourceAlias(DatomsTable::Transactions, transactions.clone())); self.from
.push(SourceAlias(DatomsTable::Transactions, transactions.clone()));
let tx_constraint = ColumnConstraint::Equals( let tx_constraint = ColumnConstraint::Equals(
QualifiedAlias(transactions.clone(), Column::Transactions(TransactionsColumn::Tx)), QualifiedAlias(
tx); transactions.clone(),
Column::Transactions(TransactionsColumn::Tx),
),
tx,
);
self.wheres.add_intersection(tx_constraint); self.wheres.add_intersection(tx_constraint);
if let VariableOrPlaceholder::Variable(ref var) = b_e { if let VariableOrPlaceholder::Variable(ref var) = b_e {
@ -201,7 +248,12 @@ impl ConjoiningClauses {
return Ok(()); return Ok(());
} }
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Entity, var.clone()); self.bind_column_to_var(
known.schema,
transactions.clone(),
TransactionsColumn::Entity,
var.clone(),
);
} }
if let VariableOrPlaceholder::Variable(ref var) = b_a { if let VariableOrPlaceholder::Variable(ref var) = b_a {
@ -211,11 +263,21 @@ impl ConjoiningClauses {
return Ok(()); return Ok(());
} }
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Attribute, var.clone()); self.bind_column_to_var(
known.schema,
transactions.clone(),
TransactionsColumn::Attribute,
var.clone(),
);
} }
if let VariableOrPlaceholder::Variable(ref var) = b_v { if let VariableOrPlaceholder::Variable(ref var) = b_v {
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Value, var.clone()); self.bind_column_to_var(
known.schema,
transactions.clone(),
TransactionsColumn::Value,
var.clone(),
);
} }
if let VariableOrPlaceholder::Variable(ref var) = b_tx { if let VariableOrPlaceholder::Variable(ref var) = b_tx {
@ -227,7 +289,12 @@ impl ConjoiningClauses {
// TODO: this might be a programming error if var is our tx argument. Perhaps we can be // TODO: this might be a programming error if var is our tx argument. Perhaps we can be
// helpful in that case. // helpful in that case.
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Tx, var.clone()); self.bind_column_to_var(
known.schema,
transactions.clone(),
TransactionsColumn::Tx,
var.clone(),
);
} }
if let VariableOrPlaceholder::Variable(ref var) = b_op { if let VariableOrPlaceholder::Variable(ref var) = b_op {
@ -237,7 +304,12 @@ impl ConjoiningClauses {
return Ok(()); return Ok(());
} }
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Added, var.clone()); self.bind_column_to_var(
known.schema,
transactions.clone(),
TransactionsColumn::Added,
var.clone(),
);
} }
Ok(()) Ok(())
@ -248,21 +320,11 @@ impl ConjoiningClauses {
mod testing { mod testing {
use super::*; use super::*;
use core_traits::{ use core_traits::{TypedValue, ValueType};
TypedValue,
ValueType,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::query::{ use edn::query::{Binding, FnArg, PlainSymbol, Variable};
Binding,
FnArg,
PlainSymbol,
Variable,
};
#[test] #[test]
fn test_apply_tx_ids() { fn test_apply_tx_ids() {
@ -272,16 +334,21 @@ mod testing {
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let op = PlainSymbol::plain("tx-ids"); let op = PlainSymbol::plain("tx-ids");
cc.apply_tx_ids(known, WhereFn { cc.apply_tx_ids(
operator: op, known,
args: vec![ WhereFn {
FnArg::SrcVar(SrcVar::DefaultSrc), operator: op,
FnArg::EntidOrInteger(1000), args: vec![
FnArg::EntidOrInteger(2000), FnArg::SrcVar(SrcVar::DefaultSrc),
], FnArg::EntidOrInteger(1000),
binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")), FnArg::EntidOrInteger(2000),
]), ],
}).expect("to be able to apply_tx_ids"); binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable(
Variable::from_valid_name("?tx"),
)]),
},
)
.expect("to be able to apply_tx_ids");
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
@ -292,31 +359,56 @@ mod testing {
let clauses = cc.wheres; let clauses = cc.wheres;
assert_eq!(clauses.len(), 2); assert_eq!(clauses.len(), 2);
assert_eq!(clauses.0[0], assert_eq!(
ColumnConstraint::Inequality { clauses.0[0],
operator: Inequality::LessThanOrEquals, ColumnConstraint::Inequality {
left: QueryValue::TypedValue(TypedValue::Ref(1000)), operator: Inequality::LessThanOrEquals,
right: QueryValue::Column(QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx))), left: QueryValue::TypedValue(TypedValue::Ref(1000)),
}.into()); right: QueryValue::Column(QualifiedAlias(
"transactions00".to_string(),
Column::Transactions(TransactionsColumn::Tx)
)),
}
.into()
);
assert_eq!(clauses.0[1], assert_eq!(
ColumnConstraint::Inequality { clauses.0[1],
operator: Inequality::LessThan, ColumnConstraint::Inequality {
left: QueryValue::Column(QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx))), operator: Inequality::LessThan,
right: QueryValue::TypedValue(TypedValue::Ref(2000)), left: QueryValue::Column(QualifiedAlias(
}.into()); "transactions00".to_string(),
Column::Transactions(TransactionsColumn::Tx)
)),
right: QueryValue::TypedValue(TypedValue::Ref(2000)),
}
.into()
);
let bindings = cc.column_bindings; let bindings = cc.column_bindings;
assert_eq!(bindings.len(), 1); assert_eq!(bindings.len(), 1);
assert_eq!(bindings.get(&Variable::from_valid_name("?tx")).expect("column binding for ?tx").clone(), assert_eq!(
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx))]); bindings
.get(&Variable::from_valid_name("?tx"))
.expect("column binding for ?tx")
.clone(),
vec![QualifiedAlias(
"transactions00".to_string(),
Column::Transactions(TransactionsColumn::Tx)
)]
);
let known_types = cc.known_types; let known_types = cc.known_types;
assert_eq!(known_types.len(), 1); assert_eq!(known_types.len(), 1);
assert_eq!(known_types.get(&Variable::from_valid_name("?tx")).expect("known types for ?tx").clone(), assert_eq!(
vec![ValueType::Ref].into_iter().collect()); known_types
.get(&Variable::from_valid_name("?tx"))
.expect("known types for ?tx")
.clone(),
vec![ValueType::Ref].into_iter().collect()
);
} }
#[test] #[test]
@ -327,20 +419,24 @@ mod testing {
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let op = PlainSymbol::plain("tx-data"); let op = PlainSymbol::plain("tx-data");
cc.apply_tx_data(known, WhereFn { cc.apply_tx_data(
operator: op, known,
args: vec![ WhereFn {
FnArg::SrcVar(SrcVar::DefaultSrc), operator: op,
FnArg::EntidOrInteger(1000), args: vec![
], FnArg::SrcVar(SrcVar::DefaultSrc),
binding: Binding::BindRel(vec![ FnArg::EntidOrInteger(1000),
VariableOrPlaceholder::Variable(Variable::from_valid_name("?e")), ],
VariableOrPlaceholder::Variable(Variable::from_valid_name("?a")), binding: Binding::BindRel(vec![
VariableOrPlaceholder::Variable(Variable::from_valid_name("?v")), VariableOrPlaceholder::Variable(Variable::from_valid_name("?e")),
VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")), VariableOrPlaceholder::Variable(Variable::from_valid_name("?a")),
VariableOrPlaceholder::Variable(Variable::from_valid_name("?added")), VariableOrPlaceholder::Variable(Variable::from_valid_name("?v")),
]), VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")),
}).expect("to be able to apply_tx_data"); VariableOrPlaceholder::Variable(Variable::from_valid_name("?added")),
]),
},
)
.expect("to be able to apply_tx_data");
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
@ -351,47 +447,123 @@ mod testing {
let clauses = cc.wheres; let clauses = cc.wheres;
assert_eq!(clauses.len(), 1); assert_eq!(clauses.len(), 1);
assert_eq!(clauses.0[0], assert_eq!(
ColumnConstraint::Equals(QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx)), clauses.0[0],
QueryValue::TypedValue(TypedValue::Ref(1000))).into()); ColumnConstraint::Equals(
QualifiedAlias(
"transactions00".to_string(),
Column::Transactions(TransactionsColumn::Tx)
),
QueryValue::TypedValue(TypedValue::Ref(1000))
)
.into()
);
let bindings = cc.column_bindings; let bindings = cc.column_bindings;
assert_eq!(bindings.len(), 5); assert_eq!(bindings.len(), 5);
assert_eq!(bindings.get(&Variable::from_valid_name("?e")).expect("column binding for ?e").clone(), assert_eq!(
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Entity))]); bindings
.get(&Variable::from_valid_name("?e"))
.expect("column binding for ?e")
.clone(),
vec![QualifiedAlias(
"transactions00".to_string(),
Column::Transactions(TransactionsColumn::Entity)
)]
);
assert_eq!(bindings.get(&Variable::from_valid_name("?a")).expect("column binding for ?a").clone(), assert_eq!(
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Attribute))]); bindings
.get(&Variable::from_valid_name("?a"))
.expect("column binding for ?a")
.clone(),
vec![QualifiedAlias(
"transactions00".to_string(),
Column::Transactions(TransactionsColumn::Attribute)
)]
);
assert_eq!(bindings.get(&Variable::from_valid_name("?v")).expect("column binding for ?v").clone(), assert_eq!(
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Value))]); bindings
.get(&Variable::from_valid_name("?v"))
.expect("column binding for ?v")
.clone(),
vec![QualifiedAlias(
"transactions00".to_string(),
Column::Transactions(TransactionsColumn::Value)
)]
);
assert_eq!(bindings.get(&Variable::from_valid_name("?tx")).expect("column binding for ?tx").clone(), assert_eq!(
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx))]); bindings
.get(&Variable::from_valid_name("?tx"))
.expect("column binding for ?tx")
.clone(),
vec![QualifiedAlias(
"transactions00".to_string(),
Column::Transactions(TransactionsColumn::Tx)
)]
);
assert_eq!(bindings.get(&Variable::from_valid_name("?added")).expect("column binding for ?added").clone(), assert_eq!(
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Added))]); bindings
.get(&Variable::from_valid_name("?added"))
.expect("column binding for ?added")
.clone(),
vec![QualifiedAlias(
"transactions00".to_string(),
Column::Transactions(TransactionsColumn::Added)
)]
);
let known_types = cc.known_types; let known_types = cc.known_types;
assert_eq!(known_types.len(), 4); assert_eq!(known_types.len(), 4);
assert_eq!(known_types.get(&Variable::from_valid_name("?e")).expect("known types for ?e").clone(), assert_eq!(
vec![ValueType::Ref].into_iter().collect()); known_types
.get(&Variable::from_valid_name("?e"))
.expect("known types for ?e")
.clone(),
vec![ValueType::Ref].into_iter().collect()
);
assert_eq!(known_types.get(&Variable::from_valid_name("?a")).expect("known types for ?a").clone(), assert_eq!(
vec![ValueType::Ref].into_iter().collect()); known_types
.get(&Variable::from_valid_name("?a"))
.expect("known types for ?a")
.clone(),
vec![ValueType::Ref].into_iter().collect()
);
assert_eq!(known_types.get(&Variable::from_valid_name("?tx")).expect("known types for ?tx").clone(), assert_eq!(
vec![ValueType::Ref].into_iter().collect()); known_types
.get(&Variable::from_valid_name("?tx"))
.expect("known types for ?tx")
.clone(),
vec![ValueType::Ref].into_iter().collect()
);
assert_eq!(known_types.get(&Variable::from_valid_name("?added")).expect("known types for ?added").clone(), assert_eq!(
vec![ValueType::Boolean].into_iter().collect()); known_types
.get(&Variable::from_valid_name("?added"))
.expect("known types for ?added")
.clone(),
vec![ValueType::Boolean].into_iter().collect()
);
let extracted_types = cc.extracted_types; let extracted_types = cc.extracted_types;
assert_eq!(extracted_types.len(), 1); assert_eq!(extracted_types.len(), 1);
assert_eq!(extracted_types.get(&Variable::from_valid_name("?v")).expect("extracted types for ?v").clone(), assert_eq!(
QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::ValueTypeTag))); extracted_types
.get(&Variable::from_valid_name("?v"))
.expect("extracted types for ?v")
.clone(),
QualifiedAlias(
"transactions00".to_string(),
Column::Transactions(TransactionsColumn::ValueTypeTag)
)
);
} }
} }

View file

@ -8,18 +8,11 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use edn::query::{ use edn::query::WhereFn;
WhereFn,
};
use clauses::{ use clauses::ConjoiningClauses;
ConjoiningClauses,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, Result};
AlgebrizerError,
Result,
};
use Known; use Known;

View file

@ -20,49 +20,23 @@ use std::collections::BTreeSet;
use std::ops::Sub; use std::ops::Sub;
use std::rc::Rc; use std::rc::Rc;
mod clauses;
mod types; mod types;
mod validate; mod validate;
mod clauses;
use core_traits::{ use core_traits::{Entid, TypedValue, ValueType};
Entid,
TypedValue,
ValueType,
};
use mentat_core::{ use mentat_core::{parse_query, CachedAttributes, Schema};
CachedAttributes,
Schema,
parse_query,
};
use mentat_core::counter::RcCounter; use mentat_core::counter::RcCounter;
use edn::query::{ use edn::query::{Element, FindSpec, Limit, Order, ParsedQuery, SrcVar, Variable, WhereClause};
Element,
FindSpec,
Limit,
Order,
ParsedQuery,
SrcVar,
Variable,
WhereClause,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, Result};
AlgebrizerError,
Result,
};
pub use clauses::{ pub use clauses::{QueryInputs, VariableBindings};
QueryInputs,
VariableBindings,
};
pub use types::{ pub use types::{EmptyBecause, FindQuery};
EmptyBecause,
FindQuery,
};
/// A convenience wrapper around things known in memory: the schema and caches. /// A convenience wrapper around things known in memory: the schema and caches.
/// We use a trait object here to avoid making dozens of functions generic over the type /// We use a trait object here to avoid making dozens of functions generic over the type
@ -71,7 +45,7 @@ pub use types::{
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Known<'s, 'c> { pub struct Known<'s, 'c> {
pub schema: &'s Schema, pub schema: &'s Schema,
pub cache: Option<&'c CachedAttributes>, pub cache: Option<&'c dyn CachedAttributes>,
} }
impl<'s, 'c> Known<'s, 'c> { impl<'s, 'c> Known<'s, 'c> {
@ -82,7 +56,7 @@ impl<'s, 'c> Known<'s, 'c> {
} }
} }
pub fn new(s: &'s Schema, c: Option<&'c CachedAttributes>) -> Known<'s, 'c> { pub fn new(s: &'s Schema, c: Option<&'c dyn CachedAttributes>) -> Known<'s, 'c> {
Known { Known {
schema: s, schema: s,
cache: c, cache: c,
@ -93,36 +67,70 @@ impl<'s, 'c> Known<'s, 'c> {
/// This is `CachedAttributes`, but with handy generic parameters. /// This is `CachedAttributes`, but with handy generic parameters.
/// Why not make the trait generic? Because then we can't use it as a trait object in `Known`. /// Why not make the trait generic? Because then we can't use it as a trait object in `Known`.
impl<'s, 'c> Known<'s, 'c> { impl<'s, 'c> Known<'s, 'c> {
pub fn is_attribute_cached_reverse<U>(&self, entid: U) -> bool where U: Into<Entid> { pub fn is_attribute_cached_reverse<U>(&self, entid: U) -> bool
where
U: Into<Entid>,
{
self.cache self.cache
.map(|cache| cache.is_attribute_cached_reverse(entid.into())) .map(|cache| cache.is_attribute_cached_reverse(entid.into()))
.unwrap_or(false) .unwrap_or(false)
} }
pub fn is_attribute_cached_forward<U>(&self, entid: U) -> bool where U: Into<Entid> { pub fn is_attribute_cached_forward<U>(&self, entid: U) -> bool
where
U: Into<Entid>,
{
self.cache self.cache
.map(|cache| cache.is_attribute_cached_forward(entid.into())) .map(|cache| cache.is_attribute_cached_forward(entid.into()))
.unwrap_or(false) .unwrap_or(false)
} }
pub fn get_values_for_entid<U, V>(&self, schema: &Schema, attribute: U, entid: V) -> Option<&Vec<TypedValue>> pub fn get_values_for_entid<U, V>(
where U: Into<Entid>, V: Into<Entid> { &self,
self.cache.and_then(|cache| cache.get_values_for_entid(schema, attribute.into(), entid.into())) schema: &Schema,
attribute: U,
entid: V,
) -> Option<&Vec<TypedValue>>
where
U: Into<Entid>,
V: Into<Entid>,
{
self.cache
.and_then(|cache| cache.get_values_for_entid(schema, attribute.into(), entid.into()))
} }
pub fn get_value_for_entid<U, V>(&self, schema: &Schema, attribute: U, entid: V) -> Option<&TypedValue> pub fn get_value_for_entid<U, V>(
where U: Into<Entid>, V: Into<Entid> { &self,
self.cache.and_then(|cache| cache.get_value_for_entid(schema, attribute.into(), entid.into())) schema: &Schema,
attribute: U,
entid: V,
) -> Option<&TypedValue>
where
U: Into<Entid>,
V: Into<Entid>,
{
self.cache
.and_then(|cache| cache.get_value_for_entid(schema, attribute.into(), entid.into()))
} }
pub fn get_entid_for_value<U>(&self, attribute: U, value: &TypedValue) -> Option<Entid> pub fn get_entid_for_value<U>(&self, attribute: U, value: &TypedValue) -> Option<Entid>
where U: Into<Entid> { where
self.cache.and_then(|cache| cache.get_entid_for_value(attribute.into(), value)) U: Into<Entid>,
{
self.cache
.and_then(|cache| cache.get_entid_for_value(attribute.into(), value))
} }
pub fn get_entids_for_value<U>(&self, attribute: U, value: &TypedValue) -> Option<&BTreeSet<Entid>> pub fn get_entids_for_value<U>(
where U: Into<Entid> { &self,
self.cache.and_then(|cache| cache.get_entids_for_value(attribute.into(), value)) attribute: U,
value: &TypedValue,
) -> Option<&BTreeSet<Entid>>
where
U: Into<Entid>,
{
self.cache
.and_then(|cache| cache.get_entids_for_value(attribute.into(), value))
} }
} }
@ -157,38 +165,41 @@ impl AlgebraicQuery {
/// Return true if every variable in the find spec is fully bound to a single value. /// Return true if every variable in the find spec is fully bound to a single value.
pub fn is_fully_bound(&self) -> bool { pub fn is_fully_bound(&self) -> bool {
self.find_spec self.find_spec.columns().all(|e| match e {
.columns() // Pull expressions are never fully bound.
.all(|e| match e { // TODO: but the 'inside' of a pull expression certainly can be.
// Pull expressions are never fully bound. &Element::Pull(_) => false,
// TODO: but the 'inside' of a pull expression certainly can be.
&Element::Pull(_) => false,
&Element::Variable(ref var) | &Element::Variable(ref var) | &Element::Corresponding(ref var) => {
&Element::Corresponding(ref var) => self.cc.is_value_bound(var), self.cc.is_value_bound(var)
}
// For now, we pretend that aggregate functions are never fully bound: // For now, we pretend that aggregate functions are never fully bound:
// we don't statically compute them, even if we know the value of the var. // we don't statically compute them, even if we know the value of the var.
&Element::Aggregate(ref _fn) => false, &Element::Aggregate(ref _fn) => false,
}) })
} }
/// Return true if every variable in the find spec is fully bound to a single value, /// Return true if every variable in the find spec is fully bound to a single value,
/// and evaluating the query doesn't require running SQL. /// and evaluating the query doesn't require running SQL.
pub fn is_fully_unit_bound(&self) -> bool { pub fn is_fully_unit_bound(&self) -> bool {
self.cc.wheres.is_empty() && self.cc.wheres.is_empty() && self.is_fully_bound()
self.is_fully_bound()
} }
/// Return a set of the input variables mentioned in the `:in` clause that have not yet been /// Return a set of the input variables mentioned in the `:in` clause that have not yet been
/// bound. We do this by looking at the CC. /// bound. We do this by looking at the CC.
pub fn unbound_variables(&self) -> BTreeSet<Variable> { pub fn unbound_variables(&self) -> BTreeSet<Variable> {
self.cc.input_variables.sub(&self.cc.value_bound_variable_set()) self.cc
.input_variables
.sub(&self.cc.value_bound_variable_set())
} }
} }
pub fn algebrize_with_counter(known: Known, parsed: FindQuery, counter: usize) -> Result<AlgebraicQuery> { pub fn algebrize_with_counter(
known: Known,
parsed: FindQuery,
counter: usize,
) -> Result<AlgebraicQuery> {
algebrize_with_inputs(known, parsed, counter, QueryInputs::default()) algebrize_with_inputs(known, parsed, counter, QueryInputs::default())
} }
@ -200,12 +211,14 @@ pub fn algebrize(known: Known, parsed: FindQuery) -> Result<AlgebraicQuery> {
/// a vector of `OrderBy` instances, including type comparisons if necessary. This function also /// a vector of `OrderBy` instances, including type comparisons if necessary. This function also
/// returns a set of variables that should be added to the `with` clause to make the ordering /// returns a set of variables that should be added to the `with` clause to make the ordering
/// clauses possible. /// clauses possible.
fn validate_and_simplify_order(cc: &ConjoiningClauses, order: Option<Vec<Order>>) fn validate_and_simplify_order(
-> Result<(Option<Vec<OrderBy>>, BTreeSet<Variable>)> { cc: &ConjoiningClauses,
order: Option<Vec<Order>>,
) -> Result<(Option<Vec<OrderBy>>, BTreeSet<Variable>)> {
match order { match order {
None => Ok((None, BTreeSet::default())), None => Ok((None, BTreeSet::default())),
Some(order) => { Some(order) => {
let mut order_bys: Vec<OrderBy> = Vec::with_capacity(order.len() * 2); // Space for tags. let mut order_bys: Vec<OrderBy> = Vec::with_capacity(order.len() * 2); // Space for tags.
let mut vars: BTreeSet<Variable> = BTreeSet::default(); let mut vars: BTreeSet<Variable> = BTreeSet::default();
for Order(direction, var) in order.into_iter() { for Order(direction, var) in order.into_iter() {
@ -221,49 +234,63 @@ fn validate_and_simplify_order(cc: &ConjoiningClauses, order: Option<Vec<Order>>
// Otherwise, determine if we also need to order by type… // Otherwise, determine if we also need to order by type…
if cc.known_type(&var).is_none() { if cc.known_type(&var).is_none() {
order_bys.push(OrderBy(direction.clone(), VariableColumn::VariableTypeTag(var.clone()))); order_bys.push(OrderBy(
direction.clone(),
VariableColumn::VariableTypeTag(var.clone()),
));
} }
order_bys.push(OrderBy(direction, VariableColumn::Variable(var.clone()))); order_bys.push(OrderBy(direction, VariableColumn::Variable(var.clone())));
vars.insert(var.clone()); vars.insert(var.clone());
} }
Ok((if order_bys.is_empty() { None } else { Some(order_bys) }, vars)) Ok((
if order_bys.is_empty() {
None
} else {
Some(order_bys)
},
vars,
))
} }
} }
} }
fn simplify_limit(mut query: AlgebraicQuery) -> Result<AlgebraicQuery> { fn simplify_limit(mut query: AlgebraicQuery) -> Result<AlgebraicQuery> {
// Unpack any limit variables in place. // Unpack any limit variables in place.
let refined_limit = let refined_limit = match query.limit {
match query.limit { Limit::Variable(ref v) => {
Limit::Variable(ref v) => { match query.cc.bound_value(v) {
match query.cc.bound_value(v) { Some(TypedValue::Long(n)) => {
Some(TypedValue::Long(n)) => { if n <= 0 {
if n <= 0 { // User-specified limits should always be natural numbers (> 0).
// User-specified limits should always be natural numbers (> 0). bail!(AlgebrizerError::InvalidLimit(
bail!(AlgebrizerError::InvalidLimit(n.to_string(), ValueType::Long)) n.to_string(),
} else { ValueType::Long
Some(Limit::Fixed(n as u64)) ))
} } else {
}, Some(Limit::Fixed(n as u64))
Some(val) => { }
// Same.
bail!(AlgebrizerError::InvalidLimit(format!("{:?}", val), val.value_type()))
},
None => {
// We know that the limit variable is mentioned in `:in`.
// That it's not bound here implies that we haven't got all the variables
// we'll need to run the query yet.
// (We should never hit this in `q_once`.)
// Simply pass the `Limit` through to `SelectQuery` untouched.
None
},
} }
}, Some(val) => {
Limit::None => None, // Same.
Limit::Fixed(_) => None, bail!(AlgebrizerError::InvalidLimit(
}; format!("{:?}", val),
val.value_type()
))
}
None => {
// We know that the limit variable is mentioned in `:in`.
// That it's not bound here implies that we haven't got all the variables
// we'll need to run the query yet.
// (We should never hit this in `q_once`.)
// Simply pass the `Limit` through to `SelectQuery` untouched.
None
}
}
}
Limit::None => None,
Limit::Fixed(_) => None,
};
if let Some(lim) = refined_limit { if let Some(lim) = refined_limit {
query.limit = lim; query.limit = lim;
@ -271,12 +298,15 @@ fn simplify_limit(mut query: AlgebraicQuery) -> Result<AlgebraicQuery> {
Ok(query) Ok(query)
} }
pub fn algebrize_with_inputs(known: Known, pub fn algebrize_with_inputs(
parsed: FindQuery, known: Known,
counter: usize, parsed: FindQuery,
inputs: QueryInputs) -> Result<AlgebraicQuery> { counter: usize,
inputs: QueryInputs,
) -> Result<AlgebraicQuery> {
let alias_counter = RcCounter::with_initial(counter); let alias_counter = RcCounter::with_initial(counter);
let mut cc = ConjoiningClauses::with_inputs_and_alias_counter(parsed.in_vars, inputs, alias_counter); let mut cc =
ConjoiningClauses::with_inputs_and_alias_counter(parsed.in_vars, inputs, alias_counter);
// This is so the rest of the query knows that `?x` is a ref if `(pull ?x …)` appears in `:find`. // This is so the rest of the query knows that `?x` is a ref if `(pull ?x …)` appears in `:find`.
cc.derive_types_from_find_spec(&parsed.find_spec); cc.derive_types_from_find_spec(&parsed.find_spec);
@ -297,11 +327,15 @@ pub fn algebrize_with_inputs(known: Known,
let (order, extra_vars) = validate_and_simplify_order(&cc, parsed.order)?; let (order, extra_vars) = validate_and_simplify_order(&cc, parsed.order)?;
// This might leave us with an unused `:in` variable. // This might leave us with an unused `:in` variable.
let limit = if parsed.find_spec.is_unit_limited() { Limit::Fixed(1) } else { parsed.limit }; let limit = if parsed.find_spec.is_unit_limited() {
Limit::Fixed(1)
} else {
parsed.limit
};
let q = AlgebraicQuery { let q = AlgebraicQuery {
default_source: parsed.default_source, default_source: parsed.default_source,
find_spec: Rc::new(parsed.find_spec), find_spec: Rc::new(parsed.find_spec),
has_aggregates: false, // TODO: we don't parse them yet. has_aggregates: false, // TODO: we don't parse them yet.
with: parsed.with, with: parsed.with,
named_projection: extra_vars, named_projection: extra_vars,
order: order, order: order,
@ -313,30 +347,14 @@ pub fn algebrize_with_inputs(known: Known,
simplify_limit(q) simplify_limit(q)
} }
pub use clauses::{ pub use clauses::ConjoiningClauses;
ConjoiningClauses,
};
pub use types::{ pub use types::{
Column, Column, ColumnAlternation, ColumnConstraint, ColumnConstraintOrAlternation, ColumnIntersection,
ColumnAlternation, ColumnName, ComputedTable, DatomsColumn, DatomsTable, FulltextColumn, OrderBy, QualifiedAlias,
ColumnConstraint, QueryValue, SourceAlias, TableAlias, VariableColumn,
ColumnConstraintOrAlternation,
ColumnIntersection,
ColumnName,
ComputedTable,
DatomsColumn,
DatomsTable,
FulltextColumn,
OrderBy,
QualifiedAlias,
QueryValue,
SourceAlias,
TableAlias,
VariableColumn,
}; };
impl FindQuery { impl FindQuery {
pub fn simple(spec: FindSpec, where_clauses: Vec<WhereClause>) -> FindQuery { pub fn simple(spec: FindSpec, where_clauses: Vec<WhereClause>) -> FindQuery {
FindQuery { FindQuery {

View file

@ -9,43 +9,24 @@
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fmt::{ use std::fmt::{Debug, Formatter};
Debug,
Formatter,
};
use core_traits::{ use core_traits::{Entid, TypedValue, ValueType, ValueTypeSet};
Entid,
TypedValue,
ValueType,
ValueTypeSet,
};
use mentat_core::{ use mentat_core::ValueRc;
ValueRc,
};
use edn::query::{ use edn::query::{Direction, FindSpec, Keyword, Limit, Order, SrcVar, Variable, WhereClause};
Direction,
FindSpec,
Keyword,
Limit,
Order,
SrcVar,
Variable,
WhereClause,
};
/// This enum models the fixed set of default tables we have -- two /// This enum models the fixed set of default tables we have -- two
/// tables and two views -- and computed tables defined in the enclosing CC. /// tables and two views -- and computed tables defined in the enclosing CC.
#[derive(PartialEq, Eq, Clone, Copy, Debug)] #[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum DatomsTable { pub enum DatomsTable {
Datoms, // The non-fulltext datoms table. Datoms, // The non-fulltext datoms table.
FulltextValues, // The virtual table mapping IDs to strings. FulltextValues, // The virtual table mapping IDs to strings.
FulltextDatoms, // The fulltext-datoms view. FulltextDatoms, // The fulltext-datoms view.
AllDatoms, // Fulltext and non-fulltext datoms. AllDatoms, // Fulltext and non-fulltext datoms.
Computed(usize), // A computed table, tracked elsewhere in the query. Computed(usize), // A computed table, tracked elsewhere in the query.
Transactions, // The transactions table, which makes the tx-data log API efficient. Transactions, // The transactions table, which makes the tx-data log API efficient.
} }
/// A source of rows that isn't a named table -- typically a subquery or union. /// A source of rows that isn't a named table -- typically a subquery or union.
@ -295,7 +276,8 @@ impl QualifiedAlias {
Column::Fulltext(_) => None, Column::Fulltext(_) => None,
Column::Variable(_) => None, Column::Variable(_) => None,
Column::Transactions(ref c) => c.associated_type_tag_column().map(Column::Transactions), Column::Transactions(ref c) => c.associated_type_tag_column().map(Column::Transactions),
}.map(|d| QualifiedAlias(self.0.clone(), d)) }
.map(|d| QualifiedAlias(self.0.clone(), d))
} }
} }
@ -316,19 +298,10 @@ impl Debug for QueryValue {
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result { fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
use self::QueryValue::*; use self::QueryValue::*;
match self { match self {
&Column(ref qa) => { &Column(ref qa) => write!(f, "{:?}", qa),
write!(f, "{:?}", qa) &Entid(ref entid) => write!(f, "entity({:?})", entid),
}, &TypedValue(ref typed_value) => write!(f, "value({:?})", typed_value),
&Entid(ref entid) => { &PrimitiveLong(value) => write!(f, "primitive({:?})", value),
write!(f, "entity({:?})", entid)
},
&TypedValue(ref typed_value) => {
write!(f, "value({:?})", typed_value)
},
&PrimitiveLong(value) => {
write!(f, "primitive({:?})", value)
},
} }
} }
} }
@ -370,25 +343,25 @@ impl Inequality {
pub fn to_sql_operator(self) -> &'static str { pub fn to_sql_operator(self) -> &'static str {
use self::Inequality::*; use self::Inequality::*;
match self { match self {
LessThan => "<", LessThan => "<",
LessThanOrEquals => "<=", LessThanOrEquals => "<=",
GreaterThan => ">", GreaterThan => ">",
GreaterThanOrEquals => ">=", GreaterThanOrEquals => ">=",
NotEquals => "<>", NotEquals => "<>",
Unpermute => "<", Unpermute => "<",
Differ => "<>", Differ => "<>",
TxAfter => ">", TxAfter => ">",
TxBefore => "<", TxBefore => "<",
} }
} }
pub fn from_datalog_operator(s: &str) -> Option<Inequality> { pub fn from_datalog_operator(s: &str) -> Option<Inequality> {
match s { match s {
"<" => Some(Inequality::LessThan), "<" => Some(Inequality::LessThan),
"<=" => Some(Inequality::LessThanOrEquals), "<=" => Some(Inequality::LessThanOrEquals),
">" => Some(Inequality::GreaterThan), ">" => Some(Inequality::GreaterThan),
">=" => Some(Inequality::GreaterThanOrEquals), ">=" => Some(Inequality::GreaterThanOrEquals),
"!=" => Some(Inequality::NotEquals), "!=" => Some(Inequality::NotEquals),
@ -405,21 +378,12 @@ impl Inequality {
pub fn supported_types(&self) -> ValueTypeSet { pub fn supported_types(&self) -> ValueTypeSet {
use self::Inequality::*; use self::Inequality::*;
match self { match self {
&LessThan | &LessThan | &LessThanOrEquals | &GreaterThan | &GreaterThanOrEquals | &NotEquals => {
&LessThanOrEquals |
&GreaterThan |
&GreaterThanOrEquals |
&NotEquals => {
let mut ts = ValueTypeSet::of_numeric_types(); let mut ts = ValueTypeSet::of_numeric_types();
ts.insert(ValueType::Instant); ts.insert(ValueType::Instant);
ts ts
}, }
&Unpermute | &Unpermute | &Differ | &TxAfter | &TxBefore => ValueTypeSet::of_one(ValueType::Ref),
&Differ |
&TxAfter |
&TxBefore => {
ValueTypeSet::of_one(ValueType::Ref)
},
} }
} }
} }
@ -432,7 +396,7 @@ impl Debug for Inequality {
&LessThanOrEquals => "<=", &LessThanOrEquals => "<=",
&GreaterThan => ">", &GreaterThan => ">",
&GreaterThanOrEquals => ">=", &GreaterThanOrEquals => ">=",
&NotEquals => "!=", // Datalog uses !=. SQL uses <>. &NotEquals => "!=", // Datalog uses !=. SQL uses <>.
&Unpermute => "<", &Unpermute => "<",
&Differ => "<>", &Differ => "<>",
@ -570,51 +534,78 @@ impl Debug for ColumnConstraint {
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result { fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
use self::ColumnConstraint::*; use self::ColumnConstraint::*;
match self { match self {
&Equals(ref qa1, ref thing) => { &Equals(ref qa1, ref thing) => write!(f, "{:?} = {:?}", qa1, thing),
write!(f, "{:?} = {:?}", qa1, thing)
},
&Inequality { operator, ref left, ref right } => { &Inequality {
write!(f, "{:?} {:?} {:?}", left, operator, right) operator,
}, ref left,
ref right,
} => write!(f, "{:?} {:?} {:?}", left, operator, right),
&Matches(ref qa, ref thing) => { &Matches(ref qa, ref thing) => write!(f, "{:?} MATCHES {:?}", qa, thing),
write!(f, "{:?} MATCHES {:?}", qa, thing)
},
&HasTypes { ref value, ref value_types, check_value } => { &HasTypes {
ref value,
ref value_types,
check_value,
} => {
// This is cludgey, but it's debug code. // This is cludgey, but it's debug code.
write!(f, "(")?; write!(f, "(")?;
for value_type in value_types.iter() { for value_type in value_types.iter() {
write!(f, "({:?}.value_type_tag = {:?}", value, value_type)?; write!(f, "({:?}.value_type_tag = {:?}", value, value_type)?;
if check_value && value_type == ValueType::Double || value_type == ValueType::Long { if check_value && value_type == ValueType::Double
write!(f, " AND typeof({:?}) = '{:?}')", value, || value_type == ValueType::Long
if value_type == ValueType::Double { "real" } else { "integer" })?; {
write!(
f,
" AND typeof({:?}) = '{:?}')",
value,
if value_type == ValueType::Double {
"real"
} else {
"integer"
}
)?;
} else { } else {
write!(f, ")")?; write!(f, ")")?;
} }
write!(f, " OR ")?; write!(f, " OR ")?;
} }
write!(f, "1)") write!(f, "1)")
}, }
&NotExists(ref ct) => { &NotExists(ref ct) => write!(f, "NOT EXISTS {:?}", ct),
write!(f, "NOT EXISTS {:?}", ct)
},
} }
} }
} }
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub enum EmptyBecause { pub enum EmptyBecause {
CachedAttributeHasNoValues { entity: Entid, attr: Entid }, CachedAttributeHasNoValues {
CachedAttributeHasNoEntity { value: TypedValue, attr: Entid }, entity: Entid,
ConflictingBindings { var: Variable, existing: TypedValue, desired: TypedValue }, attr: Entid,
},
CachedAttributeHasNoEntity {
value: TypedValue,
attr: Entid,
},
ConflictingBindings {
var: Variable,
existing: TypedValue,
desired: TypedValue,
},
// A variable is known to be of two conflicting sets of types. // A variable is known to be of two conflicting sets of types.
TypeMismatch { var: Variable, existing: ValueTypeSet, desired: ValueTypeSet }, TypeMismatch {
var: Variable,
existing: ValueTypeSet,
desired: ValueTypeSet,
},
// The same, but for non-variables. // The same, but for non-variables.
KnownTypeMismatch { left: ValueTypeSet, right: ValueTypeSet }, KnownTypeMismatch {
left: ValueTypeSet,
right: ValueTypeSet,
},
NoValidTypes(Variable), NoValidTypes(Variable),
NonAttributeArgument, NonAttributeArgument,
NonInstantArgument, NonInstantArgument,
@ -627,76 +618,70 @@ pub enum EmptyBecause {
InvalidAttributeEntid(Entid), InvalidAttributeEntid(Entid),
InvalidBinding(Column, TypedValue), InvalidBinding(Column, TypedValue),
ValueTypeMismatch(ValueType, TypedValue), ValueTypeMismatch(ValueType, TypedValue),
AttributeLookupFailed, // Catch-all, because the table lookup code is lazy. TODO AttributeLookupFailed, // Catch-all, because the table lookup code is lazy. TODO
} }
impl Debug for EmptyBecause { impl Debug for EmptyBecause {
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result { fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
use self::EmptyBecause::*; use self::EmptyBecause::*;
match self { match self {
&CachedAttributeHasNoEntity { ref value, ref attr } => { &CachedAttributeHasNoEntity {
write!(f, "(?e, {}, {:?}, _) not present in store", attr, value) ref value,
}, ref attr,
&CachedAttributeHasNoValues { ref entity, ref attr } => { } => write!(f, "(?e, {}, {:?}, _) not present in store", attr, value),
write!(f, "({}, {}, ?v, _) not present in store", entity, attr) &CachedAttributeHasNoValues {
}, ref entity,
&ConflictingBindings { ref var, ref existing, ref desired } => { ref attr,
write!(f, "Var {:?} can't be {:?} because it's already bound to {:?}", } => write!(f, "({}, {}, ?v, _) not present in store", entity, attr),
var, desired, existing) &ConflictingBindings {
}, ref var,
&TypeMismatch { ref var, ref existing, ref desired } => { ref existing,
write!(f, "Type mismatch: {:?} can't be {:?}, because it's already {:?}", ref desired,
var, desired, existing) } => write!(
}, f,
&KnownTypeMismatch { ref left, ref right } => { "Var {:?} can't be {:?} because it's already bound to {:?}",
write!(f, "Type mismatch: {:?} can't be compared to {:?}", var, desired, existing
left, right) ),
}, &TypeMismatch {
&NoValidTypes(ref var) => { ref var,
write!(f, "Type mismatch: {:?} has no valid types", var) ref existing,
}, ref desired,
&NonAttributeArgument => { } => write!(
write!(f, "Non-attribute argument in attribute place") f,
}, "Type mismatch: {:?} can't be {:?}, because it's already {:?}",
&NonInstantArgument => { var, desired, existing
write!(f, "Non-instant argument in instant place") ),
}, &KnownTypeMismatch {
&NonEntityArgument => { ref left,
write!(f, "Non-entity argument in entity place") ref right,
}, } => write!(
&NonNumericArgument => { f,
write!(f, "Non-numeric argument in numeric place") "Type mismatch: {:?} can't be compared to {:?}",
}, left, right
&NonStringFulltextValue => { ),
write!(f, "Non-string argument for fulltext attribute") &NoValidTypes(ref var) => write!(f, "Type mismatch: {:?} has no valid types", var),
}, &NonAttributeArgument => write!(f, "Non-attribute argument in attribute place"),
&UnresolvedIdent(ref kw) => { &NonInstantArgument => write!(f, "Non-instant argument in instant place"),
write!(f, "Couldn't resolve keyword {}", kw) &NonEntityArgument => write!(f, "Non-entity argument in entity place"),
}, &NonNumericArgument => write!(f, "Non-numeric argument in numeric place"),
&InvalidAttributeIdent(ref kw) => { &NonStringFulltextValue => write!(f, "Non-string argument for fulltext attribute"),
write!(f, "{} does not name an attribute", kw) &UnresolvedIdent(ref kw) => write!(f, "Couldn't resolve keyword {}", kw),
}, &InvalidAttributeIdent(ref kw) => write!(f, "{} does not name an attribute", kw),
&InvalidAttributeEntid(entid) => { &InvalidAttributeEntid(entid) => write!(f, "{} is not an attribute", entid),
write!(f, "{} is not an attribute", entid) &NonFulltextAttribute(entid) => write!(f, "{} is not a fulltext attribute", entid),
},
&NonFulltextAttribute(entid) => {
write!(f, "{} is not a fulltext attribute", entid)
},
&InvalidBinding(ref column, ref tv) => { &InvalidBinding(ref column, ref tv) => {
write!(f, "{:?} cannot name column {:?}", tv, column) write!(f, "{:?} cannot name column {:?}", tv, column)
}, }
&ValueTypeMismatch(value_type, ref typed_value) => { &ValueTypeMismatch(value_type, ref typed_value) => write!(
write!(f, "Type mismatch: {:?} doesn't match attribute type {:?}", f,
typed_value, value_type) "Type mismatch: {:?} doesn't match attribute type {:?}",
}, typed_value, value_type
&AttributeLookupFailed => { ),
write!(f, "Attribute lookup failed") &AttributeLookupFailed => write!(f, "Attribute lookup failed"),
},
} }
} }
} }
/// A `FindQuery` represents a valid query to the query algebrizer. /// A `FindQuery` represents a valid query to the query algebrizer.
/// ///
/// We split `FindQuery` from `ParsedQuery` because it's not easy to generalize over containers /// We split `FindQuery` from `ParsedQuery` because it's not easy to generalize over containers
@ -720,7 +705,7 @@ pub struct FindQuery {
pub enum EvolvedNonValuePlace { pub enum EvolvedNonValuePlace {
Placeholder, Placeholder,
Variable(Variable), Variable(Variable),
Entid(Entid), // Will always be +ve. See #190. Entid(Entid), // Will always be +ve. See #190.
} }
// TODO: some of these aren't necessary? // TODO: some of these aren't necessary?

View file

@ -10,18 +10,9 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use edn::query::{ use edn::query::{ContainsVariables, NotJoin, OrJoin, UnifyVars, Variable};
ContainsVariables,
OrJoin,
NotJoin,
Variable,
UnifyVars,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, Result};
AlgebrizerError,
Result,
};
/// In an `or` expression, every mentioned var is considered 'free'. /// In an `or` expression, every mentioned var is considered 'free'.
/// In an `or-join` expression, every var in the var list is 'required'. /// In an `or-join` expression, every var in the var list is 'required'.
@ -61,7 +52,7 @@ pub(crate) fn validate_or_join(or_join: &OrJoin) -> Result<()> {
} }
Ok(()) Ok(())
} }
}, }
UnifyVars::Explicit(ref vars) => { UnifyVars::Explicit(ref vars) => {
// Each leg must use the joined vars. // Each leg must use the joined vars.
let var_set: BTreeSet<Variable> = vars.iter().cloned().collect(); let var_set: BTreeSet<Variable> = vars.iter().cloned().collect();
@ -71,16 +62,14 @@ pub(crate) fn validate_or_join(or_join: &OrJoin) -> Result<()> {
} }
} }
Ok(()) Ok(())
}, }
} }
} }
pub(crate) fn validate_not_join(not_join: &NotJoin) -> Result<()> { pub(crate) fn validate_not_join(not_join: &NotJoin) -> Result<()> {
// Grab our mentioned variables and ensure that the rules are followed. // Grab our mentioned variables and ensure that the rules are followed.
match not_join.unify_vars { match not_join.unify_vars {
UnifyVars::Implicit => { UnifyVars::Implicit => Ok(()),
Ok(())
},
UnifyVars::Explicit(ref vars) => { UnifyVars::Explicit(ref vars) => {
// The joined vars must each appear somewhere in the clause's mentioned variables. // The joined vars must each appear somewhere in the clause's mentioned variables.
let var_set: BTreeSet<Variable> = vars.iter().cloned().collect(); let var_set: BTreeSet<Variable> = vars.iter().cloned().collect();
@ -88,33 +77,25 @@ pub(crate) fn validate_not_join(not_join: &NotJoin) -> Result<()> {
bail!(AlgebrizerError::NonMatchingVariablesInNotClause) bail!(AlgebrizerError::NonMatchingVariablesInNotClause)
} }
Ok(()) Ok(())
}, }
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
extern crate mentat_core;
extern crate edn; extern crate edn;
extern crate mentat_core;
use edn::query::{ use edn::query::{
Keyword, Keyword, OrWhereClause, Pattern, PatternNonValuePlace, PatternValuePlace, UnifyVars,
OrWhereClause, Variable, WhereClause,
Pattern,
PatternNonValuePlace,
PatternValuePlace,
UnifyVars,
Variable,
WhereClause,
}; };
use clauses::ident; use clauses::ident;
use super::*; use super::*;
use parse_find_string; use parse_find_string;
use types::{ use types::FindQuery;
FindQuery,
};
fn value_ident(ns: &str, name: &str) -> PatternValuePlace { fn value_ident(ns: &str, name: &str) -> PatternValuePlace {
Keyword::namespaced(ns, name).into() Keyword::namespaced(ns, name).into()
@ -134,7 +115,7 @@ mod tests {
assert_eq!((), validate_or_join(&or_join).unwrap()); assert_eq!((), validate_or_join(&or_join).unwrap());
assert_eq!(expected_unify, or_join.unify_vars); assert_eq!(expected_unify, or_join.unify_vars);
or_join.clauses or_join.clauses
}, }
_ => panic!(), _ => panic!(),
} }
} }
@ -157,31 +138,38 @@ mod tests {
left, left,
OrWhereClause::Clause(WhereClause::Pattern(Pattern { OrWhereClause::Clause(WhereClause::Pattern(Pattern {
source: None, source: None,
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?artist")), entity: PatternNonValuePlace::Variable(Variable::from_valid_name(
"?artist"
)),
attribute: ident("artist", "type"), attribute: ident("artist", "type"),
value: value_ident("artist.type", "group"), value: value_ident("artist.type", "group"),
tx: PatternNonValuePlace::Placeholder, tx: PatternNonValuePlace::Placeholder,
}))); }))
);
assert_eq!( assert_eq!(
right, right,
OrWhereClause::And( OrWhereClause::And(vec![
vec![ WhereClause::Pattern(Pattern {
WhereClause::Pattern(Pattern { source: None,
source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name(
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?artist")), "?artist"
attribute: ident("artist", "type"), )),
value: value_ident("artist.type", "person"), attribute: ident("artist", "type"),
tx: PatternNonValuePlace::Placeholder, value: value_ident("artist.type", "person"),
}), tx: PatternNonValuePlace::Placeholder,
WhereClause::Pattern(Pattern { }),
source: None, WhereClause::Pattern(Pattern {
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?artist")), source: None,
attribute: ident("artist", "gender"), entity: PatternNonValuePlace::Variable(Variable::from_valid_name(
value: value_ident("artist.gender", "female"), "?artist"
tx: PatternNonValuePlace::Placeholder, )),
}), attribute: ident("artist", "gender"),
])); value: value_ident("artist.gender", "female"),
}, tx: PatternNonValuePlace::Placeholder,
}),
])
);
}
_ => panic!(), _ => panic!(),
}; };
} }
@ -193,7 +181,12 @@ mod tests {
:where (or [?artist :artist/type :artist.type/group] :where (or [?artist :artist/type :artist.type/group]
[?artist :artist/type ?type])]"#; [?artist :artist/type ?type])]"#;
let parsed = parse_find_string(query).expect("expected successful parse"); let parsed = parse_find_string(query).expect("expected successful parse");
match parsed.where_clauses.into_iter().next().expect("expected at least one clause") { match parsed
.where_clauses
.into_iter()
.next()
.expect("expected at least one clause")
{
WhereClause::OrJoin(or_join) => assert!(validate_or_join(&or_join).is_err()), WhereClause::OrJoin(or_join) => assert!(validate_or_join(&or_join).is_err()),
_ => panic!(), _ => panic!(),
} }
@ -209,7 +202,10 @@ mod tests {
(and [?artist :artist/type ?type] (and [?artist :artist/type ?type]
[?type :artist/role :artist.role/parody]))]"#; [?type :artist/role :artist.role/parody]))]"#;
let parsed = parse_find_string(query).expect("expected successful parse"); let parsed = parse_find_string(query).expect("expected successful parse");
let clauses = valid_or_join(parsed, UnifyVars::Explicit(::std::iter::once(Variable::from_valid_name("?artist")).collect())); let clauses = valid_or_join(
parsed,
UnifyVars::Explicit(::std::iter::once(Variable::from_valid_name("?artist")).collect()),
);
// Let's do some detailed parse checks. // Let's do some detailed parse checks.
let mut arms = clauses.into_iter(); let mut arms = clauses.into_iter();
@ -219,36 +215,42 @@ mod tests {
left, left,
OrWhereClause::Clause(WhereClause::Pattern(Pattern { OrWhereClause::Clause(WhereClause::Pattern(Pattern {
source: None, source: None,
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?artist")), entity: PatternNonValuePlace::Variable(Variable::from_valid_name(
"?artist"
)),
attribute: ident("artist", "type"), attribute: ident("artist", "type"),
value: value_ident("artist.type", "group"), value: value_ident("artist.type", "group"),
tx: PatternNonValuePlace::Placeholder, tx: PatternNonValuePlace::Placeholder,
}))); }))
);
assert_eq!( assert_eq!(
right, right,
OrWhereClause::And( OrWhereClause::And(vec![
vec![ WhereClause::Pattern(Pattern {
WhereClause::Pattern(Pattern { source: None,
source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name(
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?artist")), "?artist"
attribute: ident("artist", "type"), )),
value: PatternValuePlace::Variable(Variable::from_valid_name("?type")), attribute: ident("artist", "type"),
tx: PatternNonValuePlace::Placeholder, value: PatternValuePlace::Variable(Variable::from_valid_name("?type")),
}), tx: PatternNonValuePlace::Placeholder,
WhereClause::Pattern(Pattern { }),
source: None, WhereClause::Pattern(Pattern {
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?type")), source: None,
attribute: ident("artist", "role"), entity: PatternNonValuePlace::Variable(Variable::from_valid_name(
value: value_ident("artist.role", "parody"), "?type"
tx: PatternNonValuePlace::Placeholder, )),
}), attribute: ident("artist", "role"),
])); value: value_ident("artist.role", "parody"),
}, tx: PatternNonValuePlace::Placeholder,
}),
])
);
}
_ => panic!(), _ => panic!(),
}; };
} }
/// Tests that the top-level form is a valid `not`, returning the clauses. /// Tests that the top-level form is a valid `not`, returning the clauses.
fn valid_not_join(parsed: FindQuery, expected_unify: UnifyVars) -> Vec<WhereClause> { fn valid_not_join(parsed: FindQuery, expected_unify: UnifyVars) -> Vec<WhereClause> {
// Filter out all the clauses that are not `not`s. // Filter out all the clauses that are not `not`s.
@ -267,7 +269,7 @@ mod tests {
assert_eq!((), validate_not_join(&not_join).unwrap()); assert_eq!((), validate_not_join(&not_join).unwrap());
assert_eq!(expected_unify, not_join.unify_vars); assert_eq!(expected_unify, not_join.unify_vars);
not_join.clauses not_join.clauses
}, }
_ => panic!(), _ => panic!(),
} }
} }
@ -296,7 +298,8 @@ mod tests {
attribute: artist_country.clone(), attribute: artist_country.clone(),
value: value_ident("country", "CA"), value: value_ident("country", "CA"),
tx: PatternNonValuePlace::Placeholder, tx: PatternNonValuePlace::Placeholder,
})); })
);
assert_eq!( assert_eq!(
clause2, clause2,
WhereClause::Pattern(Pattern { WhereClause::Pattern(Pattern {
@ -305,8 +308,9 @@ mod tests {
attribute: artist_country, attribute: artist_country,
value: value_ident("country", "GB"), value: value_ident("country", "GB"),
tx: PatternNonValuePlace::Placeholder, tx: PatternNonValuePlace::Placeholder,
})); })
}, );
}
_ => panic!(), _ => panic!(),
}; };
} }
@ -319,7 +323,10 @@ mod tests {
[?release :release/artists ?artist] [?release :release/artists ?artist]
[?release :release/year 1970])]"#; [?release :release/year 1970])]"#;
let parsed = parse_find_string(query).expect("expected successful parse"); let parsed = parse_find_string(query).expect("expected successful parse");
let clauses = valid_not_join(parsed, UnifyVars::Explicit(::std::iter::once(Variable::from_valid_name("?artist")).collect())); let clauses = valid_not_join(
parsed,
UnifyVars::Explicit(::std::iter::once(Variable::from_valid_name("?artist")).collect()),
);
let release = PatternNonValuePlace::Variable(Variable::from_valid_name("?release")); let release = PatternNonValuePlace::Variable(Variable::from_valid_name("?release"));
let artist = PatternValuePlace::Variable(Variable::from_valid_name("?artist")); let artist = PatternValuePlace::Variable(Variable::from_valid_name("?artist"));
@ -335,7 +342,8 @@ mod tests {
attribute: ident("release", "artists"), attribute: ident("release", "artists"),
value: artist, value: artist,
tx: PatternNonValuePlace::Placeholder, tx: PatternNonValuePlace::Placeholder,
})); })
);
assert_eq!( assert_eq!(
clause2, clause2,
WhereClause::Pattern(Pattern { WhereClause::Pattern(Pattern {
@ -344,8 +352,9 @@ mod tests {
attribute: ident("release", "year"), attribute: ident("release", "year"),
value: PatternValuePlace::EntidOrInteger(1970), value: PatternValuePlace::EntidOrInteger(1970),
tx: PatternNonValuePlace::Placeholder, tx: PatternNonValuePlace::Placeholder,
})); })
}, );
}
_ => panic!(), _ => panic!(),
}; };
} }

View file

@ -8,32 +8,21 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate core_traits;
extern crate edn; extern crate edn;
extern crate mentat_core; extern crate mentat_core;
extern crate core_traits;
extern crate mentat_query_algebrizer; extern crate mentat_query_algebrizer;
extern crate query_algebrizer_traits; extern crate query_algebrizer_traits;
mod utils; mod utils;
use core_traits::{ use core_traits::{Attribute, ValueType};
Attribute,
ValueType,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::query::{ use edn::query::Keyword;
Keyword,
};
use utils::{ use utils::{add_attribute, alg, associate_ident};
add_attribute,
alg,
associate_ident,
};
use mentat_query_algebrizer::Known; use mentat_query_algebrizer::Known;
@ -44,33 +33,53 @@ fn prepopulated_schema() -> Schema {
associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67); associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67);
associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68); associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68);
associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69); associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69);
add_attribute(&mut schema, 65, Attribute { add_attribute(
value_type: ValueType::String, &mut schema,
multival: false, 65,
..Default::default() Attribute {
}); value_type: ValueType::String,
add_attribute(&mut schema, 66, Attribute { multival: false,
value_type: ValueType::String, ..Default::default()
index: true, },
fulltext: true, );
multival: true, add_attribute(
..Default::default() &mut schema,
}); 66,
add_attribute(&mut schema, 67, Attribute { Attribute {
value_type: ValueType::String, value_type: ValueType::String,
multival: true, index: true,
..Default::default() fulltext: true,
}); multival: true,
add_attribute(&mut schema, 68, Attribute { ..Default::default()
value_type: ValueType::Long, },
multival: false, );
..Default::default() add_attribute(
}); &mut schema,
add_attribute(&mut schema, 69, Attribute { 67,
value_type: ValueType::Long, Attribute {
multival: false, value_type: ValueType::String,
..Default::default() multival: true,
}); ..Default::default()
},
);
add_attribute(
&mut schema,
68,
Attribute {
value_type: ValueType::Long,
multival: false,
..Default::default()
},
);
add_attribute(
&mut schema,
69,
Attribute {
value_type: ValueType::Long,
multival: false,
..Default::default()
},
);
schema schema
} }

View file

@ -8,9 +8,9 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate core_traits;
extern crate edn; extern crate edn;
extern crate mentat_core; extern crate mentat_core;
extern crate core_traits;
extern crate mentat_query_algebrizer; extern crate mentat_query_algebrizer;
extern crate query_algebrizer_traits; extern crate query_algebrizer_traits;
@ -18,40 +18,17 @@ mod utils;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use core_traits::{ use core_traits::{Attribute, TypedValue, ValueType};
Attribute,
ValueType,
TypedValue,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::query::{ use edn::query::{Keyword, PlainSymbol, Variable};
Keyword,
PlainSymbol,
Variable,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::{AlgebrizerError, BindingError};
AlgebrizerError,
BindingError,
};
use mentat_query_algebrizer::{ use mentat_query_algebrizer::{ComputedTable, Known, QueryInputs};
ComputedTable,
Known,
QueryInputs,
};
use utils::{ use utils::{add_attribute, alg, associate_ident, bails, bails_with_inputs};
add_attribute,
alg,
associate_ident,
bails,
bails_with_inputs,
};
fn prepopulated_schema() -> Schema { fn prepopulated_schema() -> Schema {
let mut schema = Schema::default(); let mut schema = Schema::default();
@ -60,31 +37,51 @@ fn prepopulated_schema() -> Schema {
associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67); associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67);
associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68); associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68);
associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69); associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69);
add_attribute(&mut schema, 65, Attribute { add_attribute(
value_type: ValueType::String, &mut schema,
multival: false, 65,
..Default::default() Attribute {
}); value_type: ValueType::String,
add_attribute(&mut schema, 66, Attribute { multival: false,
value_type: ValueType::Ref, ..Default::default()
multival: true, },
..Default::default() );
}); add_attribute(
add_attribute(&mut schema, 67, Attribute { &mut schema,
value_type: ValueType::String, 66,
multival: true, Attribute {
..Default::default() value_type: ValueType::Ref,
}); multival: true,
add_attribute(&mut schema, 68, Attribute { ..Default::default()
value_type: ValueType::Long, },
multival: false, );
..Default::default() add_attribute(
}); &mut schema,
add_attribute(&mut schema, 69, Attribute { 67,
value_type: ValueType::Long, Attribute {
multival: false, value_type: ValueType::String,
..Default::default() multival: true,
}); ..Default::default()
},
);
add_attribute(
&mut schema,
68,
Attribute {
value_type: ValueType::Long,
multival: false,
..Default::default()
},
);
add_attribute(
&mut schema,
69,
Attribute {
value_type: ValueType::Long,
multival: false,
..Default::default()
},
);
schema schema
} }
@ -126,10 +123,13 @@ fn test_ground_coll_skips_impossible() {
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let cc = alg(known, &q); let cc = alg(known, &q);
assert!(cc.empty_because.is_none()); assert!(cc.empty_because.is_none());
assert_eq!(cc.computed_tables[0], ComputedTable::NamedValues { assert_eq!(
names: vec![Variable::from_valid_name("?x")], cc.computed_tables[0],
values: vec![TypedValue::Ref(5), TypedValue::Ref(11)], ComputedTable::NamedValues {
}); names: vec![Variable::from_valid_name("?x")],
values: vec![TypedValue::Ref(5), TypedValue::Ref(11)],
}
);
} }
#[test] #[test]
@ -148,10 +148,21 @@ fn test_ground_rel_skips_impossible() {
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let cc = alg(known, &q); let cc = alg(known, &q);
assert!(cc.empty_because.is_none()); assert!(cc.empty_because.is_none());
assert_eq!(cc.computed_tables[0], ComputedTable::NamedValues { assert_eq!(
names: vec![Variable::from_valid_name("?x"), Variable::from_valid_name("?p")], cc.computed_tables[0],
values: vec![TypedValue::Ref(5), TypedValue::Ref(7), TypedValue::Ref(11), TypedValue::Ref(12)], ComputedTable::NamedValues {
}); names: vec![
Variable::from_valid_name("?x"),
Variable::from_valid_name("?p")
],
values: vec![
TypedValue::Ref(5),
TypedValue::Ref(7),
TypedValue::Ref(11),
TypedValue::Ref(12)
],
}
);
} }
#[test] #[test]
@ -186,8 +197,14 @@ fn test_ground_tuple_placeholders() {
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let cc = alg(known, &q); let cc = alg(known, &q);
assert!(cc.empty_because.is_none()); assert!(cc.empty_because.is_none());
assert_eq!(cc.bound_value(&Variable::from_valid_name("?x")), Some(TypedValue::Ref(8))); assert_eq!(
assert_eq!(cc.bound_value(&Variable::from_valid_name("?p")), Some(TypedValue::Ref(3))); cc.bound_value(&Variable::from_valid_name("?x")),
Some(TypedValue::Ref(8))
);
assert_eq!(
cc.bound_value(&Variable::from_valid_name("?p")),
Some(TypedValue::Ref(3))
);
} }
#[test] #[test]
@ -197,17 +214,23 @@ fn test_ground_rel_placeholders() {
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let cc = alg(known, &q); let cc = alg(known, &q);
assert!(cc.empty_because.is_none()); assert!(cc.empty_because.is_none());
assert_eq!(cc.computed_tables[0], ComputedTable::NamedValues { assert_eq!(
names: vec![Variable::from_valid_name("?x"), Variable::from_valid_name("?p")], cc.computed_tables[0],
values: vec![ ComputedTable::NamedValues {
TypedValue::Ref(8), names: vec![
TypedValue::Ref(3), Variable::from_valid_name("?x"),
TypedValue::Ref(5), Variable::from_valid_name("?p")
TypedValue::Ref(7), ],
TypedValue::Ref(5), values: vec![
TypedValue::Ref(9), TypedValue::Ref(8),
], TypedValue::Ref(3),
}); TypedValue::Ref(5),
TypedValue::Ref(7),
TypedValue::Ref(5),
TypedValue::Ref(9),
],
}
);
} }
// Nothing to do with ground, but while we're here… // Nothing to do with ground, but while we're here…
@ -227,8 +250,14 @@ fn test_ground_tuple_infers_types() {
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let cc = alg(known, &q); let cc = alg(known, &q);
assert!(cc.empty_because.is_none()); assert!(cc.empty_because.is_none());
assert_eq!(cc.bound_value(&Variable::from_valid_name("?x")), Some(TypedValue::Ref(8))); assert_eq!(
assert_eq!(cc.bound_value(&Variable::from_valid_name("?v")), Some(TypedValue::Long(10))); cc.bound_value(&Variable::from_valid_name("?x")),
Some(TypedValue::Ref(8))
);
assert_eq!(
cc.bound_value(&Variable::from_valid_name("?v")),
Some(TypedValue::Long(10))
);
} }
// We determine the types of variables in the query in an early first pass, and thus we can // We determine the types of variables in the query in an early first pass, and thus we can
@ -251,10 +280,16 @@ fn test_ground_rel_infers_types() {
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let cc = alg(known, &q); let cc = alg(known, &q);
assert!(cc.empty_because.is_none()); assert!(cc.empty_because.is_none());
assert_eq!(cc.computed_tables[0], ComputedTable::NamedValues { assert_eq!(
names: vec![Variable::from_valid_name("?x"), Variable::from_valid_name("?v")], cc.computed_tables[0],
values: vec![TypedValue::Ref(8), TypedValue::Long(10)], ComputedTable::NamedValues {
}); names: vec![
Variable::from_valid_name("?x"),
Variable::from_valid_name("?v")
],
values: vec![TypedValue::Ref(8), TypedValue::Long(10)],
}
);
} }
#[test] #[test]
@ -262,8 +297,7 @@ fn test_ground_coll_heterogeneous_types() {
let q = r#"[:find ?x :where [?x _ ?v] [(ground [false 8.5]) [?v ...]]]"#; let q = r#"[:find ?x :where [?x _ ?v] [(ground [false 8.5]) [?v ...]]]"#;
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
assert_eq!(bails(known, &q), assert_eq!(bails(known, &q), AlgebrizerError::InvalidGroundConstant);
AlgebrizerError::InvalidGroundConstant);
} }
#[test] #[test]
@ -271,8 +305,7 @@ fn test_ground_rel_heterogeneous_types() {
let q = r#"[:find ?x :where [?x _ ?v] [(ground [[false] [5]]) [[?v]]]]"#; let q = r#"[:find ?x :where [?x _ ?v] [(ground [[false] [5]]) [[?v]]]]"#;
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
assert_eq!(bails(known, &q), assert_eq!(bails(known, &q), AlgebrizerError::InvalidGroundConstant);
AlgebrizerError::InvalidGroundConstant);
} }
#[test] #[test]
@ -280,8 +313,13 @@ fn test_ground_tuple_duplicate_vars() {
let q = r#"[:find ?x :where [?x :foo/age ?v] [(ground [8 10]) [?x ?x]]]"#; let q = r#"[:find ?x :where [?x :foo/age ?v] [(ground [8 10]) [?x ?x]]]"#;
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
assert_eq!(bails(known, &q), assert_eq!(
AlgebrizerError::InvalidBinding(PlainSymbol::plain("ground"), BindingError::RepeatedBoundVariable)); bails(known, &q),
AlgebrizerError::InvalidBinding(
PlainSymbol::plain("ground"),
BindingError::RepeatedBoundVariable
)
);
} }
#[test] #[test]
@ -289,8 +327,13 @@ fn test_ground_rel_duplicate_vars() {
let q = r#"[:find ?x :where [?x :foo/age ?v] [(ground [[8 10]]) [[?x ?x]]]]"#; let q = r#"[:find ?x :where [?x :foo/age ?v] [(ground [[8 10]]) [[?x ?x]]]]"#;
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
assert_eq!(bails(known, &q), assert_eq!(
AlgebrizerError::InvalidBinding(PlainSymbol::plain("ground"), BindingError::RepeatedBoundVariable)); bails(known, &q),
AlgebrizerError::InvalidBinding(
PlainSymbol::plain("ground"),
BindingError::RepeatedBoundVariable
)
);
} }
#[test] #[test]
@ -298,8 +341,10 @@ fn test_ground_nonexistent_variable_invalid() {
let q = r#"[:find ?x ?e :where [?e _ ?x] (not [(ground 17) ?v])]"#; let q = r#"[:find ?x ?e :where [?e _ ?x] (not [(ground 17) ?v])]"#;
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
assert_eq!(bails(known, &q), assert_eq!(
AlgebrizerError::UnboundVariable(PlainSymbol::plain("?v"))); bails(known, &q),
AlgebrizerError::UnboundVariable(PlainSymbol::plain("?v"))
);
} }
#[test] #[test]
@ -315,6 +360,8 @@ fn test_unbound_input_variable_invalid() {
let i = QueryInputs::new(types, BTreeMap::default()).expect("valid QueryInputs"); let i = QueryInputs::new(types, BTreeMap::default()).expect("valid QueryInputs");
assert_eq!(bails_with_inputs(known, &q, i), assert_eq!(
AlgebrizerError::UnboundVariable(PlainSymbol::plain("?x"))); bails_with_inputs(known, &q, i),
AlgebrizerError::UnboundVariable(PlainSymbol::plain("?x"))
);
} }

View file

@ -8,71 +8,58 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate core_traits;
extern crate edn; extern crate edn;
extern crate mentat_core; extern crate mentat_core;
extern crate core_traits;
extern crate mentat_query_algebrizer; extern crate mentat_query_algebrizer;
extern crate query_algebrizer_traits; extern crate query_algebrizer_traits;
mod utils; mod utils;
use core_traits::{ use core_traits::{Attribute, TypedValue, ValueType, ValueTypeSet};
Attribute,
ValueType,
TypedValue,
ValueTypeSet,
};
use mentat_core::{ use mentat_core::{DateTime, Schema, Utc};
DateTime,
Schema,
Utc,
};
use edn::query::{ use edn::query::{Keyword, PlainSymbol, Variable};
Keyword,
PlainSymbol,
Variable,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::AlgebrizerError;
AlgebrizerError,
};
use mentat_query_algebrizer::{ use mentat_query_algebrizer::{EmptyBecause, Known, QueryInputs};
EmptyBecause,
Known,
QueryInputs,
};
use utils::{ use utils::{add_attribute, alg, alg_with_inputs, associate_ident, bails};
add_attribute,
alg,
alg_with_inputs,
associate_ident,
bails,
};
fn prepopulated_schema() -> Schema { fn prepopulated_schema() -> Schema {
let mut schema = Schema::default(); let mut schema = Schema::default();
associate_ident(&mut schema, Keyword::namespaced("foo", "date"), 65); associate_ident(&mut schema, Keyword::namespaced("foo", "date"), 65);
associate_ident(&mut schema, Keyword::namespaced("foo", "double"), 66); associate_ident(&mut schema, Keyword::namespaced("foo", "double"), 66);
associate_ident(&mut schema, Keyword::namespaced("foo", "long"), 67); associate_ident(&mut schema, Keyword::namespaced("foo", "long"), 67);
add_attribute(&mut schema, 65, Attribute { add_attribute(
value_type: ValueType::Instant, &mut schema,
multival: false, 65,
..Default::default() Attribute {
}); value_type: ValueType::Instant,
add_attribute(&mut schema, 66, Attribute { multival: false,
value_type: ValueType::Double, ..Default::default()
multival: false, },
..Default::default() );
}); add_attribute(
add_attribute(&mut schema, 67, Attribute { &mut schema,
value_type: ValueType::Long, 66,
multival: false, Attribute {
..Default::default() value_type: ValueType::Double,
}); multival: false,
..Default::default()
},
);
add_attribute(
&mut schema,
67,
Attribute {
value_type: ValueType::Long,
multival: false,
..Default::default()
},
);
schema schema
} }
@ -86,21 +73,27 @@ fn test_instant_predicates_require_instants() {
:where :where
[?e :foo/date ?t] [?e :foo/date ?t]
[(> ?t "2017-06-16T00:56:41.257Z")]]"#; [(> ?t "2017-06-16T00:56:41.257Z")]]"#;
assert_eq!(bails(known, query), assert_eq!(
bails(known, query),
AlgebrizerError::InvalidArgumentType( AlgebrizerError::InvalidArgumentType(
PlainSymbol::plain(">"), PlainSymbol::plain(">"),
ValueTypeSet::of_numeric_and_instant_types(), ValueTypeSet::of_numeric_and_instant_types(),
1)); 1
)
);
let query = r#"[:find ?e let query = r#"[:find ?e
:where :where
[?e :foo/date ?t] [?e :foo/date ?t]
[(> "2017-06-16T00:56:41.257Z", ?t)]]"#; [(> "2017-06-16T00:56:41.257Z", ?t)]]"#;
assert_eq!(bails(known, query), assert_eq!(
bails(known, query),
AlgebrizerError::InvalidArgumentType( AlgebrizerError::InvalidArgumentType(
PlainSymbol::plain(">"), PlainSymbol::plain(">"),
ValueTypeSet::of_numeric_and_instant_types(), ValueTypeSet::of_numeric_and_instant_types(),
0)); // We get this right. 0
)
); // We get this right.
// You can try using a number, which is valid input to a numeric predicate. // You can try using a number, which is valid input to a numeric predicate.
// In this store and query, though, that means we expect `?t` to be both // In this store and query, though, that means we expect `?t` to be both
@ -111,12 +104,14 @@ fn test_instant_predicates_require_instants() {
[(> ?t 1234512345)]]"#; [(> ?t 1234512345)]]"#;
let cc = alg(known, query); let cc = alg(known, query);
assert!(cc.is_known_empty()); assert!(cc.is_known_empty());
assert_eq!(cc.empty_because.unwrap(), assert_eq!(
EmptyBecause::TypeMismatch { cc.empty_because.unwrap(),
var: Variable::from_valid_name("?t"), EmptyBecause::TypeMismatch {
existing: ValueTypeSet::of_one(ValueType::Instant), var: Variable::from_valid_name("?t"),
desired: ValueTypeSet::of_numeric_types(), existing: ValueTypeSet::of_one(ValueType::Instant),
}); desired: ValueTypeSet::of_numeric_types(),
}
);
// You can compare doubles to longs. // You can compare doubles to longs.
let query = r#"[:find ?e let query = r#"[:find ?e
@ -125,8 +120,11 @@ fn test_instant_predicates_require_instants() {
[(< ?t 1234512345)]]"#; [(< ?t 1234512345)]]"#;
let cc = alg(known, query); let cc = alg(known, query);
assert!(!cc.is_known_empty()); assert!(!cc.is_known_empty());
assert_eq!(cc.known_type(&Variable::from_valid_name("?t")).expect("?t is known"), assert_eq!(
ValueType::Double); cc.known_type(&Variable::from_valid_name("?t"))
.expect("?t is known"),
ValueType::Double
);
} }
#[test] #[test]
@ -135,27 +133,41 @@ fn test_instant_predicates_accepts_var() {
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let instant_var = Variable::from_valid_name("?time"); let instant_var = Variable::from_valid_name("?time");
let instant_value = TypedValue::Instant(DateTime::parse_from_rfc3339("2018-04-11T19:17:00.000Z") let instant_value = TypedValue::Instant(
.map(|t| t.with_timezone(&Utc)) DateTime::parse_from_rfc3339("2018-04-11T19:17:00.000Z")
.expect("expected valid date")); .map(|t| t.with_timezone(&Utc))
.expect("expected valid date"),
);
let query = r#"[:find ?e let query = r#"[:find ?e
:in ?time :in ?time
:where :where
[?e :foo/date ?t] [?e :foo/date ?t]
[(< ?t ?time)]]"#; [(< ?t ?time)]]"#;
let cc = alg_with_inputs(known, query, QueryInputs::with_value_sequence(vec![(instant_var.clone(), instant_value.clone())])); let cc = alg_with_inputs(
assert_eq!(cc.known_type(&instant_var).expect("?time is known"), known,
ValueType::Instant); query,
QueryInputs::with_value_sequence(vec![(instant_var.clone(), instant_value.clone())]),
);
assert_eq!(
cc.known_type(&instant_var).expect("?time is known"),
ValueType::Instant
);
let query = r#"[:find ?e let query = r#"[:find ?e
:in ?time :in ?time
:where :where
[?e :foo/date ?t] [?e :foo/date ?t]
[(> ?time, ?t)]]"#; [(> ?time, ?t)]]"#;
let cc = alg_with_inputs(known, query, QueryInputs::with_value_sequence(vec![(instant_var.clone(), instant_value.clone())])); let cc = alg_with_inputs(
assert_eq!(cc.known_type(&instant_var).expect("?time is known"), known,
ValueType::Instant); query,
QueryInputs::with_value_sequence(vec![(instant_var.clone(), instant_value.clone())]),
);
assert_eq!(
cc.known_type(&instant_var).expect("?time is known"),
ValueType::Instant
);
} }
#[test] #[test]
@ -172,16 +184,28 @@ fn test_numeric_predicates_accepts_var() {
:where :where
[?e :foo/long ?t] [?e :foo/long ?t]
[(> ?t ?long)]]"#; [(> ?t ?long)]]"#;
let cc = alg_with_inputs(known, query, QueryInputs::with_value_sequence(vec![(numeric_var.clone(), numeric_value.clone())])); let cc = alg_with_inputs(
assert_eq!(cc.known_type(&numeric_var).expect("?long is known"), known,
ValueType::Long); query,
QueryInputs::with_value_sequence(vec![(numeric_var.clone(), numeric_value.clone())]),
);
assert_eq!(
cc.known_type(&numeric_var).expect("?long is known"),
ValueType::Long
);
let query = r#"[:find ?e let query = r#"[:find ?e
:in ?long :in ?long
:where :where
[?e :foo/long ?t] [?e :foo/long ?t]
[(> ?long, ?t)]]"#; [(> ?long, ?t)]]"#;
let cc = alg_with_inputs(known, query, QueryInputs::with_value_sequence(vec![(numeric_var.clone(), numeric_value.clone())])); let cc = alg_with_inputs(
assert_eq!(cc.known_type(&numeric_var).expect("?long is known"), known,
ValueType::Long); query,
QueryInputs::with_value_sequence(vec![(numeric_var.clone(), numeric_value.clone())]),
);
assert_eq!(
cc.known_type(&numeric_var).expect("?long is known"),
ValueType::Long
);
} }

View file

@ -8,27 +8,19 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate edn;
extern crate core_traits; extern crate core_traits;
extern crate edn;
extern crate mentat_core; extern crate mentat_core;
extern crate mentat_query_algebrizer; extern crate mentat_query_algebrizer;
extern crate query_algebrizer_traits; extern crate query_algebrizer_traits;
mod utils; mod utils;
use utils::{ use utils::{alg, bails, SchemaBuilder};
alg,
SchemaBuilder,
bails,
};
use core_traits::{ use core_traits::ValueType;
ValueType,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use mentat_query_algebrizer::Known; use mentat_query_algebrizer::Known;
@ -51,13 +43,21 @@ fn test_empty_known() {
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
for known_type in ValueType::all_enums().iter() { for known_type in ValueType::all_enums().iter() {
for required in ValueType::all_enums().iter() { for required in ValueType::all_enums().iter() {
let q = format!("[:find ?e :where [?e :test/{} ?v] [(type ?v {})]]", let q = format!(
known_type.into_keyword().name(), required); "[:find ?e :where [?e :test/{} ?v] [(type ?v {})]]",
known_type.into_keyword().name(),
required
);
println!("Query: {}", q); println!("Query: {}", q);
let cc = alg(known, &q); let cc = alg(known, &q);
// It should only be empty if the known type and our requirement differ. // It should only be empty if the known type and our requirement differ.
assert_eq!(cc.empty_because.is_some(), known_type != required, assert_eq!(
"known_type = {}; required = {}", known_type, required); cc.empty_because.is_some(),
known_type != required,
"known_type = {}; required = {}",
known_type,
required
);
} }
} }
} }

View file

@ -13,31 +13,16 @@
// this module will get warnings otherwise). // this module will get warnings otherwise).
#![allow(dead_code)] #![allow(dead_code)]
use core_traits::{ use core_traits::{Attribute, Entid, ValueType};
Attribute,
Entid,
ValueType,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::query::{ use edn::query::Keyword;
Keyword,
};
use query_algebrizer_traits::errors::{ use query_algebrizer_traits::errors::AlgebrizerError;
AlgebrizerError,
};
use mentat_query_algebrizer::{ use mentat_query_algebrizer::{
ConjoiningClauses, algebrize, algebrize_with_inputs, parse_find_string, ConjoiningClauses, Known, QueryInputs,
Known,
QueryInputs,
algebrize,
algebrize_with_inputs,
parse_find_string,
}; };
// Common utility functions used in multiple test files. // Common utility functions used in multiple test files.
@ -61,7 +46,7 @@ impl SchemaBuilder {
pub fn new() -> SchemaBuilder { pub fn new() -> SchemaBuilder {
SchemaBuilder { SchemaBuilder {
schema: Schema::default(), schema: Schema::default(),
counter: 65 counter: 65,
} }
} }
@ -72,18 +57,24 @@ impl SchemaBuilder {
self self
} }
pub fn define_simple_attr<T>(self, pub fn define_simple_attr<T>(
keyword_ns: T, self,
keyword_name: T, keyword_ns: T,
value_type: ValueType, keyword_name: T,
multival: bool) -> Self value_type: ValueType,
where T: AsRef<str> multival: bool,
) -> Self
where
T: AsRef<str>,
{ {
self.define_attr(Keyword::namespaced(keyword_ns, keyword_name), Attribute { self.define_attr(
value_type, Keyword::namespaced(keyword_ns, keyword_name),
multival, Attribute {
..Default::default() value_type,
}) multival,
..Default::default()
},
)
} }
} }
@ -99,10 +90,14 @@ pub fn bails_with_inputs(known: Known, input: &str, inputs: QueryInputs) -> Alge
pub fn alg(known: Known, input: &str) -> ConjoiningClauses { pub fn alg(known: Known, input: &str) -> ConjoiningClauses {
let parsed = parse_find_string(input).expect("query input to have parsed"); let parsed = parse_find_string(input).expect("query input to have parsed");
algebrize(known, parsed).expect("algebrizing to have succeeded").cc algebrize(known, parsed)
.expect("algebrizing to have succeeded")
.cc
} }
pub fn alg_with_inputs(known: Known, input: &str, inputs: QueryInputs) -> ConjoiningClauses { pub fn alg_with_inputs(known: Known, input: &str, inputs: QueryInputs) -> ConjoiningClauses {
let parsed = parse_find_string(input).expect("query input to have parsed"); let parsed = parse_find_string(input).expect("query input to have parsed");
algebrize_with_inputs(known, parsed, 0, inputs).expect("algebrizing to have succeeded").cc algebrize_with_inputs(known, parsed, 0, inputs)
.expect("algebrizing to have succeeded")
.cc
} }

View file

@ -15,7 +15,7 @@ failure = "0.1.1"
failure_derive = "0.1.1" failure_derive = "0.1.1"
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.13" version = "0.21"
features = ["limits"] features = ["limits"]
[dependencies.edn] [dependencies.edn]

View file

@ -8,34 +8,15 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use core_traits::{ use core_traits::{ValueType, ValueTypeSet};
ValueType,
ValueTypeSet,
};
use edn::query::{ use edn::query::{Aggregate, QueryFunction, Variable};
Aggregate,
QueryFunction,
Variable,
};
use mentat_query_algebrizer::{ use mentat_query_algebrizer::{ColumnName, ConjoiningClauses, VariableColumn};
ColumnName,
ConjoiningClauses,
VariableColumn,
};
use mentat_query_sql::{ use mentat_query_sql::{ColumnOrExpression, Expression, Name, ProjectedColumn};
ColumnOrExpression,
Expression,
Name,
ProjectedColumn,
};
use errors::{ use errors::{ProjectorError, Result};
ProjectorError,
Result,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SimpleAggregationOp { pub enum SimpleAggregationOp {
@ -92,9 +73,12 @@ impl SimpleAggregationOp {
// The mean of a set of numeric values will always, for our purposes, be a double. // The mean of a set of numeric values will always, for our purposes, be a double.
Ok(ValueType::Double) Ok(ValueType::Double)
} else { } else {
bail!(ProjectorError::CannotApplyAggregateOperationToTypes(*self, possibilities)) bail!(ProjectorError::CannotApplyAggregateOperationToTypes(
*self,
possibilities
))
} }
}, }
&Sum => { &Sum => {
if possibilities.is_only_numeric() { if possibilities.is_only_numeric() {
if possibilities.contains(ValueType::Double) { if possibilities.contains(ValueType::Double) {
@ -104,9 +88,12 @@ impl SimpleAggregationOp {
Ok(ValueType::Long) Ok(ValueType::Long)
} }
} else { } else {
bail!(ProjectorError::CannotApplyAggregateOperationToTypes(*self, possibilities)) bail!(ProjectorError::CannotApplyAggregateOperationToTypes(
*self,
possibilities
))
} }
}, }
&Max | &Min => { &Max | &Min => {
if possibilities.is_unit() { if possibilities.is_unit() {
@ -124,8 +111,11 @@ impl SimpleAggregationOp {
// These types are unordered. // These types are unordered.
Keyword | Ref | Uuid => { Keyword | Ref | Uuid => {
bail!(ProjectorError::CannotApplyAggregateOperationToTypes(*self, possibilities)) bail!(ProjectorError::CannotApplyAggregateOperationToTypes(
}, *self,
possibilities
))
}
} }
} else { } else {
// It cannot be empty -- we checked. // It cannot be empty -- we checked.
@ -139,10 +129,13 @@ impl SimpleAggregationOp {
Ok(ValueType::Long) Ok(ValueType::Long)
} }
} else { } else {
bail!(ProjectorError::CannotApplyAggregateOperationToTypes(*self, possibilities)) bail!(ProjectorError::CannotApplyAggregateOperationToTypes(
*self,
possibilities
))
} }
} }
}, }
} }
} }
} }
@ -184,10 +177,10 @@ impl SimpleAggregation for Aggregate {
if self.args.len() != 1 { if self.args.len() != 1 {
return None; return None;
} }
self.args[0] self.args[0].as_variable().and_then(|v| {
.as_variable() SimpleAggregationOp::for_function(&self.func)
.and_then(|v| SimpleAggregationOp::for_function(&self.func) .map(|op| SimpleAggregate { op, var: v.clone() })
.map(|op| SimpleAggregate { op, var: v.clone(), })) })
} }
} }
@ -195,39 +188,44 @@ impl SimpleAggregation for Aggregate {
/// - The `ColumnOrExpression` to use in the query. This will always refer to other /// - The `ColumnOrExpression` to use in the query. This will always refer to other
/// variables by name; never to a datoms column. /// variables by name; never to a datoms column.
/// - The known type of that value. /// - The known type of that value.
pub fn projected_column_for_simple_aggregate(simple: &SimpleAggregate, cc: &ConjoiningClauses) -> Result<(ProjectedColumn, ValueType)> { pub fn projected_column_for_simple_aggregate(
simple: &SimpleAggregate,
cc: &ConjoiningClauses,
) -> Result<(ProjectedColumn, ValueType)> {
let known_types = cc.known_type_set(&simple.var); let known_types = cc.known_type_set(&simple.var);
let return_type = simple.op.is_applicable_to_types(known_types)?; let return_type = simple.op.is_applicable_to_types(known_types)?;
let projected_column_or_expression = let projected_column_or_expression = if let Some(value) = cc.bound_value(&simple.var) {
if let Some(value) = cc.bound_value(&simple.var) { // Oh, we already know the value!
// Oh, we already know the value! if simple.use_static_value() {
if simple.use_static_value() { // We can statically compute the aggregate result for some operators -- not count or
// We can statically compute the aggregate result for some operators -- not count or // sum, but avg/max/min are OK.
// sum, but avg/max/min are OK. ColumnOrExpression::Value(value)
ColumnOrExpression::Value(value)
} else {
let expression = Expression::Unary {
sql_op: simple.op.to_sql(),
arg: ColumnOrExpression::Value(value),
};
if simple.is_nullable() {
ColumnOrExpression::NullableAggregate(Box::new(expression), return_type)
} else {
ColumnOrExpression::Expression(Box::new(expression), return_type)
}
}
} else { } else {
// The common case: the values are bound during execution.
let name = VariableColumn::Variable(simple.var.clone()).column_name();
let expression = Expression::Unary { let expression = Expression::Unary {
sql_op: simple.op.to_sql(), sql_op: simple.op.to_sql(),
arg: ColumnOrExpression::ExistingColumn(name), arg: ColumnOrExpression::Value(value),
}; };
if simple.is_nullable() { if simple.is_nullable() {
ColumnOrExpression::NullableAggregate(Box::new(expression), return_type) ColumnOrExpression::NullableAggregate(Box::new(expression), return_type)
} else { } else {
ColumnOrExpression::Expression(Box::new(expression), return_type) ColumnOrExpression::Expression(Box::new(expression), return_type)
} }
}
} else {
// The common case: the values are bound during execution.
let name = VariableColumn::Variable(simple.var.clone()).column_name();
let expression = Expression::Unary {
sql_op: simple.op.to_sql(),
arg: ColumnOrExpression::ExistingColumn(name),
}; };
Ok((ProjectedColumn(projected_column_or_expression, simple.column_name()), return_type)) if simple.is_nullable() {
ColumnOrExpression::NullableAggregate(Box::new(expression), return_type)
} else {
ColumnOrExpression::Expression(Box::new(expression), return_type)
}
};
Ok((
ProjectedColumn(projected_column_or_expression, simple.column_name()),
return_type,
))
} }

View file

@ -12,20 +12,12 @@ use std; // To refer to std::result::Result.
use rusqlite; use rusqlite;
use core_traits::{ use core_traits::ValueTypeSet;
ValueTypeSet,
};
use db_traits::errors::DbError; use db_traits::errors::DbError;
use edn::query::{ use edn::query::PlainSymbol;
PlainSymbol, use query_pull_traits::errors::PullError;
};
use query_pull_traits::errors::{
PullError,
};
use aggregates::{ use aggregates::SimpleAggregationOp;
SimpleAggregationOp,
};
pub type Result<T> = std::result::Result<T, ProjectorError>; pub type Result<T> = std::result::Result<T, ProjectorError>;
@ -39,7 +31,10 @@ pub enum ProjectorError {
#[fail(display = "no possible types for value provided to {:?}", _0)] #[fail(display = "no possible types for value provided to {:?}", _0)]
CannotProjectImpossibleBinding(SimpleAggregationOp), CannotProjectImpossibleBinding(SimpleAggregationOp),
#[fail(display = "cannot apply projection operation {:?} to types {:?}", _0, _1)] #[fail(
display = "cannot apply projection operation {:?} to types {:?}",
_0, _1
)]
CannotApplyAggregateOperationToTypes(SimpleAggregationOp, ValueTypeSet), CannotApplyAggregateOperationToTypes(SimpleAggregationOp, ValueTypeSet),
#[fail(display = "invalid projection: {}", _0)] #[fail(display = "invalid projection: {}", _0)]
@ -54,7 +49,10 @@ pub enum ProjectorError {
#[fail(display = "expected {}, got {}", _0, _1)] #[fail(display = "expected {}, got {}", _0, _1)]
UnexpectedResultsType(&'static str, &'static str), UnexpectedResultsType(&'static str, &'static str),
#[fail(display = "expected tuple of length {}, got tuple of length {}", _0, _1)] #[fail(
display = "expected tuple of length {}, got tuple of length {}",
_0, _1
)]
UnexpectedResultsTupleLength(usize, usize), UnexpectedResultsTupleLength(usize, usize),
#[fail(display = "min/max expressions: {} (max 1), corresponding: {}", _0, _1)] #[fail(display = "min/max expressions: {} (max 1), corresponding: {}", _0, _1)]

View file

@ -23,6 +23,5 @@ extern crate query_pull_traits;
extern crate mentat_query_algebrizer; extern crate mentat_query_algebrizer;
extern crate mentat_query_sql; extern crate mentat_query_sql;
pub mod errors;
pub mod aggregates; pub mod aggregates;
pub mod errors;

View file

@ -8,36 +8,22 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate core_traits;
extern crate edn; extern crate edn;
extern crate mentat_core; extern crate mentat_core;
extern crate core_traits;
extern crate mentat_query_algebrizer; extern crate mentat_query_algebrizer;
extern crate mentat_query_projector; extern crate mentat_query_projector;
extern crate query_projector_traits; extern crate query_projector_traits;
use core_traits::{ use core_traits::{Attribute, Entid, ValueType};
Attribute,
Entid,
ValueType,
};
use mentat_core::{ use mentat_core::Schema;
Schema,
};
use edn::query::{ use edn::query::Keyword;
Keyword,
};
use mentat_query_algebrizer::{ use mentat_query_algebrizer::{algebrize, parse_find_string, Known};
Known,
algebrize,
parse_find_string,
};
use mentat_query_projector::{ use mentat_query_projector::query_projection;
query_projection,
};
// These are helpers that tests use to build Schema instances. // These are helpers that tests use to build Schema instances.
fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) { fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) {
@ -54,21 +40,33 @@ fn prepopulated_schema() -> Schema {
associate_ident(&mut schema, Keyword::namespaced("foo", "name"), 65); associate_ident(&mut schema, Keyword::namespaced("foo", "name"), 65);
associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68); associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68);
associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69); associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69);
add_attribute(&mut schema, 65, Attribute { add_attribute(
value_type: ValueType::String, &mut schema,
multival: false, 65,
..Default::default() Attribute {
}); value_type: ValueType::String,
add_attribute(&mut schema, 68, Attribute { multival: false,
value_type: ValueType::Long, ..Default::default()
multival: false, },
..Default::default() );
}); add_attribute(
add_attribute(&mut schema, 69, Attribute { &mut schema,
value_type: ValueType::Long, 68,
multival: false, Attribute {
..Default::default() value_type: ValueType::Long,
}); multival: false,
..Default::default()
},
);
add_attribute(
&mut schema,
69,
Attribute {
value_type: ValueType::Long,
multival: false,
..Default::default()
},
);
schema schema
} }
@ -103,13 +101,11 @@ fn test_the_without_max_or_min() {
// … when we look at the projection list, we cannot reconcile the types. // … when we look at the projection list, we cannot reconcile the types.
let projection = query_projection(&schema, &algebrized); let projection = query_projection(&schema, &algebrized);
assert!(projection.is_err()); assert!(projection.is_err());
use query_projector_traits::errors::{ use query_projector_traits::errors::ProjectorError;
ProjectorError,
};
match projection.err().expect("expected failure") { match projection.err().expect("expected failure") {
ProjectorError::InvalidProjection(s) => { ProjectorError::InvalidProjection(s) => {
assert_eq!(s.as_str(), "Warning: used `the` without `min` or `max`."); assert_eq!(s.as_str(), "Warning: used `the` without `min` or `max`.");
}, }
_ => panic!(), _ => panic!(),
} }
} }

View file

@ -11,7 +11,7 @@ failure = "0.1.1"
indexmap = "1" indexmap = "1"
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.13" version = "0.21"
features = ["limits"] features = ["limits"]
[dependencies.core_traits] [dependencies.core_traits]

View file

@ -8,14 +8,9 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use core_traits::{ use core_traits::Binding;
Binding,
};
use query_projector_traits::errors::{ use query_projector_traits::errors::{ProjectorError, Result};
ProjectorError,
Result,
};
/// A `BindingTuple` is any type that can accommodate a Mentat tuple query result of fixed length. /// A `BindingTuple` is any type that can accommodate a Mentat tuple query result of fixed length.
/// ///
@ -32,11 +27,14 @@ impl BindingTuple for Vec<Binding> {
None => Ok(None), None => Ok(None),
Some(vec) => { Some(vec) => {
if expected != vec.len() { if expected != vec.len() {
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) Err(ProjectorError::UnexpectedResultsTupleLength(
expected,
vec.len(),
))
} else { } else {
Ok(Some(vec)) Ok(Some(vec))
} }
}, }
} }
} }
} }
@ -51,7 +49,10 @@ impl BindingTuple for (Binding,) {
None => Ok(None), None => Ok(None),
Some(vec) => { Some(vec) => {
if expected != vec.len() { if expected != vec.len() {
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) Err(ProjectorError::UnexpectedResultsTupleLength(
expected,
vec.len(),
))
} else { } else {
let mut iter = vec.into_iter(); let mut iter = vec.into_iter();
Ok(Some((iter.next().unwrap(),))) Ok(Some((iter.next().unwrap(),)))
@ -70,7 +71,10 @@ impl BindingTuple for (Binding, Binding) {
None => Ok(None), None => Ok(None),
Some(vec) => { Some(vec) => {
if expected != vec.len() { if expected != vec.len() {
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) Err(ProjectorError::UnexpectedResultsTupleLength(
expected,
vec.len(),
))
} else { } else {
let mut iter = vec.into_iter(); let mut iter = vec.into_iter();
Ok(Some((iter.next().unwrap(), iter.next().unwrap()))) Ok(Some((iter.next().unwrap(), iter.next().unwrap())))
@ -89,10 +93,17 @@ impl BindingTuple for (Binding, Binding, Binding) {
None => Ok(None), None => Ok(None),
Some(vec) => { Some(vec) => {
if expected != vec.len() { if expected != vec.len() {
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) Err(ProjectorError::UnexpectedResultsTupleLength(
expected,
vec.len(),
))
} else { } else {
let mut iter = vec.into_iter(); let mut iter = vec.into_iter();
Ok(Some((iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap()))) Ok(Some((
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
)))
} }
} }
} }
@ -108,10 +119,18 @@ impl BindingTuple for (Binding, Binding, Binding, Binding) {
None => Ok(None), None => Ok(None),
Some(vec) => { Some(vec) => {
if expected != vec.len() { if expected != vec.len() {
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) Err(ProjectorError::UnexpectedResultsTupleLength(
expected,
vec.len(),
))
} else { } else {
let mut iter = vec.into_iter(); let mut iter = vec.into_iter();
Ok(Some((iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap()))) Ok(Some((
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
)))
} }
} }
} }
@ -127,10 +146,19 @@ impl BindingTuple for (Binding, Binding, Binding, Binding, Binding) {
None => Ok(None), None => Ok(None),
Some(vec) => { Some(vec) => {
if expected != vec.len() { if expected != vec.len() {
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) Err(ProjectorError::UnexpectedResultsTupleLength(
expected,
vec.len(),
))
} else { } else {
let mut iter = vec.into_iter(); let mut iter = vec.into_iter();
Ok(Some((iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap()))) Ok(Some((
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
)))
} }
} }
} }
@ -148,10 +176,20 @@ impl BindingTuple for (Binding, Binding, Binding, Binding, Binding, Binding) {
None => Ok(None), None => Ok(None),
Some(vec) => { Some(vec) => {
if expected != vec.len() { if expected != vec.len() {
Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) Err(ProjectorError::UnexpectedResultsTupleLength(
expected,
vec.len(),
))
} else { } else {
let mut iter = vec.into_iter(); let mut iter = vec.into_iter();
Ok(Some((iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap()))) Ok(Some((
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
)))
} }
} }
} }

View file

@ -13,111 +13,63 @@ extern crate failure;
extern crate indexmap; extern crate indexmap;
extern crate rusqlite; extern crate rusqlite;
extern crate db_traits;
extern crate edn; extern crate edn;
extern crate mentat_core; extern crate mentat_core;
extern crate db_traits;
#[macro_use] #[macro_use]
extern crate core_traits; extern crate core_traits;
extern crate mentat_db; // For value conversion. extern crate mentat_db; // For value conversion.
extern crate mentat_query_algebrizer; extern crate mentat_query_algebrizer;
extern crate mentat_query_pull; extern crate mentat_query_pull;
extern crate query_pull_traits;
extern crate query_projector_traits;
extern crate mentat_query_sql; extern crate mentat_query_sql;
extern crate query_projector_traits;
extern crate query_pull_traits;
use std::collections::{ use std::collections::BTreeSet;
BTreeSet,
};
use std::iter; use std::iter;
use std::rc::Rc; use std::rc::Rc;
use rusqlite::{ use rusqlite::{Row, Rows};
Row,
Rows,
};
use core_traits::{ use core_traits::{Binding, TypedValue};
Binding,
TypedValue,
};
use mentat_core::{ use mentat_core::{Schema, ValueTypeTag};
Schema,
ValueTypeTag,
};
use mentat_core::util::{ use mentat_core::util::Either;
Either,
};
use mentat_db::{ use mentat_db::TypedSQLValue;
TypedSQLValue,
};
use edn::query::{ use edn::query::{Element, FindSpec, Limit, Variable};
Element,
FindSpec,
Limit,
Variable,
};
use mentat_query_algebrizer::{ use mentat_query_algebrizer::{AlgebraicQuery, VariableBindings};
AlgebraicQuery,
VariableBindings,
};
use mentat_query_sql::{ use mentat_query_sql::{GroupBy, Projection};
GroupBy,
Projection,
};
pub mod translate; pub mod translate;
mod binding_tuple; mod binding_tuple;
pub use binding_tuple::{ pub use binding_tuple::BindingTuple;
BindingTuple,
};
mod project; mod project;
mod projectors; mod projectors;
mod pull; mod pull;
mod relresult; mod relresult;
use project::{ use project::{project_elements, ProjectedElements};
ProjectedElements,
project_elements,
};
pub use project::{ pub use project::projected_column_for_var;
projected_column_for_var,
};
pub use projectors::{ pub use projectors::{ConstantProjector, Projector};
ConstantProjector,
Projector,
};
use projectors::{ use projectors::{
CollProjector, CollProjector, CollTwoStagePullProjector, RelProjector, RelTwoStagePullProjector,
CollTwoStagePullProjector, ScalarProjector, ScalarTwoStagePullProjector, TupleProjector, TupleTwoStagePullProjector,
RelProjector,
RelTwoStagePullProjector,
ScalarProjector,
ScalarTwoStagePullProjector,
TupleProjector,
TupleTwoStagePullProjector,
}; };
pub use relresult::{ pub use relresult::{RelResult, StructuredRelResult};
RelResult,
StructuredRelResult,
};
use query_projector_traits::errors::{ use query_projector_traits::errors::{ProjectorError, Result};
ProjectorError,
Result,
};
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct QueryOutput { pub struct QueryOutput {
@ -140,16 +92,16 @@ impl From<QueryOutput> for QueryResults {
} }
impl QueryOutput { impl QueryOutput {
pub fn empty_factory(spec: &FindSpec) -> Box<Fn() -> QueryResults> { pub fn empty_factory(spec: &FindSpec) -> Box<dyn Fn() -> QueryResults> {
use self::FindSpec::*; use self::FindSpec::*;
match spec { match spec {
&FindScalar(_) => Box::new(|| QueryResults::Scalar(None)), &FindScalar(_) => Box::new(|| QueryResults::Scalar(None)),
&FindTuple(_) => Box::new(|| QueryResults::Tuple(None)), &FindTuple(_) => Box::new(|| QueryResults::Tuple(None)),
&FindColl(_) => Box::new(|| QueryResults::Coll(vec![])), &FindColl(_) => Box::new(|| QueryResults::Coll(vec![])),
&FindRel(ref es) => { &FindRel(ref es) => {
let width = es.len(); let width = es.len();
Box::new(move || QueryResults::Rel(RelResult::empty(width))) Box::new(move || QueryResults::Rel(RelResult::empty(width)))
}, }
} }
} }
@ -163,13 +115,12 @@ impl QueryOutput {
pub fn empty(spec: &Rc<FindSpec>) -> QueryOutput { pub fn empty(spec: &Rc<FindSpec>) -> QueryOutput {
use self::FindSpec::*; use self::FindSpec::*;
let results = let results = match &**spec {
match &**spec { &FindScalar(_) => QueryResults::Scalar(None),
&FindScalar(_) => QueryResults::Scalar(None), &FindTuple(_) => QueryResults::Tuple(None),
&FindTuple(_) => QueryResults::Tuple(None), &FindColl(_) => QueryResults::Coll(vec![]),
&FindColl(_) => QueryResults::Coll(vec![]), &FindRel(ref es) => QueryResults::Rel(RelResult::empty(es.len())),
&FindRel(ref es) => QueryResults::Rel(RelResult::empty(es.len())), };
};
QueryOutput { QueryOutput {
spec: spec.clone(), spec: spec.clone(),
results: results, results: results,
@ -179,85 +130,83 @@ impl QueryOutput {
pub fn from_constants(spec: &Rc<FindSpec>, bindings: VariableBindings) -> QueryResults { pub fn from_constants(spec: &Rc<FindSpec>, bindings: VariableBindings) -> QueryResults {
use self::FindSpec::*; use self::FindSpec::*;
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) let val = bindings.get(var).cloned().map(|v| v.into());
.cloned()
.map(|v| v.into());
QueryResults::Scalar(val) QueryResults::Scalar(val)
}, }
&FindScalar(Element::Aggregate(ref _agg)) => { &FindScalar(Element::Aggregate(ref _agg)) => {
// TODO: static aggregates. // TODO: static aggregates.
unimplemented!(); unimplemented!();
}, }
&FindScalar(Element::Pull(ref _pull)) => { &FindScalar(Element::Pull(ref _pull)) => {
// TODO: static pull. // TODO: static pull.
unimplemented!(); unimplemented!();
}, }
&FindTuple(ref elements) => { &FindTuple(ref elements) => {
let values = elements.iter() let values = elements
.map(|e| match e { .iter()
&Element::Variable(ref var) | .map(|e| match e {
&Element::Corresponding(ref var) => { &Element::Variable(ref var) | &Element::Corresponding(ref var) => bindings
bindings.get(var) .get(var)
.cloned() .cloned()
.expect("every var to have a binding") .expect("every var to have a binding")
.into() .into(),
}, &Element::Pull(ref _pull) => {
&Element::Pull(ref _pull) => { // TODO: static pull.
// TODO: static pull. unreachable!();
unreachable!(); }
}, &Element::Aggregate(ref _agg) => {
&Element::Aggregate(ref _agg) => { // TODO: static computation of aggregates, then
// TODO: static computation of aggregates, then // implement the condition in `is_fully_bound`.
// implement the condition in `is_fully_bound`. unreachable!();
unreachable!(); }
}, })
}) .collect();
.collect();
QueryResults::Tuple(Some(values)) QueryResults::Tuple(Some(values))
}, }
&FindColl(Element::Variable(ref var)) | &FindColl(Element::Variable(ref var)) | &FindColl(Element::Corresponding(ref var)) => {
&FindColl(Element::Corresponding(ref var)) => { let val = bindings
let val = bindings.get(var) .get(var)
.cloned() .cloned()
.expect("every var to have a binding") .expect("every var to have a binding")
.into(); .into();
QueryResults::Coll(vec![val]) QueryResults::Coll(vec![val])
}, }
&FindColl(Element::Pull(ref _pull)) => { &FindColl(Element::Pull(ref _pull)) => {
// TODO: static pull. // TODO: static pull.
unimplemented!(); unimplemented!();
}, }
&FindColl(Element::Aggregate(ref _agg)) => { &FindColl(Element::Aggregate(ref _agg)) => {
// Does it even make sense to write // Does it even make sense to write
// [:find [(max ?x) ...] :where [_ :foo/bar ?x]] // [:find [(max ?x) ...] :where [_ :foo/bar ?x]]
// ? // ?
// TODO // TODO
unimplemented!(); unimplemented!();
}, }
&FindRel(ref elements) => { &FindRel(ref elements) => {
let width = elements.len(); let width = elements.len();
let values = elements.iter().map(|e| match e { let values = elements
&Element::Variable(ref var) | .iter()
&Element::Corresponding(ref var) => { .map(|e| match e {
bindings.get(var) &Element::Variable(ref var) | &Element::Corresponding(ref var) => bindings
.cloned() .get(var)
.expect("every var to have a binding") .cloned()
.into() .expect("every var to have a binding")
}, .into(),
&Element::Pull(ref _pull) => { &Element::Pull(ref _pull) => {
// TODO: static pull. // TODO: static pull.
unreachable!(); unreachable!();
}, }
&Element::Aggregate(ref _agg) => { &Element::Aggregate(ref _agg) => {
// TODO: static computation of aggregates, then // TODO: static computation of aggregates, then
// implement the condition in `is_fully_bound`. // implement the condition in `is_fully_bound`.
unreachable!(); unreachable!();
}, }
}).collect(); })
.collect();
QueryResults::Rel(RelResult { width, values }) QueryResults::Rel(RelResult { width, values })
}, }
} }
} }
@ -275,9 +224,14 @@ impl QueryOutput {
/// ///
/// This is the moral equivalent of `collect` (and `BindingTuple` of `FromIterator`), but /// This is the moral equivalent of `collect` (and `BindingTuple` of `FromIterator`), but
/// specialized to tuples of expected length. /// specialized to tuples of expected length.
pub fn into_tuple<B>(self) -> Result<Option<B>> where B: BindingTuple { pub fn into_tuple<B>(self) -> Result<Option<B>>
where
B: BindingTuple,
{
let expected = self.spec.expected_column_count(); let expected = self.spec.expected_column_count();
self.results.into_tuple().and_then(|vec| B::from_binding_vec(expected, vec)) self.results
.into_tuple()
.and_then(|vec| B::from_binding_vec(expected, vec))
} }
pub fn into_rel(self) -> Result<RelResult<Binding>> { pub fn into_rel(self) -> Result<RelResult<Binding>> {
@ -289,10 +243,22 @@ impl QueryResults {
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
use QueryResults::*; use QueryResults::*;
match self { match self {
&Scalar(ref o) => if o.is_some() { 1 } else { 0 }, &Scalar(ref o) => {
&Tuple(ref o) => if o.is_some() { 1 } else { 0 }, if o.is_some() {
&Coll(ref v) => v.len(), 1
&Rel(ref r) => r.row_count(), } else {
0
}
}
&Tuple(ref o) => {
if o.is_some() {
1
} else {
0
}
}
&Coll(ref v) => v.len(),
&Rel(ref r) => r.row_count(),
} }
} }
@ -300,9 +266,9 @@ impl QueryResults {
use QueryResults::*; use QueryResults::*;
match self { match self {
&Scalar(ref o) => o.is_none(), &Scalar(ref o) => o.is_none(),
&Tuple(ref o) => o.is_none(), &Tuple(ref o) => o.is_none(),
&Coll(ref v) => v.is_empty(), &Coll(ref v) => v.is_empty(),
&Rel(ref r) => r.is_empty(), &Rel(ref r) => r.is_empty(),
} }
} }
@ -310,14 +276,18 @@ impl QueryResults {
match self { match self {
QueryResults::Scalar(o) => Ok(o), QueryResults::Scalar(o) => Ok(o),
QueryResults::Coll(_) => bail!(ProjectorError::UnexpectedResultsType("coll", "scalar")), QueryResults::Coll(_) => bail!(ProjectorError::UnexpectedResultsType("coll", "scalar")),
QueryResults::Tuple(_) => bail!(ProjectorError::UnexpectedResultsType("tuple", "scalar")), QueryResults::Tuple(_) => {
bail!(ProjectorError::UnexpectedResultsType("tuple", "scalar"))
}
QueryResults::Rel(_) => bail!(ProjectorError::UnexpectedResultsType("rel", "scalar")), QueryResults::Rel(_) => bail!(ProjectorError::UnexpectedResultsType("rel", "scalar")),
} }
} }
pub fn into_coll(self) -> Result<Vec<Binding>> { pub fn into_coll(self) -> Result<Vec<Binding>> {
match self { match self {
QueryResults::Scalar(_) => bail!(ProjectorError::UnexpectedResultsType("scalar", "coll")), QueryResults::Scalar(_) => {
bail!(ProjectorError::UnexpectedResultsType("scalar", "coll"))
}
QueryResults::Coll(c) => Ok(c), QueryResults::Coll(c) => Ok(c),
QueryResults::Tuple(_) => bail!(ProjectorError::UnexpectedResultsType("tuple", "coll")), QueryResults::Tuple(_) => bail!(ProjectorError::UnexpectedResultsType("tuple", "coll")),
QueryResults::Rel(_) => bail!(ProjectorError::UnexpectedResultsType("rel", "coll")), QueryResults::Rel(_) => bail!(ProjectorError::UnexpectedResultsType("rel", "coll")),
@ -326,7 +296,9 @@ impl QueryResults {
pub fn into_tuple(self) -> Result<Option<Vec<Binding>>> { pub fn into_tuple(self) -> Result<Option<Vec<Binding>>> {
match self { match self {
QueryResults::Scalar(_) => bail!(ProjectorError::UnexpectedResultsType("scalar", "tuple")), QueryResults::Scalar(_) => {
bail!(ProjectorError::UnexpectedResultsType("scalar", "tuple"))
}
QueryResults::Coll(_) => bail!(ProjectorError::UnexpectedResultsType("coll", "tuple")), QueryResults::Coll(_) => bail!(ProjectorError::UnexpectedResultsType("coll", "tuple")),
QueryResults::Tuple(t) => Ok(t), QueryResults::Tuple(t) => Ok(t),
QueryResults::Rel(_) => bail!(ProjectorError::UnexpectedResultsType("rel", "tuple")), QueryResults::Rel(_) => bail!(ProjectorError::UnexpectedResultsType("rel", "tuple")),
@ -335,7 +307,9 @@ impl QueryResults {
pub fn into_rel(self) -> Result<RelResult<Binding>> { pub fn into_rel(self) -> Result<RelResult<Binding>> {
match self { match self {
QueryResults::Scalar(_) => bail!(ProjectorError::UnexpectedResultsType("scalar", "rel")), QueryResults::Scalar(_) => {
bail!(ProjectorError::UnexpectedResultsType("scalar", "rel"))
}
QueryResults::Coll(_) => bail!(ProjectorError::UnexpectedResultsType("coll", "rel")), QueryResults::Coll(_) => bail!(ProjectorError::UnexpectedResultsType("coll", "rel")),
QueryResults::Tuple(_) => bail!(ProjectorError::UnexpectedResultsType("tuple", "rel")), QueryResults::Tuple(_) => bail!(ProjectorError::UnexpectedResultsType("tuple", "rel")),
QueryResults::Rel(r) => Ok(r), QueryResults::Rel(r) => Ok(r),
@ -343,7 +317,7 @@ impl QueryResults {
} }
} }
type Index = i32; // See rusqlite::RowIndex. type Index = usize; // See rusqlite::RowIndex.
enum TypedIndex { enum TypedIndex {
Known(Index, ValueTypeTag), Known(Index, ValueTypeTag),
Unknown(Index, Index), Unknown(Index, Index),
@ -364,28 +338,27 @@ 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<Binding> { fn lookup<'a>(&self, row: &Row<'a>) -> 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).unwrap();
TypedValue::from_sql_value_pair(v, value_type) TypedValue::from_sql_value_pair(v, value_type)
.map(|v| v.into()) .map(|v| v.into())
.map_err(|e| e.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).unwrap();
let value_type_tag: i32 = row.get(type_index); let value_type_tag: i32 = row.get(type_index).unwrap();
TypedValue::from_sql_value_pair(v, value_type_tag) TypedValue::from_sql_value_pair(v, value_type_tag)
.map(|v| v.into()) .map(|v| v.into())
.map_err(|e| e.into()) .map_err(|e| e.into())
}, }
} }
} }
} }
/// Combines the things you need to turn a query into SQL and turn its results into /// Combines the things you need to turn a query into SQL and turn its results into
/// `QueryResults`: SQL-related projection information (`DISTINCT`, columns, etc.) and /// `QueryResults`: SQL-related projection information (`DISTINCT`, columns, etc.) and
/// a Datalog projector that turns SQL into structures. /// a Datalog projector that turns SQL into structures.
@ -406,7 +379,7 @@ pub struct CombinedProjection {
/// A Datalog projection. This consumes rows of the appropriate shape (as defined by /// A Datalog projection. This consumes rows of the appropriate shape (as defined by
/// the SQL projection) to yield one of the four kinds of Datalog query result. /// the SQL projection) to yield one of the four kinds of Datalog query result.
pub datalog_projector: Box<Projector>, pub datalog_projector: Box<dyn Projector>,
/// True if this query requires the SQL query to include DISTINCT. /// True if this query requires the SQL query to include DISTINCT.
pub distinct: bool, pub distinct: bool,
@ -445,35 +418,38 @@ impl IsPull for Element {
/// - The bindings established by the topmost CC. /// - The bindings established by the topmost CC.
/// - The types known at algebrizing time. /// - The types known at algebrizing time.
/// - The types extracted from the store for unknown attributes. /// - The types extracted from the store for unknown attributes.
pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result<Either<ConstantProjector, CombinedProjection>> { pub fn query_projection(
schema: &Schema,
query: &AlgebraicQuery,
) -> Result<Either<ConstantProjector, CombinedProjection>> {
use self::FindSpec::*; use self::FindSpec::*;
let spec = query.find_spec.clone(); let spec = query.find_spec.clone();
if query.is_fully_unit_bound() { if query.is_fully_unit_bound() {
// Do a few gyrations to produce empty results of the right kind for the query. // Do a few gyrations to produce empty results of the right kind for the query.
let variables: BTreeSet<Variable> = spec.columns() let variables: BTreeSet<Variable> = spec
.map(|e| match e { .columns()
&Element::Variable(ref var) | .map(|e| match e {
&Element::Corresponding(ref var) => var.clone(), &Element::Variable(ref var) | &Element::Corresponding(ref var) => var.clone(),
// Pull expressions can never be fully bound. // Pull expressions can never be fully bound.
// TODO: but the interior can be, in which case we // TODO: but the interior can be, in which case we
// can handle this and simply project. // can handle this and simply project.
&Element::Pull(_) => { &Element::Pull(_) => {
unreachable!(); unreachable!();
}, }
&Element::Aggregate(ref _agg) => { &Element::Aggregate(ref _agg) => {
// TODO: static computation of aggregates, then // TODO: static computation of aggregates, then
// implement the condition in `is_fully_bound`. // implement the condition in `is_fully_bound`.
unreachable!(); unreachable!();
}, }
}) })
.collect(); .collect();
// TODO: error handling // TODO: error handling
let results = QueryOutput::from_constants(&spec, query.cc.value_bindings(&variables)); let results = QueryOutput::from_constants(&spec, query.cc.value_bindings(&variables));
let f = Box::new(move || { results.clone() }); let f = Box::new(move || results.clone());
Ok(Either::Left(ConstantProjector::new(spec, f))) Ok(Either::Left(ConstantProjector::new(spec, f)))
} else if query.is_known_empty() { } else if query.is_known_empty() {
@ -488,8 +464,9 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result<Eithe
CollTwoStagePullProjector::combine(spec, elements) CollTwoStagePullProjector::combine(spec, elements)
} else { } else {
CollProjector::combine(spec, elements) CollProjector::combine(spec, elements)
}.map(|p| p.flip_distinct_for_limit(&query.limit)) }
}, .map(|p| p.flip_distinct_for_limit(&query.limit))
}
FindScalar(ref element) => { FindScalar(ref element) => {
let elements = project_elements(1, iter::once(element), query)?; let elements = project_elements(1, iter::once(element), query)?;
@ -498,7 +475,7 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result<Eithe
} else { } else {
ScalarProjector::combine(spec, elements) ScalarProjector::combine(spec, elements)
} }
}, }
FindRel(ref elements) => { FindRel(ref elements) => {
let is_pull = elements.iter().any(|e| e.is_pull()); let is_pull = elements.iter().any(|e| e.is_pull());
@ -508,8 +485,9 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result<Eithe
RelTwoStagePullProjector::combine(spec, column_count, elements) RelTwoStagePullProjector::combine(spec, column_count, elements)
} else { } else {
RelProjector::combine(spec, column_count, elements) RelProjector::combine(spec, column_count, elements)
}.map(|p| p.flip_distinct_for_limit(&query.limit)) }
}, .map(|p| p.flip_distinct_for_limit(&query.limit))
}
FindTuple(ref elements) => { FindTuple(ref elements) => {
let is_pull = elements.iter().any(|e| e.is_pull()); let is_pull = elements.iter().any(|e| e.is_pull());
@ -520,41 +498,51 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result<Eithe
} else { } else {
TupleProjector::combine(spec, column_count, elements) TupleProjector::combine(spec, column_count, elements)
} }
}, }
}.map(Either::Right) }
.map(Either::Right)
} }
} }
#[test] #[test]
fn test_into_tuple() { fn test_into_tuple() {
let query_output = QueryOutput { let query_output = QueryOutput {
spec: Rc::new(FindSpec::FindTuple(vec![Element::Variable(Variable::from_valid_name("?x")), spec: Rc::new(FindSpec::FindTuple(vec![
Element::Variable(Variable::from_valid_name("?y"))])), Element::Variable(Variable::from_valid_name("?x")),
results: QueryResults::Tuple(Some(vec![Binding::Scalar(TypedValue::Long(0)), Element::Variable(Variable::from_valid_name("?y")),
Binding::Scalar(TypedValue::Long(2))])), ])),
results: QueryResults::Tuple(Some(vec![
Binding::Scalar(TypedValue::Long(0)),
Binding::Scalar(TypedValue::Long(2)),
])),
}; };
assert_eq!(query_output.clone().into_tuple().expect("into_tuple"), assert_eq!(
Some((Binding::Scalar(TypedValue::Long(0)), query_output.clone().into_tuple().expect("into_tuple"),
Binding::Scalar(TypedValue::Long(2))))); Some((
Binding::Scalar(TypedValue::Long(0)),
Binding::Scalar(TypedValue::Long(2))
))
);
match query_output.clone().into_tuple() { match query_output.clone().into_tuple() {
Err(ProjectorError::UnexpectedResultsTupleLength(expected, got)) => { Err(ProjectorError::UnexpectedResultsTupleLength(expected, got)) => {
assert_eq!((expected, got), (3, 2)); assert_eq!((expected, got), (3, 2));
}, }
// This forces the result type. // This forces the result type.
Ok(Some((_, _, _))) | _ => panic!("expected error"), Ok(Some((_, _, _))) | _ => panic!("expected error"),
} }
let query_output = QueryOutput { let query_output = QueryOutput {
spec: Rc::new(FindSpec::FindTuple(vec![Element::Variable(Variable::from_valid_name("?x")), spec: Rc::new(FindSpec::FindTuple(vec![
Element::Variable(Variable::from_valid_name("?y"))])), Element::Variable(Variable::from_valid_name("?x")),
Element::Variable(Variable::from_valid_name("?y")),
])),
results: QueryResults::Tuple(None), results: QueryResults::Tuple(None),
}; };
match query_output.clone().into_tuple() { match query_output.clone().into_tuple() {
Ok(None) => {}, Ok(None) => {}
// This forces the result type. // This forces the result type.
Ok(Some((_, _))) | _ => panic!("expected error"), Ok(Some((_, _))) | _ => panic!("expected error"),
} }
@ -562,7 +550,7 @@ fn test_into_tuple() {
match query_output.clone().into_tuple() { match query_output.clone().into_tuple() {
Err(ProjectorError::UnexpectedResultsTupleLength(expected, got)) => { Err(ProjectorError::UnexpectedResultsTupleLength(expected, got)) => {
assert_eq!((expected, got), (3, 2)); assert_eq!((expected, got), (3, 2));
}, }
// This forces the result type. // This forces the result type.
Ok(Some((_, _, _))) | _ => panic!("expected error"), Ok(Some((_, _, _))) | _ => panic!("expected error"),
} }

View file

@ -8,74 +8,35 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use std::collections::{ use std::collections::BTreeSet;
BTreeSet,
};
use indexmap::{ use indexmap::IndexSet;
IndexSet,
};
use core_traits::{ use core_traits::ValueTypeSet;
ValueTypeSet,
};
use mentat_core::{ use mentat_core::{SQLValueType, SQLValueTypeSet};
SQLValueType,
SQLValueTypeSet,
};
use mentat_core::util::{ use mentat_core::util::Either;
Either,
};
use edn::query::{ use edn::query::{Element, Pull, Variable};
Element,
Pull,
Variable,
};
use mentat_query_algebrizer::{ use mentat_query_algebrizer::{
AlgebraicQuery, AlgebraicQuery, ColumnName, ConjoiningClauses, QualifiedAlias, VariableColumn,
ColumnName,
ConjoiningClauses,
QualifiedAlias,
VariableColumn,
}; };
use mentat_query_sql::{ColumnOrExpression, GroupBy, Name, ProjectedColumn, Projection};
use mentat_query_sql::{
ColumnOrExpression,
GroupBy,
Name,
Projection,
ProjectedColumn,
};
use query_projector_traits::aggregates::{ use query_projector_traits::aggregates::{
SimpleAggregation, projected_column_for_simple_aggregate, SimpleAggregation,
projected_column_for_simple_aggregate,
}; };
use query_projector_traits::errors::{ use query_projector_traits::errors::{ProjectorError, Result};
ProjectorError,
Result,
};
use projectors::{ use projectors::Projector;
Projector,
};
use pull::{ use pull::{PullIndices, PullOperation, PullTemplate};
PullIndices,
PullOperation,
PullTemplate,
};
use super::{ use super::{CombinedProjection, TypedIndex};
CombinedProjection,
TypedIndex,
};
/// An internal temporary struct to pass between the projection 'walk' and the /// An internal temporary struct to pass between the projection 'walk' and the
/// resultant projector. /// resultant projector.
@ -97,7 +58,11 @@ pub(crate) struct ProjectedElements {
} }
impl ProjectedElements { impl ProjectedElements {
pub(crate) fn combine(self, projector: Box<Projector>, distinct: bool) -> Result<CombinedProjection> { pub(crate) fn combine(
self,
projector: Box<dyn Projector>,
distinct: bool,
) -> Result<CombinedProjection> {
Ok(CombinedProjection { Ok(CombinedProjection {
sql_projection: self.sql_projection, sql_projection: self.sql_projection,
pre_aggregate_projection: self.pre_aggregate_projection, pre_aggregate_projection: self.pre_aggregate_projection,
@ -122,44 +87,52 @@ impl ProjectedElements {
} }
} }
fn candidate_type_column(cc: &ConjoiningClauses, var: &Variable) -> Result<(ColumnOrExpression, Name)> { fn candidate_type_column(
cc: &ConjoiningClauses,
var: &Variable,
) -> Result<(ColumnOrExpression, Name)> {
cc.extracted_types cc.extracted_types
.get(var) .get(var)
.cloned() .cloned()
.map(|alias| { .map(|alias| {
let type_name = VariableColumn::VariableTypeTag(var.clone()).column_name(); let type_name = VariableColumn::VariableTypeTag(var.clone()).column_name();
(ColumnOrExpression::Column(alias), type_name) (ColumnOrExpression::Column(alias), type_name)
}) })
.ok_or_else(|| ProjectorError::UnboundVariable(var.name()).into()) .ok_or_else(|| ProjectorError::UnboundVariable(var.name()).into())
} }
fn cc_column(cc: &ConjoiningClauses, var: &Variable) -> Result<QualifiedAlias> { fn cc_column(cc: &ConjoiningClauses, var: &Variable) -> Result<QualifiedAlias> {
cc.column_bindings cc.column_bindings
.get(var) .get(var)
.and_then(|cols| cols.get(0).cloned()) .and_then(|cols| cols.get(0).cloned())
.ok_or_else(|| ProjectorError::UnboundVariable(var.name()).into()) .ok_or_else(|| ProjectorError::UnboundVariable(var.name()).into())
} }
fn candidate_column(cc: &ConjoiningClauses, var: &Variable) -> Result<(ColumnOrExpression, Name)> { fn candidate_column(cc: &ConjoiningClauses, var: &Variable) -> Result<(ColumnOrExpression, Name)> {
// Every variable should be bound by the top-level CC to at least // Every variable should be bound by the top-level CC to at least
// one column in the query. If that constraint is violated it's a // one column in the query. If that constraint is violated it's a
// bug in our code, so it's appropriate to panic here. // bug in our code, so it's appropriate to panic here.
cc_column(cc, var) cc_column(cc, var).map(|qa| {
.map(|qa| { let name = VariableColumn::Variable(var.clone()).column_name();
let name = VariableColumn::Variable(var.clone()).column_name(); (ColumnOrExpression::Column(qa), name)
(ColumnOrExpression::Column(qa), name) })
})
} }
/// Return the projected column -- that is, a value or SQL column and an associated name -- for a /// Return the projected column -- that is, a value or SQL column and an associated name -- for a
/// given variable. Also return the type. /// given variable. Also return the type.
/// Callers are expected to determine whether to project a type tag as an additional SQL column. /// Callers are expected to determine whether to project a type tag as an additional SQL column.
pub fn projected_column_for_var(var: &Variable, cc: &ConjoiningClauses) -> Result<(ProjectedColumn, ValueTypeSet)> { pub fn projected_column_for_var(
var: &Variable,
cc: &ConjoiningClauses,
) -> Result<(ProjectedColumn, ValueTypeSet)> {
if let Some(value) = cc.bound_value(&var) { if let Some(value) = cc.bound_value(&var) {
// If we already know the value, then our lives are easy. // If we already know the value, then our lives are easy.
let tag = value.value_type(); let tag = value.value_type();
let name = VariableColumn::Variable(var.clone()).column_name(); let name = VariableColumn::Variable(var.clone()).column_name();
Ok((ProjectedColumn(ColumnOrExpression::Value(value.clone()), name), ValueTypeSet::of_one(tag))) Ok((
ProjectedColumn(ColumnOrExpression::Value(value.clone()), name),
ValueTypeSet::of_one(tag),
))
} else { } else {
// If we don't, then the CC *must* have bound the variable. // If we don't, then the CC *must* have bound the variable.
let (column, name) = candidate_column(cc, var)?; let (column, name) = candidate_column(cc, var)?;
@ -182,8 +155,8 @@ pub fn projected_column_for_var(var: &Variable, cc: &ConjoiningClauses) -> Resul
pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>( pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
count: usize, count: usize,
elements: I, elements: I,
query: &AlgebraicQuery) -> Result<ProjectedElements> { query: &AlgebraicQuery,
) -> Result<ProjectedElements> {
// Give a little padding for type tags. // Give a little padding for type tags.
let mut inner_projection = Vec::with_capacity(count + 2); let mut inner_projection = Vec::with_capacity(count + 2);
@ -192,7 +165,7 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
// We'll expand them later. // We'll expand them later.
let mut outer_projection: Vec<Either<Name, ProjectedColumn>> = Vec::with_capacity(count + 2); let mut outer_projection: Vec<Either<Name, ProjectedColumn>> = Vec::with_capacity(count + 2);
let mut i: i32 = 0; let mut i: usize = 0;
let mut min_max_count: usize = 0; let mut min_max_count: usize = 0;
let mut templates = vec![]; let mut templates = vec![];
let mut pulls: Vec<PullTemplate> = vec![]; let mut pulls: Vec<PullTemplate> = vec![];
@ -214,24 +187,34 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
match e { match e {
&Element::Variable(ref var) => { &Element::Variable(ref var) => {
if outer_variables.contains(var) { if outer_variables.contains(var) {
bail!(ProjectorError::InvalidProjection(format!("Duplicate variable {} in query.", var))); bail!(ProjectorError::InvalidProjection(format!(
"Duplicate variable {} in query.",
var
)));
} }
if corresponded_variables.contains(var) { if corresponded_variables.contains(var) {
bail!(ProjectorError::InvalidProjection(format!("Can't project both {} and `(the {})` from a query.", var, var))); bail!(ProjectorError::InvalidProjection(format!(
"Can't project both {} and `(the {})` from a query.",
var, var
)));
} }
}, }
&Element::Corresponding(ref var) => { &Element::Corresponding(ref var) => {
if outer_variables.contains(var) { if outer_variables.contains(var) {
bail!(ProjectorError::InvalidProjection(format!("Can't project both {} and `(the {})` from a query.", var, var))); bail!(ProjectorError::InvalidProjection(format!(
"Can't project both {} and `(the {})` from a query.",
var, var
)));
} }
if corresponded_variables.contains(var) { if corresponded_variables.contains(var) {
bail!(ProjectorError::InvalidProjection(format!("`(the {})` appears twice in query.", var))); bail!(ProjectorError::InvalidProjection(format!(
"`(the {})` appears twice in query.",
var
)));
} }
}, }
&Element::Aggregate(_) => { &Element::Aggregate(_) => {}
}, &Element::Pull(_) => {}
&Element::Pull(_) => {
},
}; };
// Record variables -- `(the ?x)` and `?x` are different in this regard, because we don't want // Record variables -- `(the ?x)` and `?x` are different in this regard, because we don't want
@ -239,19 +222,21 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
match e { match e {
&Element::Variable(ref var) => { &Element::Variable(ref var) => {
outer_variables.insert(var.clone()); outer_variables.insert(var.clone());
}, }
&Element::Corresponding(ref var) => { &Element::Corresponding(ref var) => {
// We will project these later; don't put them in `outer_variables` // We will project these later; don't put them in `outer_variables`
// so we know not to group them. // so we know not to group them.
corresponded_variables.insert(var.clone()); corresponded_variables.insert(var.clone());
}, }
&Element::Pull(Pull { ref var, patterns: _ }) => { &Element::Pull(Pull {
ref var,
patterns: _,
}) => {
// We treat `pull` as an ordinary variable extraction, // We treat `pull` as an ordinary variable extraction,
// and we expand it later. // and we expand it later.
outer_variables.insert(var.clone()); outer_variables.insert(var.clone());
}, }
&Element::Aggregate(_) => { &Element::Aggregate(_) => {}
},
}; };
// Now do the main processing of each element. // Now do the main processing of each element.
@ -259,8 +244,7 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
// Each time we come across a variable, we push a SQL column // Each time we come across a variable, we push a SQL column
// into the SQL projection, aliased to the name of the variable, // into the SQL projection, aliased to the name of the variable,
// and we push an annotated index into the projector. // and we push an annotated index into the projector.
&Element::Variable(ref var) | &Element::Variable(ref var) | &Element::Corresponding(ref var) => {
&Element::Corresponding(ref var) => {
inner_variables.insert(var.clone()); inner_variables.insert(var.clone());
let (projected_column, type_set) = projected_column_for_var(&var, &query.cc)?; let (projected_column, type_set) = projected_column_for_var(&var, &query.cc)?;
@ -269,18 +253,21 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
if let Some(tag) = type_set.unique_type_tag() { if let Some(tag) = type_set.unique_type_tag() {
templates.push(TypedIndex::Known(i, tag)); templates.push(TypedIndex::Known(i, tag));
i += 1; // We used one SQL column. i += 1; // We used one SQL column.
} else { } else {
templates.push(TypedIndex::Unknown(i, i + 1)); templates.push(TypedIndex::Unknown(i, i + 1));
i += 2; // We used two SQL columns. i += 2; // We used two SQL columns.
// Also project the type from the SQL query. // Also project the type from the SQL query.
let (type_column, type_name) = candidate_type_column(&query.cc, &var)?; let (type_column, type_name) = candidate_type_column(&query.cc, &var)?;
inner_projection.push(ProjectedColumn(type_column, type_name.clone())); inner_projection.push(ProjectedColumn(type_column, type_name.clone()));
outer_projection.push(Either::Left(type_name)); outer_projection.push(Either::Left(type_name));
} }
}, }
&Element::Pull(Pull { ref var, ref patterns }) => { &Element::Pull(Pull {
ref var,
ref patterns,
}) => {
inner_variables.insert(var.clone()); inner_variables.insert(var.clone());
let (projected_column, type_set) = projected_column_for_var(&var, &query.cc)?; let (projected_column, type_set) = projected_column_for_var(&var, &query.cc)?;
@ -303,12 +290,12 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
}, },
op: PullOperation((*patterns).clone()), op: PullOperation((*patterns).clone()),
}); });
i += 1; // We used one SQL column. i += 1; // We used one SQL column.
} else { } else {
// This should be impossible: (pull ?x) implies that ?x is a ref. // This should be impossible: (pull ?x) implies that ?x is a ref.
unreachable!(); unreachable!();
} }
}, }
&Element::Aggregate(ref a) => { &Element::Aggregate(ref a) => {
if let Some(simple) = a.to_simple() { if let Some(simple) = a.to_simple() {
aggregates = true; aggregates = true;
@ -317,7 +304,7 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
match simple.op { match simple.op {
Max | Min => { Max | Min => {
min_max_count += 1; min_max_count += 1;
}, }
Avg | Count | Sum => (), Avg | Count | Sum => (),
} }
@ -330,16 +317,24 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
// - The type set must be appropriate for the operation. E.g., `Sum` is not a // - The type set must be appropriate for the operation. E.g., `Sum` is not a
// meaningful operation on instants. // meaningful operation on instants.
let (projected_column, return_type) = projected_column_for_simple_aggregate(&simple, &query.cc)?; let (projected_column, return_type) =
projected_column_for_simple_aggregate(&simple, &query.cc)?;
outer_projection.push(Either::Right(projected_column)); outer_projection.push(Either::Right(projected_column));
if !inner_variables.contains(&simple.var) { if !inner_variables.contains(&simple.var) {
inner_variables.insert(simple.var.clone()); inner_variables.insert(simple.var.clone());
let (projected_column, _type_set) = projected_column_for_var(&simple.var, &query.cc)?; let (projected_column, _type_set) =
projected_column_for_var(&simple.var, &query.cc)?;
inner_projection.push(projected_column); inner_projection.push(projected_column);
if query.cc.known_type_set(&simple.var).unique_type_tag().is_none() { if query
.cc
.known_type_set(&simple.var)
.unique_type_tag()
.is_none()
{
// Also project the type from the SQL query. // Also project the type from the SQL query.
let (type_column, type_name) = candidate_type_column(&query.cc, &simple.var)?; let (type_column, type_name) =
candidate_type_column(&query.cc, &simple.var)?;
inner_projection.push(ProjectedColumn(type_column, type_name.clone())); inner_projection.push(ProjectedColumn(type_column, type_name.clone()));
} }
} }
@ -349,23 +344,27 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
i += 1; i += 1;
} else { } else {
// TODO: complex aggregates. // TODO: complex aggregates.
bail!(ProjectorError::NotYetImplemented("complex aggregates".into())); bail!(ProjectorError::NotYetImplemented(
"complex aggregates".into()
));
} }
}, }
} }
} }
match (min_max_count, corresponded_variables.len()) { match (min_max_count, corresponded_variables.len()) {
(0, 0) | (_, 0) => {}, (0, 0) | (_, 0) => {}
(0, _) => { (0, _) => {
bail!(ProjectorError::InvalidProjection("Warning: used `the` without `min` or `max`.".to_string())); bail!(ProjectorError::InvalidProjection(
}, "Warning: used `the` without `min` or `max`.".to_string()
));
}
(1, _) => { (1, _) => {
// This is the success case! // This is the success case!
}, }
(n, c) => { (n, c) => {
bail!(ProjectorError::AmbiguousAggregates(n, c)); bail!(ProjectorError::AmbiguousAggregates(n, c));
}, }
} }
// Anything used in ORDER BY (which we're given in `named_projection`) // Anything used in ORDER BY (which we're given in `named_projection`)
@ -412,12 +411,12 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
if !aggregates { if !aggregates {
// We're done -- we never need to group unless we're aggregating. // We're done -- we never need to group unless we're aggregating.
return Ok(ProjectedElements { return Ok(ProjectedElements {
sql_projection: Projection::Columns(inner_projection), sql_projection: Projection::Columns(inner_projection),
pre_aggregate_projection: None, pre_aggregate_projection: None,
templates, templates,
pulls, pulls,
group_by: vec![], group_by: vec![],
}); });
} }
// OK, on to aggregates. // OK, on to aggregates.
@ -439,7 +438,9 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
let mut group_by = Vec::with_capacity(outer_variables.len() + 2); let mut group_by = Vec::with_capacity(outer_variables.len() + 2);
let vars = outer_variables.into_iter().zip(::std::iter::repeat(true)); let vars = outer_variables.into_iter().zip(::std::iter::repeat(true));
let corresponds = corresponded_variables.into_iter().zip(::std::iter::repeat(false)); let corresponds = corresponded_variables
.into_iter()
.zip(::std::iter::repeat(false));
for (var, group) in vars.chain(corresponds) { for (var, group) in vars.chain(corresponds) {
if query.cc.is_value_bound(&var) { if query.cc.is_value_bound(&var) {
@ -464,12 +465,13 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
if needs_type_projection { if needs_type_projection {
let type_name = VariableColumn::VariableTypeTag(var.clone()).column_name(); let type_name = VariableColumn::VariableTypeTag(var.clone()).column_name();
if !already_inner { if !already_inner {
let type_col = query.cc let type_col = query.cc.extracted_types.get(&var).cloned().ok_or_else(|| {
.extracted_types ProjectorError::NoTypeAvailableForVariable(var.name().clone())
.get(&var) })?;
.cloned() inner_projection.push(ProjectedColumn(
.ok_or_else(|| ProjectorError::NoTypeAvailableForVariable(var.name().clone()))?; ColumnOrExpression::Column(type_col),
inner_projection.push(ProjectedColumn(ColumnOrExpression::Column(type_col), type_name.clone())); type_name.clone(),
));
} }
if group { if group {
group_by.push(GroupBy::ProjectedColumn(type_name)); group_by.push(GroupBy::ProjectedColumn(type_name));
@ -505,15 +507,15 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
// `ground` and known values?) // `ground` and known values?)
// Walk the projection, switching the outer columns to use the inner names. // Walk the projection, switching the outer columns to use the inner names.
let outer_projection = outer_projection.into_iter().map(|c| { let outer_projection = outer_projection
match c { .into_iter()
.map(|c| match c {
Either::Left(name) => { Either::Left(name) => {
ProjectedColumn(ColumnOrExpression::ExistingColumn(name.clone()), ProjectedColumn(ColumnOrExpression::ExistingColumn(name.clone()), name)
name) }
},
Either::Right(pc) => pc, Either::Right(pc) => pc,
} })
}).collect(); .collect();
Ok(ProjectedElements { Ok(ProjectedElements {
sql_projection: Projection::Columns(outer_projection), sql_projection: Projection::Columns(outer_projection),

View file

@ -10,33 +10,24 @@
use std::rc::Rc; use std::rc::Rc;
use ::{ use {rusqlite, Element, FindSpec, QueryOutput, QueryResults, Rows, Schema};
Element,
FindSpec,
QueryOutput,
QueryResults,
Rows,
Schema,
rusqlite,
};
use query_projector_traits::errors::{ use query_projector_traits::errors::Result;
Result,
};
use super::{ use super::Projector;
Projector,
};
/// A projector that produces a `QueryResult` containing fixed data. /// A projector that produces a `QueryResult` containing fixed data.
/// Takes a boxed function that should return an empty result set of the desired type. /// Takes a boxed function that should return an empty result set of the desired type.
pub struct ConstantProjector { pub struct ConstantProjector {
spec: Rc<FindSpec>, spec: Rc<FindSpec>,
results_factory: Box<Fn() -> QueryResults>, results_factory: Box<dyn Fn() -> QueryResults>,
} }
impl ConstantProjector { impl ConstantProjector {
pub fn new(spec: Rc<FindSpec>, results_factory: Box<Fn() -> QueryResults>) -> ConstantProjector { pub fn new(
spec: Rc<FindSpec>,
results_factory: Box<dyn Fn() -> QueryResults>,
) -> ConstantProjector {
ConstantProjector { ConstantProjector {
spec: spec, spec: spec,
results_factory: results_factory, results_factory: results_factory,
@ -56,11 +47,16 @@ impl ConstantProjector {
// TODO: a ConstantProjector with non-constant pull expressions. // TODO: a ConstantProjector with non-constant pull expressions.
impl Projector for ConstantProjector { impl Projector for ConstantProjector {
fn project<'stmt, 's>(&self, _schema: &Schema, _sqlite: &'s rusqlite::Connection, _rows: Rows<'stmt>) -> Result<QueryOutput> { fn project<'stmt, 's>(
&self,
_schema: &Schema,
_sqlite: &'s rusqlite::Connection,
_rows: Rows<'stmt>,
) -> Result<QueryOutput> {
self.project_without_rows() self.project_without_rows()
} }
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> { fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns() self.spec.columns()
} }
} }

View file

@ -8,39 +8,29 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use super::{ use super::{rusqlite, Element, QueryOutput, Rows, Schema};
Element,
Schema,
QueryOutput,
Rows,
rusqlite,
};
use query_projector_traits::errors::{ use query_projector_traits::errors::Result;
Result,
};
pub trait Projector { pub trait Projector {
fn project<'stmt, 's>(&self, schema: &Schema, sqlite: &'s rusqlite::Connection, rows: Rows<'stmt>) -> Result<QueryOutput>; fn project<'stmt, 's>(
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's>; &self,
schema: &Schema,
sqlite: &'s rusqlite::Connection,
rows: Rows<'stmt>,
) -> Result<QueryOutput>;
fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's>;
} }
mod constant; mod constant;
mod simple;
mod pull_two_stage; mod pull_two_stage;
mod simple;
pub use self::constant::ConstantProjector; pub use self::constant::ConstantProjector;
pub(crate) use self::simple::{ pub(crate) use self::simple::{CollProjector, RelProjector, ScalarProjector, TupleProjector};
CollProjector,
RelProjector,
ScalarProjector,
TupleProjector,
};
pub(crate) use self::pull_two_stage::{ pub(crate) use self::pull_two_stage::{
CollTwoStagePullProjector, CollTwoStagePullProjector, RelTwoStagePullProjector, ScalarTwoStagePullProjector,
RelTwoStagePullProjector,
ScalarTwoStagePullProjector,
TupleTwoStagePullProjector, TupleTwoStagePullProjector,
}; };

View file

@ -10,47 +10,22 @@
use std::rc::Rc; use std::rc::Rc;
use std::iter::{ use std::iter::once;
once,
use mentat_query_pull::Puller;
use core_traits::Entid;
use {
rusqlite, Binding, CombinedProjection, Element, FindSpec, ProjectedElements, QueryOutput,
QueryResults, RelResult, Row, Rows, Schema, TypedIndex,
}; };
use mentat_query_pull::{ use pull::{PullConsumer, PullOperation, PullTemplate};
Puller,
};
use core_traits::{ use query_projector_traits::errors::Result;
Entid,
};
use ::{ use super::Projector;
Binding,
CombinedProjection,
Element,
FindSpec,
ProjectedElements,
QueryOutput,
QueryResults,
RelResult,
Row,
Rows,
Schema,
TypedIndex,
rusqlite,
};
use ::pull::{
PullConsumer,
PullOperation,
PullTemplate,
};
use query_projector_traits::errors::{
Result,
};
use super::{
Projector,
};
pub(crate) struct ScalarTwoStagePullProjector { pub(crate) struct ScalarTwoStagePullProjector {
spec: Rc<FindSpec>, spec: Rc<FindSpec>,
@ -61,34 +36,53 @@ pub(crate) struct ScalarTwoStagePullProjector {
// The only output is the pull expression, and so we can directly supply the projected entity // The only output is the pull expression, and so we can directly supply the projected entity
// to the pull SQL. // to the pull SQL.
impl ScalarTwoStagePullProjector { impl ScalarTwoStagePullProjector {
fn with_template(schema: &Schema, spec: Rc<FindSpec>, pull: PullOperation) -> Result<ScalarTwoStagePullProjector> { fn with_template(
schema: &Schema,
spec: Rc<FindSpec>,
pull: PullOperation,
) -> Result<ScalarTwoStagePullProjector> {
Ok(ScalarTwoStagePullProjector { Ok(ScalarTwoStagePullProjector {
spec: spec, spec: spec,
puller: Puller::prepare(schema, pull.0.clone())?, puller: Puller::prepare(schema, pull.0.clone())?,
}) })
} }
pub(crate) fn combine(schema: &Schema, spec: Rc<FindSpec>, mut elements: ProjectedElements) -> Result<CombinedProjection> { pub(crate) fn combine(
schema: &Schema,
spec: Rc<FindSpec>,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let pull = elements.pulls.pop().expect("Expected a single pull"); let pull = elements.pulls.pop().expect("Expected a single pull");
let projector = Box::new(ScalarTwoStagePullProjector::with_template(schema, spec, pull.op)?); let projector = Box::new(ScalarTwoStagePullProjector::with_template(
schema, spec, pull.op,
)?);
let distinct = false; let distinct = false;
elements.combine(projector, distinct) elements.combine(projector, distinct)
} }
} }
impl Projector for ScalarTwoStagePullProjector { impl Projector for ScalarTwoStagePullProjector {
fn project<'stmt, 's>(&self, schema: &Schema, sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result<QueryOutput> { fn project<'stmt, 's>(
&self,
schema: &Schema,
sqlite: &'s rusqlite::Connection,
mut rows: Rows<'stmt>,
) -> Result<QueryOutput> {
// Scalar is pretty straightforward -- zero or one entity, do the pull directly. // Scalar is pretty straightforward -- zero or one entity, do the pull directly.
let results = let results = if let Some(r) = rows.next().unwrap() {
if let Some(r) = rows.next() { let row = r;
let row = r?; let entity: Entid = row.get(0).unwrap(); // This will always be 0 and a ref.
let entity: Entid = row.get(0); // This will always be 0 and a ref. let bindings = self.puller.pull(schema, sqlite, once(entity))?;
let bindings = self.puller.pull(schema, sqlite, once(entity))?; let m = Binding::Map(
let m = Binding::Map(bindings.get(&entity).cloned().unwrap_or_else(Default::default)); bindings
QueryResults::Scalar(Some(m)) .get(&entity)
} else { .cloned()
QueryResults::Scalar(None) .unwrap_or_else(Default::default),
}; );
QueryResults::Scalar(Some(m))
} else {
QueryResults::Scalar(None)
};
Ok(QueryOutput { Ok(QueryOutput {
spec: self.spec.clone(), spec: self.spec.clone(),
@ -96,7 +90,7 @@ impl Projector for ScalarTwoStagePullProjector {
}) })
} }
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> { fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns() self.spec.columns()
} }
} }
@ -110,7 +104,12 @@ pub(crate) struct TupleTwoStagePullProjector {
} }
impl TupleTwoStagePullProjector { impl TupleTwoStagePullProjector {
fn with_templates(spec: Rc<FindSpec>, len: usize, templates: Vec<TypedIndex>, pulls: Vec<PullTemplate>) -> TupleTwoStagePullProjector { fn with_templates(
spec: Rc<FindSpec>,
len: usize,
templates: Vec<TypedIndex>,
pulls: Vec<PullTemplate>,
) -> TupleTwoStagePullProjector {
TupleTwoStagePullProjector { TupleTwoStagePullProjector {
spec: spec, spec: spec,
len: len, len: len,
@ -120,65 +119,79 @@ impl TupleTwoStagePullProjector {
} }
// 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<Binding>> { fn collect_bindings<'a>(&self, row: &Row<'a>) -> 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.
assert!(row.column_count() >= self.len as i32); assert!(row.column_count() >= self.len);
self.templates self.templates
.iter() .iter()
.map(|ti| ti.lookup(&row)) .map(|ti| ti.lookup(row))
.collect::<Result<Vec<Binding>>>() .collect::<Result<Vec<Binding>>>()
} }
pub(crate) fn combine(spec: Rc<FindSpec>, column_count: usize, mut elements: ProjectedElements) -> Result<CombinedProjection> { pub(crate) fn combine(
let projector = Box::new(TupleTwoStagePullProjector::with_templates(spec, column_count, elements.take_templates(), elements.take_pulls())); spec: Rc<FindSpec>,
column_count: usize,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let projector = Box::new(TupleTwoStagePullProjector::with_templates(
spec,
column_count,
elements.take_templates(),
elements.take_pulls(),
));
let distinct = false; let distinct = false;
elements.combine(projector, distinct) elements.combine(projector, distinct)
} }
} }
impl Projector for TupleTwoStagePullProjector { impl Projector for TupleTwoStagePullProjector {
fn project<'stmt, 's>(&self, schema: &Schema, sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result<QueryOutput> { fn project<'stmt, 's>(
let results = &self,
if let Some(r) = rows.next() { schema: &Schema,
let row = r?; sqlite: &'s rusqlite::Connection,
mut rows: Rows<'stmt>,
) -> Result<QueryOutput> {
let results = if let Some(r) = rows.next().unwrap() {
let row = r;
// Keeping the compiler happy. // Keeping the compiler happy.
let pull_consumers: Result<Vec<PullConsumer>> = self.pulls let pull_consumers: Result<Vec<PullConsumer>> = self
.iter() .pulls
.map(|op| PullConsumer::for_template(schema, op)) .iter()
.collect(); .map(|op| PullConsumer::for_template(schema, op))
let mut pull_consumers = pull_consumers?; .collect();
let mut pull_consumers = pull_consumers?;
// Collect the usual bindings and accumulate entity IDs for pull. // Collect the usual bindings and accumulate entity IDs for pull.
for mut p in pull_consumers.iter_mut() { for p in pull_consumers.iter_mut() {
p.collect_entity(&row); p.collect_entity(&row);
} }
let mut bindings = self.collect_bindings(row)?; let mut bindings = self.collect_bindings(row)?;
// Run the pull expressions for the collected IDs. // Run the pull expressions for the collected IDs.
for mut p in pull_consumers.iter_mut() { for p in pull_consumers.iter_mut() {
p.pull(sqlite)?; p.pull(sqlite)?;
} }
// Expand the pull expressions back into the results vector. // Expand the pull expressions back into the results vector.
for p in pull_consumers.into_iter() { for p in pull_consumers.into_iter() {
p.expand(&mut bindings); p.expand(&mut bindings);
} }
QueryResults::Tuple(Some(bindings)) QueryResults::Tuple(Some(bindings))
} else { } else {
QueryResults::Tuple(None) QueryResults::Tuple(None)
}; };
Ok(QueryOutput { Ok(QueryOutput {
spec: self.spec.clone(), spec: self.spec.clone(),
results: results, results: results,
}) })
} }
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> { fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns() self.spec.columns()
} }
} }
@ -195,7 +208,12 @@ pub(crate) struct RelTwoStagePullProjector {
} }
impl RelTwoStagePullProjector { impl RelTwoStagePullProjector {
fn with_templates(spec: Rc<FindSpec>, len: usize, templates: Vec<TypedIndex>, pulls: Vec<PullTemplate>) -> RelTwoStagePullProjector { fn with_templates(
spec: Rc<FindSpec>,
len: usize,
templates: Vec<TypedIndex>,
pulls: Vec<PullTemplate>,
) -> RelTwoStagePullProjector {
RelTwoStagePullProjector { RelTwoStagePullProjector {
spec: spec, spec: spec,
len: len, len: len,
@ -204,15 +222,13 @@ impl RelTwoStagePullProjector {
} }
} }
fn collect_bindings_into<'a, 'stmt, 'out>(&self, row: Row<'a, 'stmt>, out: &mut Vec<Binding>) -> Result<()> { fn collect_bindings_into<'a>(&self, row: &Row<'a>, 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.
assert!(row.column_count() >= self.len as i32); assert!(row.column_count() >= self.len);
let mut count = 0; let mut count = 0;
for binding in self.templates for binding in self.templates.iter().map(|ti| ti.lookup(&row)) {
.iter()
.map(|ti| ti.lookup(&row)) {
out.push(binding?); out.push(binding?);
count += 1; count += 1;
} }
@ -220,45 +236,60 @@ impl RelTwoStagePullProjector {
Ok(()) Ok(())
} }
pub(crate) fn combine(spec: Rc<FindSpec>, column_count: usize, mut elements: ProjectedElements) -> Result<CombinedProjection> { pub(crate) fn combine(
let projector = Box::new(RelTwoStagePullProjector::with_templates(spec, column_count, elements.take_templates(), elements.take_pulls())); spec: Rc<FindSpec>,
column_count: usize,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let projector = Box::new(RelTwoStagePullProjector::with_templates(
spec,
column_count,
elements.take_templates(),
elements.take_pulls(),
));
// If every column yields only one value, or if this is an aggregate query // If every column yields only one value, or if this is an aggregate query
// (because by definition every column in an aggregate query is either // (because by definition every column in an aggregate query is either
// aggregated or is a variable _upon which we group_), then don't bother // aggregated or is a variable _upon which we group_), then don't bother
// with DISTINCT. // with DISTINCT.
let already_distinct = elements.pre_aggregate_projection.is_some() || let already_distinct =
projector.columns().all(|e| e.is_unit()); elements.pre_aggregate_projection.is_some() || projector.columns().all(|e| e.is_unit());
elements.combine(projector, !already_distinct) elements.combine(projector, !already_distinct)
} }
} }
impl Projector for RelTwoStagePullProjector { impl Projector for RelTwoStagePullProjector {
fn project<'stmt, 's>(&self, schema: &Schema, sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result<QueryOutput> { fn project<'stmt, 's>(
&self,
schema: &Schema,
sqlite: &'s rusqlite::Connection,
mut rows: Rows<'stmt>,
) -> Result<QueryOutput> {
// Allocate space for five rows to start. // Allocate space for five rows to start.
// This is better than starting off by doubling the buffer a couple of times, and will // This is better than starting off by doubling the buffer a couple of times, and will
// rapidly grow to support larger query results. // rapidly grow to support larger query results.
let width = self.len; let width = self.len;
let mut values: Vec<_> = Vec::with_capacity(5 * width); let mut values: Vec<_> = Vec::with_capacity(5 * width);
let pull_consumers: Result<Vec<PullConsumer>> = self.pulls let pull_consumers: Result<Vec<PullConsumer>> = self
.iter() .pulls
.map(|op| PullConsumer::for_template(schema, op)) .iter()
.collect(); .map(|op| PullConsumer::for_template(schema, op))
.collect();
let mut pull_consumers = pull_consumers?; let mut pull_consumers = pull_consumers?;
// Collect the usual bindings and accumulate entity IDs for pull. // Collect the usual bindings and accumulate entity IDs for pull.
while let Some(r) = rows.next() { while let Some(r) = rows.next().unwrap() {
let row = r?; let row = r;
for mut p in pull_consumers.iter_mut() { for p in pull_consumers.iter_mut() {
p.collect_entity(&row); p.collect_entity(&row);
} }
self.collect_bindings_into(row, &mut values)?; self.collect_bindings_into(row, &mut values)?;
} }
// Run the pull expressions for the collected IDs. // Run the pull expressions for the collected IDs.
for mut p in pull_consumers.iter_mut() { for p in pull_consumers.iter_mut() {
p.pull(sqlite)?; p.pull(sqlite)?;
} }
@ -267,7 +298,7 @@ impl Projector for RelTwoStagePullProjector {
for p in pull_consumers.iter() { for p in pull_consumers.iter() {
p.expand(bindings); p.expand(bindings);
} }
}; }
Ok(QueryOutput { Ok(QueryOutput {
spec: self.spec.clone(), spec: self.spec.clone(),
@ -275,7 +306,7 @@ impl Projector for RelTwoStagePullProjector {
}) })
} }
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> { fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns() self.spec.columns()
} }
} }
@ -295,24 +326,32 @@ impl CollTwoStagePullProjector {
} }
} }
pub(crate) fn combine(spec: Rc<FindSpec>, mut elements: ProjectedElements) -> Result<CombinedProjection> { pub(crate) fn combine(
spec: Rc<FindSpec>,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let pull = elements.pulls.pop().expect("Expected a single pull"); let pull = elements.pulls.pop().expect("Expected a single pull");
let projector = Box::new(CollTwoStagePullProjector::with_pull(spec, pull.op)); let projector = Box::new(CollTwoStagePullProjector::with_pull(spec, pull.op));
// If every column yields only one value, or we're grouping by the value, // If every column yields only one value, or we're grouping by the value,
// don't bother with DISTINCT. This shouldn't really apply to coll-pull. // don't bother with DISTINCT. This shouldn't really apply to coll-pull.
let already_distinct = elements.pre_aggregate_projection.is_some() || let already_distinct =
projector.columns().all(|e| e.is_unit()); elements.pre_aggregate_projection.is_some() || projector.columns().all(|e| e.is_unit());
elements.combine(projector, !already_distinct) elements.combine(projector, !already_distinct)
} }
} }
impl Projector for CollTwoStagePullProjector { impl Projector for CollTwoStagePullProjector {
fn project<'stmt, 's>(&self, schema: &Schema, sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result<QueryOutput> { fn project<'stmt, 's>(
&self,
schema: &Schema,
sqlite: &'s rusqlite::Connection,
mut rows: Rows<'stmt>,
) -> Result<QueryOutput> {
let mut pull_consumer = PullConsumer::for_operation(schema, &self.pull)?; let mut pull_consumer = PullConsumer::for_operation(schema, &self.pull)?;
while let Some(r) = rows.next() { while let Some(r) = rows.next().unwrap() {
let row = r?; let row = r;
pull_consumer.collect_entity(&row); pull_consumer.collect_entity(&row);
} }
@ -328,8 +367,7 @@ impl Projector for CollTwoStagePullProjector {
}) })
} }
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> { fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns() self.spec.columns()
} }
} }

View file

@ -10,29 +10,14 @@
use std::rc::Rc; use std::rc::Rc;
use ::{ use {
Binding, rusqlite, Binding, CombinedProjection, Element, FindSpec, ProjectedElements, QueryOutput,
CombinedProjection, QueryResults, RelResult, Row, Rows, Schema, TypedIndex,
Element,
FindSpec,
ProjectedElements,
QueryOutput,
QueryResults,
RelResult,
Row,
Rows,
Schema,
TypedIndex,
rusqlite,
}; };
use query_projector_traits::errors::{ use query_projector_traits::errors::Result;
Result,
};
use super::{ use super::Projector;
Projector,
};
pub(crate) struct ScalarProjector { pub(crate) struct ScalarProjector {
spec: Rc<FindSpec>, spec: Rc<FindSpec>,
@ -47,8 +32,14 @@ impl ScalarProjector {
} }
} }
pub(crate) fn combine(spec: Rc<FindSpec>, mut elements: ProjectedElements) -> Result<CombinedProjection> { pub(crate) fn combine(
let template = elements.templates.pop().expect("Expected a single template"); spec: Rc<FindSpec>,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let template = elements
.templates
.pop()
.expect("Expected a single template");
let projector = Box::new(ScalarProjector::with_template(spec, template)); let projector = Box::new(ScalarProjector::with_template(spec, template));
let distinct = false; let distinct = false;
elements.combine(projector, distinct) elements.combine(projector, distinct)
@ -56,22 +47,26 @@ impl ScalarProjector {
} }
impl Projector for ScalarProjector { impl Projector for ScalarProjector {
fn project<'stmt, 's>(&self, _schema: &Schema, _sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result<QueryOutput> { fn project<'stmt, 's>(
let results = &self,
if let Some(r) = rows.next() { _schema: &Schema,
let row = r?; _sqlite: &'s rusqlite::Connection,
let binding = self.template.lookup(&row)?; mut rows: Rows<'stmt>,
QueryResults::Scalar(Some(binding)) ) -> Result<QueryOutput> {
} else { let results = if let Some(r) = rows.next().unwrap() {
QueryResults::Scalar(None) let row = r;
}; let binding = self.template.lookup(&row)?;
QueryResults::Scalar(Some(binding))
} else {
QueryResults::Scalar(None)
};
Ok(QueryOutput { Ok(QueryOutput {
spec: self.spec.clone(), spec: self.spec.clone(),
results: results, results: results,
}) })
} }
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> { fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns() self.spec.columns()
} }
} }
@ -84,7 +79,11 @@ pub(crate) struct TupleProjector {
} }
impl TupleProjector { impl TupleProjector {
fn with_templates(spec: Rc<FindSpec>, len: usize, templates: Vec<TypedIndex>) -> TupleProjector { fn with_templates(
spec: Rc<FindSpec>,
len: usize,
templates: Vec<TypedIndex>,
) -> TupleProjector {
TupleProjector { TupleProjector {
spec: spec, spec: spec,
len: len, len: len,
@ -93,41 +92,53 @@ impl TupleProjector {
} }
// This is just like we do for `rel`, but into a vec of its own. // This is just like we do for `rel`, but into a vec of its own.
fn collect_bindings<'a, 'stmt>(&self, row: Row<'a, 'stmt>) -> Result<Vec<Binding>> { fn collect_bindings<'a>(&self, row: &Row<'a>) -> 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.
assert!(row.column_count() >= self.len as i32); assert!(row.column_count() >= self.len);
self.templates self.templates
.iter() .iter()
.map(|ti| ti.lookup(&row)) .map(|ti| ti.lookup(&row))
.collect::<Result<Vec<Binding>>>() .collect::<Result<Vec<Binding>>>()
} }
pub(crate) fn combine(spec: Rc<FindSpec>, column_count: usize, mut elements: ProjectedElements) -> Result<CombinedProjection> { pub(crate) fn combine(
let projector = Box::new(TupleProjector::with_templates(spec, column_count, elements.take_templates())); spec: Rc<FindSpec>,
column_count: usize,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let projector = Box::new(TupleProjector::with_templates(
spec,
column_count,
elements.take_templates(),
));
let distinct = false; let distinct = false;
elements.combine(projector, distinct) elements.combine(projector, distinct)
} }
} }
impl Projector for TupleProjector { impl Projector for TupleProjector {
fn project<'stmt, 's>(&self, _schema: &Schema, _sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result<QueryOutput> { fn project<'stmt, 's>(
let results = &self,
if let Some(r) = rows.next() { _schema: &Schema,
let row = r?; _sqlite: &'s rusqlite::Connection,
let bindings = self.collect_bindings(row)?; mut rows: Rows<'stmt>,
QueryResults::Tuple(Some(bindings)) ) -> Result<QueryOutput> {
} else { let results = if let Some(r) = rows.next().unwrap() {
QueryResults::Tuple(None) let row = r;
}; let bindings = self.collect_bindings(row)?;
QueryResults::Tuple(Some(bindings))
} else {
QueryResults::Tuple(None)
};
Ok(QueryOutput { Ok(QueryOutput {
spec: self.spec.clone(), spec: self.spec.clone(),
results: results, results: results,
}) })
} }
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> { fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns() self.spec.columns()
} }
} }
@ -151,15 +162,13 @@ impl RelProjector {
} }
} }
fn collect_bindings_into<'a, 'stmt, 'out>(&self, row: Row<'a, 'stmt>, out: &mut Vec<Binding>) -> Result<()> { fn collect_bindings_into<'a>(&self, row: &Row<'a>, 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.
assert!(row.column_count() >= self.len as i32); assert!(row.column_count() >= self.len);
let mut count = 0; let mut count = 0;
for binding in self.templates for binding in self.templates.iter().map(|ti| ti.lookup(&row)) {
.iter()
.map(|ti| ti.lookup(&row)) {
out.push(binding?); out.push(binding?);
count += 1; count += 1;
} }
@ -167,29 +176,42 @@ impl RelProjector {
Ok(()) Ok(())
} }
pub(crate) fn combine(spec: Rc<FindSpec>, column_count: usize, mut elements: ProjectedElements) -> Result<CombinedProjection> { pub(crate) fn combine(
let projector = Box::new(RelProjector::with_templates(spec, column_count, elements.take_templates())); spec: Rc<FindSpec>,
column_count: usize,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let projector = Box::new(RelProjector::with_templates(
spec,
column_count,
elements.take_templates(),
));
// If every column yields only one value, or if this is an aggregate query // If every column yields only one value, or if this is an aggregate query
// (because by definition every column in an aggregate query is either // (because by definition every column in an aggregate query is either
// aggregated or is a variable _upon which we group_), then don't bother // aggregated or is a variable _upon which we group_), then don't bother
// with DISTINCT. // with DISTINCT.
let already_distinct = elements.pre_aggregate_projection.is_some() || let already_distinct =
projector.columns().all(|e| e.is_unit()); elements.pre_aggregate_projection.is_some() || projector.columns().all(|e| e.is_unit());
elements.combine(projector, !already_distinct) elements.combine(projector, !already_distinct)
} }
} }
impl Projector for RelProjector { impl Projector for RelProjector {
fn project<'stmt, 's>(&self, _schema: &Schema, _sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result<QueryOutput> { fn project<'stmt, 's>(
&self,
_schema: &Schema,
_sqlite: &'s rusqlite::Connection,
mut rows: Rows<'stmt>,
) -> Result<QueryOutput> {
// Allocate space for five rows to start. // Allocate space for five rows to start.
// This is better than starting off by doubling the buffer a couple of times, and will // This is better than starting off by doubling the buffer a couple of times, and will
// rapidly grow to support larger query results. // rapidly grow to support larger query results.
let width = self.len; let width = self.len;
let mut values: Vec<_> = Vec::with_capacity(5 * width); let mut values: Vec<_> = Vec::with_capacity(5 * width);
while let Some(r) = rows.next() { while let Some(r) = rows.next().unwrap() {
let row = r?; let row = r;
self.collect_bindings_into(row, &mut values)?; self.collect_bindings_into(row, &mut values)?;
} }
@ -199,7 +221,7 @@ impl Projector for RelProjector {
}) })
} }
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> { fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns() self.spec.columns()
} }
} }
@ -219,25 +241,36 @@ impl CollProjector {
} }
} }
pub(crate) fn combine(spec: Rc<FindSpec>, mut elements: ProjectedElements) -> Result<CombinedProjection> { pub(crate) fn combine(
let template = elements.templates.pop().expect("Expected a single template"); spec: Rc<FindSpec>,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let template = elements
.templates
.pop()
.expect("Expected a single template");
let projector = Box::new(CollProjector::with_template(spec, template)); let projector = Box::new(CollProjector::with_template(spec, template));
// If every column yields only one value, or if this is an aggregate query // If every column yields only one value, or if this is an aggregate query
// (because by definition every column in an aggregate query is either // (because by definition every column in an aggregate query is either
// aggregated or is a variable _upon which we group_), then don't bother // aggregated or is a variable _upon which we group_), then don't bother
// with DISTINCT. // with DISTINCT.
let already_distinct = elements.pre_aggregate_projection.is_some() || let already_distinct =
projector.columns().all(|e| e.is_unit()); elements.pre_aggregate_projection.is_some() || projector.columns().all(|e| e.is_unit());
elements.combine(projector, !already_distinct) elements.combine(projector, !already_distinct)
} }
} }
impl Projector for CollProjector { impl Projector for CollProjector {
fn project<'stmt, 's>(&self, _schema: &Schema, _sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result<QueryOutput> { fn project<'stmt, 's>(
&self,
_schema: &Schema,
_sqlite: &'s rusqlite::Connection,
mut rows: Rows<'stmt>,
) -> Result<QueryOutput> {
let mut out: Vec<_> = vec![]; let mut out: Vec<_> = vec![];
while let Some(r) = rows.next() { while let Some(r) = rows.next().unwrap() {
let row = r?; let row = r;
let binding = self.template.lookup(&row)?; let binding = self.template.lookup(&row)?;
out.push(binding); out.push(binding);
} }
@ -247,7 +280,7 @@ impl Projector for CollProjector {
}) })
} }
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> { fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns() self.spec.columns()
} }
} }

View file

@ -8,44 +8,26 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use std::collections::{ use std::collections::{BTreeMap, BTreeSet};
BTreeMap,
BTreeSet,
};
use core_traits::{ use core_traits::{Binding, Entid, StructuredMap, TypedValue};
Binding,
Entid,
StructuredMap,
TypedValue,
};
use mentat_core::{ use mentat_core::{Schema, ValueRc};
Schema,
ValueRc,
};
use edn::query::{ use edn::query::PullAttributeSpec;
PullAttributeSpec,
};
use mentat_query_pull::{ use mentat_query_pull::Puller;
Puller,
};
use query_projector_traits::errors::Result; use query_projector_traits::errors::Result;
use super::{ use super::{rusqlite, Index};
Index,
rusqlite,
};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct PullOperation(pub(crate) Vec<PullAttributeSpec>); pub(crate) struct PullOperation(pub(crate) Vec<PullAttributeSpec>);
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub(crate) struct PullIndices { pub(crate) struct PullIndices {
pub(crate) sql_index: Index, // SQLite column index. pub(crate) sql_index: Index, // SQLite column index.
pub(crate) output_index: usize, pub(crate) output_index: usize,
} }
@ -73,7 +55,11 @@ pub(crate) struct PullConsumer<'schema> {
} }
impl<'schema> PullConsumer<'schema> { impl<'schema> PullConsumer<'schema> {
pub(crate) fn for_puller(puller: Puller, schema: &'schema Schema, indices: PullIndices) -> PullConsumer<'schema> { pub(crate) fn for_puller(
puller: Puller,
schema: &'schema Schema,
indices: PullIndices,
) -> PullConsumer<'schema> {
PullConsumer { PullConsumer {
indices: indices, indices: indices,
schema: schema, schema: schema,
@ -83,18 +69,28 @@ impl<'schema> PullConsumer<'schema> {
} }
} }
pub(crate) fn for_template(schema: &'schema Schema, template: &PullTemplate) -> Result<PullConsumer<'schema>> { pub(crate) fn for_template(
schema: &'schema Schema,
template: &PullTemplate,
) -> Result<PullConsumer<'schema>> {
let puller = Puller::prepare(schema, template.op.0.clone())?; let puller = Puller::prepare(schema, template.op.0.clone())?;
Ok(PullConsumer::for_puller(puller, schema, template.indices)) Ok(PullConsumer::for_puller(puller, schema, template.indices))
} }
pub(crate) fn for_operation(schema: &'schema Schema, operation: &PullOperation) -> Result<PullConsumer<'schema>> { pub(crate) fn for_operation(
schema: &'schema Schema,
operation: &PullOperation,
) -> Result<PullConsumer<'schema>> {
let puller = Puller::prepare(schema, operation.0.clone())?; let puller = Puller::prepare(schema, operation.0.clone())?;
Ok(PullConsumer::for_puller(puller, schema, PullIndices::zero())) Ok(PullConsumer::for_puller(
puller,
schema,
PullIndices::zero(),
))
} }
pub(crate) fn collect_entity<'a, 'stmt>(&mut self, row: &rusqlite::Row<'a, 'stmt>) -> Entid { pub(crate) fn collect_entity<'a>(&mut self, row: &rusqlite::Row<'a>) -> Entid {
let entity = row.get(self.indices.sql_index); let entity = row.get(self.indices.sql_index).unwrap();
self.entities.insert(entity); self.entities.insert(entity);
entity entity
} }
@ -110,13 +106,18 @@ impl<'schema> PullConsumer<'schema> {
if let Some(pulled) = self.results.get(&id).cloned() { if let Some(pulled) = self.results.get(&id).cloned() {
bindings[self.indices.output_index] = Binding::Map(pulled); bindings[self.indices.output_index] = Binding::Map(pulled);
} else { } else {
bindings[self.indices.output_index] = Binding::Map(ValueRc::new(Default::default())); bindings[self.indices.output_index] =
Binding::Map(ValueRc::new(Default::default()));
} }
} }
} }
// TODO: do we need to include empty maps for entities that didn't match any pull? // TODO: do we need to include empty maps for entities that didn't match any pull?
pub(crate) fn into_coll_results(self) -> Vec<Binding> { pub(crate) fn into_coll_results(self) -> Vec<Binding> {
self.results.values().cloned().map(|vrc| Binding::Map(vrc)).collect() self.results
.values()
.cloned()
.map(|vrc| Binding::Map(vrc))
.collect()
} }
} }

View file

@ -8,10 +8,7 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use core_traits::{ use core_traits::{Binding, TypedValue};
Binding,
TypedValue,
};
/// The result you get from a 'rel' query, like: /// The result you get from a 'rel' query, like:
/// ///
@ -80,8 +77,12 @@ fn test_rel_result() {
}; };
let two_by_two = StructuredRelResult { let two_by_two = StructuredRelResult {
width: 2, width: 2,
values: vec![TypedValue::Long(5).into(), TypedValue::Boolean(true).into(), values: vec![
TypedValue::Long(-2).into(), TypedValue::Boolean(false).into()], TypedValue::Long(5).into(),
TypedValue::Boolean(true).into(),
TypedValue::Long(-2).into(),
TypedValue::Boolean(false).into(),
],
}; };
assert!(empty.is_empty()); assert!(empty.is_empty());
@ -96,13 +97,40 @@ 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).into()].as_slice())); assert_eq!(
assert_eq!(two_by_two.row(0), Some(vec![TypedValue::Long(5).into(), TypedValue::Boolean(true).into()].as_slice())); unit.row(0),
assert_eq!(two_by_two.row(1), Some(vec![TypedValue::Long(-2).into(), TypedValue::Boolean(false).into()].as_slice())); 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(); let mut rr = two_by_two.rows();
assert_eq!(rr.next(), Some(vec![TypedValue::Long(5).into(), TypedValue::Boolean(true).into()].as_slice())); assert_eq!(
assert_eq!(rr.next(), Some(vec![TypedValue::Long(-2).into(), TypedValue::Boolean(false).into()].as_slice())); 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); assert_eq!(rr.next(), None);
} }
@ -115,7 +143,10 @@ impl From<Vec<Vec<TypedValue>>> for RelResult<Binding> {
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().map(|v| v.into())).collect(), values: src
.into_iter()
.flat_map(|r| r.into_iter().map(|v| v.into()))
.collect(),
} }
} }
} }

View file

@ -8,67 +8,27 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use core_traits::{ use core_traits::{TypedValue, ValueType, ValueTypeSet};
TypedValue,
ValueType,
ValueTypeSet,
};
use mentat_core::{ use mentat_core::{SQLTypeAffinity, SQLValueType, SQLValueTypeSet, Schema, ValueTypeTag};
Schema,
SQLTypeAffinity,
SQLValueType,
SQLValueTypeSet,
ValueTypeTag,
};
use mentat_core::util::{ use mentat_core::util::Either;
Either,
};
use edn::query::{ use edn::query::Limit;
Limit,
};
use mentat_query_algebrizer::{ use mentat_query_algebrizer::{
AlgebraicQuery, AlgebraicQuery, ColumnAlternation, ColumnConstraint, ColumnConstraintOrAlternation,
ColumnAlternation, ColumnIntersection, ColumnName, ComputedTable, ConjoiningClauses, DatomsColumn, DatomsTable,
ColumnConstraint, OrderBy, QualifiedAlias, QueryValue, SourceAlias, TableAlias, VariableColumn,
ColumnConstraintOrAlternation,
ColumnIntersection,
ColumnName,
ComputedTable,
ConjoiningClauses,
DatomsColumn,
DatomsTable,
OrderBy,
QualifiedAlias,
QueryValue,
SourceAlias,
TableAlias,
VariableColumn,
}; };
use ::{ use {
CombinedProjection, projected_column_for_var, query_projection, CombinedProjection, ConstantProjector, Projector,
ConstantProjector,
Projector,
projected_column_for_var,
query_projection,
}; };
use mentat_query_sql::{ use mentat_query_sql::{
ColumnOrExpression, ColumnOrExpression, Constraint, FromClause, GroupBy, Op, ProjectedColumn, Projection,
Constraint, SelectQuery, TableList, TableOrSubquery, Values,
FromClause,
GroupBy,
Op,
ProjectedColumn,
Projection,
SelectQuery,
TableList,
TableOrSubquery,
Values,
}; };
use std::collections::HashMap; use std::collections::HashMap;
@ -92,7 +52,7 @@ impl ToColumn for QualifiedAlias {
impl ToConstraint for ColumnIntersection { impl ToConstraint for ColumnIntersection {
fn to_constraint(self) -> Constraint { fn to_constraint(self) -> Constraint {
Constraint::And { Constraint::And {
constraints: self.into_iter().map(|x| x.to_constraint()).collect() constraints: self.into_iter().map(|x| x.to_constraint()).collect(),
} }
} }
} }
@ -100,7 +60,7 @@ impl ToConstraint for ColumnIntersection {
impl ToConstraint for ColumnAlternation { impl ToConstraint for ColumnAlternation {
fn to_constraint(self) -> Constraint { fn to_constraint(self) -> Constraint {
Constraint::Or { Constraint::Or {
constraints: self.into_iter().map(|x| x.to_constraint()).collect() constraints: self.into_iter().map(|x| x.to_constraint()).collect(),
} }
} }
} }
@ -116,30 +76,31 @@ impl ToConstraint for ColumnConstraintOrAlternation {
} }
fn affinity_count(tag: i32) -> usize { fn affinity_count(tag: i32) -> usize {
ValueTypeSet::any().into_iter() ValueTypeSet::any()
.filter(|t| t.value_type_tag() == tag) .into_iter()
.count() .filter(|t| t.value_type_tag() == tag)
.count()
} }
fn type_constraint(table: &TableAlias, tag: i32, to_check: Option<Vec<SQLTypeAffinity>>) -> Constraint { fn type_constraint(
let type_column = QualifiedAlias::new(table.clone(), table: &TableAlias,
DatomsColumn::ValueTypeTag).to_column(); tag: i32,
to_check: Option<Vec<SQLTypeAffinity>>,
) -> Constraint {
let type_column = QualifiedAlias::new(table.clone(), DatomsColumn::ValueTypeTag).to_column();
let check_type_tag = Constraint::equal(type_column, ColumnOrExpression::Integer(tag)); let check_type_tag = Constraint::equal(type_column, ColumnOrExpression::Integer(tag));
if let Some(affinities) = to_check { if let Some(affinities) = to_check {
let check_affinities = Constraint::Or { let check_affinities = Constraint::Or {
constraints: affinities.into_iter().map(|affinity| { constraints: affinities
Constraint::TypeCheck { .into_iter()
value: QualifiedAlias::new(table.clone(), .map(|affinity| Constraint::TypeCheck {
DatomsColumn::Value).to_column(), value: QualifiedAlias::new(table.clone(), DatomsColumn::Value).to_column(),
affinity, affinity,
} })
}).collect() .collect(),
}; };
Constraint::And { Constraint::And {
constraints: vec![ constraints: vec![check_type_tag, check_affinities],
check_type_tag,
check_affinities
]
} }
} else { } else {
check_type_tag check_type_tag
@ -164,17 +125,23 @@ impl ToConstraint for ColumnConstraint {
fn to_constraint(self) -> Constraint { fn to_constraint(self) -> Constraint {
use self::ColumnConstraint::*; use self::ColumnConstraint::*;
match self { match self {
Equals(qa, QueryValue::Entid(entid)) => Equals(qa, QueryValue::Entid(entid)) => {
Constraint::equal(qa.to_column(), ColumnOrExpression::Entid(entid)), Constraint::equal(qa.to_column(), ColumnOrExpression::Entid(entid))
}
Equals(qa, QueryValue::TypedValue(tv)) => Equals(qa, QueryValue::TypedValue(tv)) => {
Constraint::equal(qa.to_column(), ColumnOrExpression::Value(tv)), Constraint::equal(qa.to_column(), ColumnOrExpression::Value(tv))
}
Equals(left, QueryValue::Column(right)) => Equals(left, QueryValue::Column(right)) => {
Constraint::equal(left.to_column(), right.to_column()), Constraint::equal(left.to_column(), right.to_column())
}
Equals(qa, QueryValue::PrimitiveLong(value)) => { Equals(qa, QueryValue::PrimitiveLong(value)) => {
let tag_column = qa.for_associated_type_tag().expect("an associated type tag alias").to_column(); let tag_column = qa
.for_associated_type_tag()
.expect("an associated type tag alias")
.to_column();
let value_column = qa.to_column(); let value_column = qa.to_column();
// A bare long in a query might match a ref, an instant, a long (obviously), or a // A bare long in a query might match a ref, an instant, a long (obviously), or a
@ -194,58 +161,71 @@ impl ToConstraint for ColumnConstraint {
if must_exclude_boolean { if must_exclude_boolean {
Constraint::And { Constraint::And {
constraints: vec![ constraints: vec![
Constraint::equal(value_column, Constraint::equal(
ColumnOrExpression::Value(TypedValue::Long(value))), value_column,
Constraint::not_equal(tag_column, ColumnOrExpression::Value(TypedValue::Long(value)),
ColumnOrExpression::Integer(ValueType::Boolean.value_type_tag())), ),
Constraint::not_equal(
tag_column,
ColumnOrExpression::Integer(ValueType::Boolean.value_type_tag()),
),
], ],
} }
} else { } else {
Constraint::equal(value_column, ColumnOrExpression::Value(TypedValue::Long(value))) Constraint::equal(
value_column,
ColumnOrExpression::Value(TypedValue::Long(value)),
)
} }
}
Inequality {
operator,
left,
right,
} => Constraint::Infix {
op: Op(operator.to_sql_operator()),
left: left.into(),
right: right.into(),
}, },
Inequality { operator, left, right } => { Matches(left, right) => Constraint::Infix {
Constraint::Infix { op: Op("MATCH"),
op: Op(operator.to_sql_operator()), left: ColumnOrExpression::Column(left),
left: left.into(), right: right.into(),
right: right.into(),
}
}, },
HasTypes {
Matches(left, right) => { value: table,
Constraint::Infix { value_types,
op: Op("MATCH"), check_value,
left: ColumnOrExpression::Column(left), } => {
right: right.into(),
}
},
HasTypes { value: table, value_types, check_value } => {
let constraints = if check_value { let constraints = if check_value {
possible_affinities(value_types) possible_affinities(value_types)
.into_iter() .into_iter()
.map(|(tag, affinities)| { .map(|(tag, affinities)| {
let to_check = if affinities.is_empty() || affinities.len() == affinity_count(tag) { let to_check = if affinities.is_empty()
|| affinities.len() == affinity_count(tag)
{
None None
} else { } else {
Some(affinities) Some(affinities)
}; };
type_constraint(&table, tag, to_check) type_constraint(&table, tag, to_check)
}).collect() })
.collect()
} else { } else {
value_types.into_iter() value_types
.map(|vt| type_constraint(&table, vt.value_type_tag(), None)) .into_iter()
.collect() .map(|vt| type_constraint(&table, vt.value_type_tag(), None))
.collect()
}; };
Constraint::Or { constraints } Constraint::Or { constraints }
}, }
NotExists(computed_table) => { NotExists(computed_table) => {
let subquery = table_for_computed(computed_table, TableAlias::new()); let subquery = table_for_computed(computed_table, TableAlias::new());
Constraint::NotExists { Constraint::NotExists { subquery: subquery }
subquery: subquery, }
}
},
} }
} }
} }
@ -254,7 +234,7 @@ pub enum ProjectedSelect {
Constant(ConstantProjector), Constant(ConstantProjector),
Query { Query {
query: SelectQuery, query: SelectQuery,
projector: Box<Projector>, projector: Box<dyn Projector>,
}, },
} }
@ -265,7 +245,9 @@ struct ConsumableVec<T> {
impl<T> From<Vec<T>> for ConsumableVec<T> { impl<T> From<Vec<T>> for ConsumableVec<T> {
fn from(vec: Vec<T>) -> ConsumableVec<T> { fn from(vec: Vec<T>) -> ConsumableVec<T> {
ConsumableVec { inner: vec.into_iter().map(|x| Some(x)).collect() } ConsumableVec {
inner: vec.into_iter().map(|x| Some(x)).collect(),
}
} }
} }
@ -278,7 +260,9 @@ impl<T> ConsumableVec<T> {
fn table_for_computed(computed: ComputedTable, alias: TableAlias) -> TableOrSubquery { fn table_for_computed(computed: ComputedTable, alias: TableAlias) -> TableOrSubquery {
match computed { match computed {
ComputedTable::Union { ComputedTable::Union {
projection, type_extraction, arms, projection,
type_extraction,
arms,
} => { } => {
// The projection list for each CC must have the same shape and the same names. // The projection list for each CC must have the same shape and the same names.
// The values we project might be fixed or they might be columns. // The values we project might be fixed or they might be columns.
@ -329,16 +313,14 @@ fn table_for_computed(computed: ComputedTable, alias: TableAlias) -> TableOrSubq
cc_to_select_query(projection, cc, false, vec![], None, Limit::None) cc_to_select_query(projection, cc, false, vec![], None, Limit::None)
}).collect(), }).collect(),
alias) alias)
}, }
ComputedTable::Subquery(subquery) => { ComputedTable::Subquery(subquery) => {
TableOrSubquery::Subquery(Box::new(cc_to_exists(subquery))) TableOrSubquery::Subquery(Box::new(cc_to_exists(subquery)))
}, }
ComputedTable::NamedValues { ComputedTable::NamedValues { names, values } => {
names, values,
} => {
// We assume column homogeneity, so we won't have any type tag columns. // We assume column homogeneity, so we won't have any type tag columns.
TableOrSubquery::Values(Values::Named(names, values), alias) TableOrSubquery::Values(Values::Named(names, values), alias)
}, }
} }
} }
@ -357,12 +339,14 @@ fn empty_query() -> SelectQuery {
/// Returns a `SelectQuery` that queries for the provided `cc`. Note that this _always_ returns a /// Returns a `SelectQuery` that queries for the provided `cc`. Note that this _always_ returns a
/// query that runs SQL. The next level up the call stack can check for known-empty queries if /// query that runs SQL. The next level up the call stack can check for known-empty queries if
/// needed. /// needed.
fn cc_to_select_query(projection: Projection, fn cc_to_select_query(
cc: ConjoiningClauses, projection: Projection,
distinct: bool, cc: ConjoiningClauses,
group_by: Vec<GroupBy>, distinct: bool,
order: Option<Vec<OrderBy>>, group_by: Vec<GroupBy>,
limit: Limit) -> SelectQuery { order: Option<Vec<OrderBy>>,
limit: Limit,
) -> SelectQuery {
let from = if cc.from.is_empty() { let from = if cc.from.is_empty() {
FromClause::Nothing FromClause::Nothing
} else { } else {
@ -374,33 +358,29 @@ fn cc_to_select_query(projection: Projection,
// a CTE (`WITH`). They're typically equivalent, but some SQL systems (notably Postgres) // a CTE (`WITH`). They're typically equivalent, but some SQL systems (notably Postgres)
// treat CTEs as optimization barriers, so a `WITH` can be significantly slower. Given that // treat CTEs as optimization barriers, so a `WITH` can be significantly slower. Given that
// this is easy enough to change later, we'll opt for using direct inclusion in `FROM`. // this is easy enough to change later, we'll opt for using direct inclusion in `FROM`.
let tables = let tables = from.into_iter().map(|source_alias| match source_alias {
from.into_iter().map(|source_alias| { SourceAlias(DatomsTable::Computed(i), alias) => {
match source_alias { let comp = computed.take_dangerously(i);
SourceAlias(DatomsTable::Computed(i), alias) => { table_for_computed(comp, alias)
let comp = computed.take_dangerously(i); }
table_for_computed(comp, alias) _ => TableOrSubquery::Table(source_alias),
}, });
_ => {
TableOrSubquery::Table(source_alias)
}
}
});
FromClause::TableList(TableList(tables.collect())) FromClause::TableList(TableList(tables.collect()))
}; };
let order = order.map_or(vec![], |vec| { vec.into_iter().map(|o| o.into()).collect() }); let order = order.map_or(vec![], |vec| vec.into_iter().map(|o| o.into()).collect());
let limit = if cc.empty_because.is_some() { Limit::Fixed(0) } else { limit }; let limit = if cc.empty_because.is_some() {
Limit::Fixed(0)
} else {
limit
};
SelectQuery { SelectQuery {
distinct: distinct, distinct: distinct,
projection: projection, projection: projection,
from: from, from: from,
group_by: group_by, group_by: group_by,
constraints: cc.wheres constraints: cc.wheres.into_iter().map(|c| c.to_constraint()).collect(),
.into_iter()
.map(|c| c.to_constraint())
.collect(),
order: order, order: order,
limit: limit, limit: limit,
} }
@ -433,18 +413,17 @@ fn re_project(mut inner: SelectQuery, projection: Projection) -> SelectQuery {
use self::Projection::*; use self::Projection::*;
let nullable = match &projection { let nullable = match &projection {
&Columns(ref columns) => { &Columns(ref columns) => columns
columns.iter().filter_map(|pc| { .iter()
match pc { .filter_map(|pc| match pc {
&ProjectedColumn(ColumnOrExpression::NullableAggregate(_, _), ref name) => { &ProjectedColumn(ColumnOrExpression::NullableAggregate(_, _), ref name) => {
Some(Constraint::IsNotNull { Some(Constraint::IsNotNull {
value: ColumnOrExpression::ExistingColumn(name.clone()), value: ColumnOrExpression::ExistingColumn(name.clone()),
}) })
},
_ => None,
} }
}).collect() _ => None,
}, })
.collect(),
&Star => vec![], &Star => vec![],
&One => vec![], &One => vec![],
}; };
@ -453,7 +432,9 @@ fn re_project(mut inner: SelectQuery, projection: Projection) -> SelectQuery {
return SelectQuery { return SelectQuery {
distinct: outer_distinct, distinct: outer_distinct,
projection: projection, projection: projection,
from: FromClause::TableList(TableList(vec![TableOrSubquery::Subquery(Box::new(inner))])), from: FromClause::TableList(TableList(vec![TableOrSubquery::Subquery(Box::new(
inner,
))])),
constraints: vec![], constraints: vec![],
group_by: group_by, group_by: group_by,
order: order_by, order: order_by,
@ -482,7 +463,9 @@ fn re_project(mut inner: SelectQuery, projection: Projection) -> SelectQuery {
SelectQuery { SelectQuery {
distinct: false, distinct: false,
projection: Projection::Star, projection: Projection::Star,
from: FromClause::TableList(TableList(vec![TableOrSubquery::Subquery(Box::new(subselect))])), from: FromClause::TableList(TableList(vec![TableOrSubquery::Subquery(Box::new(
subselect,
))])),
constraints: nullable, constraints: nullable,
group_by: vec![], group_by: vec![],
order: order_by, order: order_by,
@ -508,21 +491,28 @@ pub fn query_to_select(schema: &Schema, query: AlgebraicQuery) -> Result<Project
query: match pre_aggregate_projection { query: match pre_aggregate_projection {
// If we know we need a nested query for aggregation, build that first. // If we know we need a nested query for aggregation, build that first.
Some(pre_aggregate) => { Some(pre_aggregate) => {
let inner = cc_to_select_query(pre_aggregate, let inner = cc_to_select_query(
query.cc, pre_aggregate,
distinct, query.cc,
group_by_cols, distinct,
query.order, group_by_cols,
query.limit); query.order,
query.limit,
);
let outer = re_project(inner, sql_projection); let outer = re_project(inner, sql_projection);
outer outer
}, }
None => { None => cc_to_select_query(
cc_to_select_query(sql_projection, query.cc, distinct, group_by_cols, query.order, query.limit) sql_projection,
}, query.cc,
distinct,
group_by_cols,
query.order,
query.limit,
),
}, },
projector: datalog_projector, projector: datalog_projector,
} }
}, }
}) })
} }

File diff suppressed because it is too large Load diff

View file

@ -10,13 +10,9 @@
use std; // To refer to std::result::Result. use std; // To refer to std::result::Result.
use db_traits::errors::{ use db_traits::errors::DbError;
DbError,
};
use core_traits::{ use core_traits::Entid;
Entid,
};
pub type Result<T> = std::result::Result<T, PullError>; pub type Result<T> = std::result::Result<T, PullError>;

View file

@ -13,7 +13,7 @@ failure = "0.1.1"
path = "../query-pull-traits" path = "../query-pull-traits"
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.13" version = "0.21"
features = ["limits"] features = ["limits"]
[dependencies.edn] [dependencies.edn]

View file

@ -56,85 +56,72 @@
///! (pull ?person [:person/friend]) ///! (pull ?person [:person/friend])
/// [*])) /// [*]))
///! ``` ///! ```
extern crate failure; extern crate failure;
extern crate rusqlite; extern crate rusqlite;
extern crate core_traits;
extern crate edn; extern crate edn;
extern crate mentat_core; extern crate mentat_core;
extern crate core_traits;
extern crate mentat_db; extern crate mentat_db;
extern crate query_pull_traits; extern crate query_pull_traits;
use std::collections::{ use std::collections::{BTreeMap, BTreeSet};
BTreeMap,
BTreeSet,
};
use std::iter::{ use std::iter::once;
once,
};
use core_traits::{ use core_traits::{Binding, Entid, StructuredMap, TypedValue};
Binding,
Entid,
TypedValue,
StructuredMap,
};
use mentat_core::{ use mentat_core::{Cloned, HasSchema, Keyword, Schema, ValueRc};
Cloned,
HasSchema,
Keyword,
Schema,
ValueRc,
};
use mentat_db::cache; use mentat_db::cache;
use edn::query::{ use edn::query::{NamedPullAttribute, PullAttributeSpec, PullConcreteAttribute};
NamedPullAttribute,
PullAttributeSpec,
PullConcreteAttribute,
};
use query_pull_traits::errors::{ use query_pull_traits::errors::{PullError, Result};
PullError,
Result,
};
type PullResults = BTreeMap<Entid, ValueRc<StructuredMap>>; type PullResults = BTreeMap<Entid, ValueRc<StructuredMap>>;
pub fn pull_attributes_for_entity<A>(schema: &Schema, pub fn pull_attributes_for_entity<A>(
db: &rusqlite::Connection, schema: &Schema,
entity: Entid, db: &rusqlite::Connection,
attributes: A) -> Result<StructuredMap> entity: Entid,
where A: IntoIterator<Item=Entid> { attributes: A,
let attrs = attributes.into_iter() ) -> Result<StructuredMap>
.map(|e| PullAttributeSpec::Attribute(PullConcreteAttribute::Entid(e).into())) where
.collect(); A: IntoIterator<Item = Entid>,
{
let attrs = attributes
.into_iter()
.map(|e| PullAttributeSpec::Attribute(PullConcreteAttribute::Entid(e).into()))
.collect();
Puller::prepare(schema, attrs)? Puller::prepare(schema, attrs)?
.pull(schema, db, once(entity)) .pull(schema, db, once(entity))
.map(|m| m.into_iter() .map(|m| {
.next() m.into_iter()
.map(|(k, vs)| { .next()
assert_eq!(k, entity); .map(|(k, vs)| {
vs.cloned() assert_eq!(k, entity);
}) vs.cloned()
.unwrap_or_else(StructuredMap::default)) })
.unwrap_or_else(StructuredMap::default)
})
} }
pub fn pull_attributes_for_entities<E, A>(schema: &Schema, pub fn pull_attributes_for_entities<E, A>(
db: &rusqlite::Connection, schema: &Schema,
entities: E, db: &rusqlite::Connection,
attributes: A) -> Result<PullResults> entities: E,
where E: IntoIterator<Item=Entid>, attributes: A,
A: IntoIterator<Item=Entid> { ) -> Result<PullResults>
let attrs = attributes.into_iter() where
.map(|e| PullAttributeSpec::Attribute(PullConcreteAttribute::Entid(e).into())) E: IntoIterator<Item = Entid>,
.collect(); A: IntoIterator<Item = Entid>,
Puller::prepare(schema, attrs)? {
.pull(schema, db, entities) let attrs = attributes
.into_iter()
.map(|e| PullAttributeSpec::Attribute(PullConcreteAttribute::Entid(e).into()))
.collect();
Puller::prepare(schema, attrs)?.pull(schema, db, entities)
} }
/// A `Puller` constructs on demand a map from a provided set of entity IDs to a set of structured maps. /// A `Puller` constructs on demand a map from a provided set of entity IDs to a set of structured maps.
@ -159,9 +146,10 @@ impl Puller {
let lookup_name = |i: &Entid| { let lookup_name = |i: &Entid| {
// In the unlikely event that we have an attribute with no name, we bail. // In the unlikely event that we have an attribute with no name, we bail.
schema.get_ident(*i) schema
.map(|ident| ValueRc::new(ident.clone())) .get_ident(*i)
.ok_or_else(|| PullError::UnnamedAttribute(*i)) .map(|ident| ValueRc::new(ident.clone()))
.ok_or_else(|| PullError::UnnamedAttribute(*i))
}; };
let mut names: BTreeMap<Entid, ValueRc<Keyword>> = Default::default(); let mut names: BTreeMap<Entid, ValueRc<Keyword>> = Default::default();
@ -178,13 +166,12 @@ impl Puller {
attrs.insert(*id); attrs.insert(*id);
} }
break; break;
}, }
&PullAttributeSpec::Attribute(NamedPullAttribute { &PullAttributeSpec::Attribute(NamedPullAttribute {
ref attribute, ref attribute,
ref alias, ref alias,
}) => { }) => {
let alias = alias.as_ref() let alias = alias.as_ref().map(|ref r| r.to_value_rc());
.map(|ref r| r.to_value_rc());
match attribute { match attribute {
// Handle :db/id. // Handle :db/id.
&PullConcreteAttribute::Ident(ref i) if i.as_ref() == db_id.as_ref() => { &PullConcreteAttribute::Ident(ref i) if i.as_ref() == db_id.as_ref() => {
@ -193,21 +180,21 @@ impl Puller {
Err(PullError::RepeatedDbId)? Err(PullError::RepeatedDbId)?
} }
db_id_alias = Some(alias.unwrap_or_else(|| db_id.to_value_rc())); db_id_alias = Some(alias.unwrap_or_else(|| db_id.to_value_rc()));
}, }
&PullConcreteAttribute::Ident(ref i) => { &PullConcreteAttribute::Ident(ref i) => {
if let Some(entid) = schema.get_entid(i) { if let Some(entid) = schema.get_entid(i) {
let name = alias.unwrap_or_else(|| i.to_value_rc()); let name = alias.unwrap_or_else(|| i.to_value_rc());
names.insert(entid.into(), name); names.insert(entid.into(), name);
attrs.insert(entid.into()); attrs.insert(entid.into());
} }
}, }
&PullConcreteAttribute::Entid(ref entid) => { &PullConcreteAttribute::Entid(ref entid) => {
let name = alias.map(Ok).unwrap_or_else(|| lookup_name(entid))?; let name = alias.map(Ok).unwrap_or_else(|| lookup_name(entid))?;
names.insert(*entid, name); names.insert(*entid, name);
attrs.insert(*entid); attrs.insert(*entid);
}, }
} }
}, }
} }
} }
@ -218,11 +205,15 @@ impl Puller {
}) })
} }
pub fn pull<E>(&self, pub fn pull<E>(
schema: &Schema, &self,
db: &rusqlite::Connection, schema: &Schema,
entities: E) -> Result<PullResults> db: &rusqlite::Connection,
where E: IntoIterator<Item=Entid> { entities: E,
) -> Result<PullResults>
where
E: IntoIterator<Item = Entid>,
{
// We implement pull by: // We implement pull by:
// - Generating `AttributeCaches` for the provided attributes and entities. // - Generating `AttributeCaches` for the provided attributes and entities.
// TODO: it would be nice to invert the cache as we build it, rather than have to invert it here. // TODO: it would be nice to invert the cache as we build it, rather than have to invert it here.
@ -238,7 +229,8 @@ impl Puller {
schema, schema,
db, db,
self.attribute_spec.clone(), self.attribute_spec.clone(),
&entities)?; &entities,
)?;
// Now construct the appropriate result format. // Now construct the appropriate result format.
// TODO: should we walk `e` then `a`, or `a` then `e`? Possibly the right answer // TODO: should we walk `e` then `a`, or `a` then `e`? Possibly the right answer
@ -248,25 +240,28 @@ impl Puller {
// Collect :db/id if requested. // Collect :db/id if requested.
if let Some(ref alias) = self.db_id_alias { if let Some(ref alias) = self.db_id_alias {
for e in entities.iter() { for e in entities.iter() {
let mut r = maps.entry(*e) let r = maps
.or_insert(ValueRc::new(StructuredMap::default())); .entry(*e)
let mut m = ValueRc::get_mut(r).unwrap(); .or_insert(ValueRc::new(StructuredMap::default()));
let m = ValueRc::get_mut(r).unwrap();
m.insert(alias.clone(), Binding::Scalar(TypedValue::Ref(*e))); m.insert(alias.clone(), Binding::Scalar(TypedValue::Ref(*e)));
} }
} }
for (name, cache) in self.attributes.iter().filter_map(|(a, name)| for (name, cache) in self.attributes.iter().filter_map(|(a, name)| {
caches.forward_attribute_cache_for_attribute(schema, *a) caches
.map(|cache| (name.clone(), cache))) { .forward_attribute_cache_for_attribute(schema, *a)
.map(|cache| (name.clone(), cache))
}) {
for e in entities.iter() { for e in entities.iter() {
if let Some(binding) = cache.binding_for_e(*e) { if let Some(binding) = cache.binding_for_e(*e) {
let mut r = maps.entry(*e) let r = maps
.or_insert(ValueRc::new(StructuredMap::default())); .entry(*e)
.or_insert(ValueRc::new(StructuredMap::default()));
// Get into the inner map so we can accumulate a value. // Get into the inner map so we can accumulate a value.
// We can unwrap here because we created all of these maps… // We can unwrap here because we created all of these maps…
let mut m = ValueRc::get_mut(r).unwrap(); let m = ValueRc::get_mut(r).unwrap();
m.insert(name.clone(), binding); m.insert(name.clone(), binding);
} }
@ -275,5 +270,4 @@ impl Puller {
Ok(maps) Ok(maps)
} }
} }

Some files were not shown because too many files have changed in this diff Show more